自己写一个最简单的嵌入式操作系统
2013-02-09
标签:

任务结构数组(或链表)的实现

我们的任务结构就采用链表形式吧,但其长度是限定了的,头指针是一个全局指针变量(指针变量是一个无符号整型指针,其指针本身所在的地址是在BSS段,但其指向的内容是分配在堆上的一片内存),分配内核内存的函数就用kmalloc吧,kmalloc函数需要自己编写呵,为了简单,这个函数只接受一个参数,就是所需分配大小,这个函数做得很简单,首先有一个全局针指,它在初始化时指向了整个堆的起始位置,并且固定大小,就是所谓的内核堆栈,在内核堆栈之后就是用户堆栈,由于总共有十个任务,当然不包括内核本身的任务,所以整个堆栈就平均分成十一部分,注意:在所有任务初始化完成之后,还有一个步骤就是将内核这个任务移到用户态,相当于要将自己的任务结构的堆栈指针修改一下就行了),判断大小是否超出了内核堆的可分配范围,还有一点,需要维护内核堆和其它任务的堆,需要进行分块,并且有一个全局的内存使用标识,就用数组吧,简单,0表示相应的内存部分未占用,1就表示占用,对应的kfree就相当于把标志置0),对于内存的维护,比较复杂,为了简单,就定为4K,并且不能进行大于四K的内存申请,因为大于4K之后,由于没有虚拟地址的概念,就不能实现堆上的连续分配地址,当然在栈上分配是可以大于4K的,栈是由编译器和CPU所决定了的

任务结构包括:

1.所剩的时间片

2.本任务所指向的代码段内存地址,这里也就是函数入口地址

3.本任务所指向的数据段地址,这里的数据段被包含进了整个内核中,所以并没有用,作为保留

4.本任务的函数体是否存在,也就是否会被调度

5.本任务所使用的栈指针

6.本任务所使用的堆指针

7.本任务的标识,用0代表是IDLE,1代表是其它进程

8.所有寄存器的值

9.当前PC值,初始化时被置成了函数入口地址

首先讲解一下任务数组结构的初始化:

将先定义一个全局指针,然后将此指针强制转换为一个任务结构指针,并通过kmalloc函在内核所占用的堆(前而讲过内核的堆的起始就是整个堆的起始)上去分配十个任务结构所占的内存,这里是绝不会超过4K的并且为这十个任务结构赋值,将第一个任务置为IDLE,时间片为20,代码段内存地址为main函数的的地址,数据段地址忽略,函数体存在,可以被调度,栈指针指向的位置根据以下来计算:

假定每个给每个任务可使用的堆栈设定为64K,而整个堆的起始位置是0x20030000,那么第一个堆指针所指向的就是0x20030000,栈就是0x20030000+64K的位置,第二个以后就以此类推

注意:在初始化任务结构之前,不允许系统使用堆,但可以使用栈,那么内核任务栈部分就分成了

两个,在未进行调度之前,栈就是上一页中第二步中所设的栈,那么上一页设置堆栈的时候就得注

意必须将堆栈空间设成十个64K再加上在本步骤使用以前的最大可能所需的栈空间

再讲解一下任务切换时所要做的事情:

进入整个中断处理入口时,会将所有寄存器推入IRQ栈之中,并把值拷贝到当前任务结构相应的字段当中,并取出被中断的进程的当前PC值存入当前任务结构中的相应字段中,接下就判别中断类型,以进入相应的中断处理函数,这里就会进入do_timer函数中,以下就是进入此函数之后的流程:

内核中还有一个全局指针,就是当前任务指针,它本身也是在系统BSS段中,它的定义如上一步中的那个全局指针一样,当由系统时钟中断之后,就取出这个全局指针,上一步初始化完成之后,还会把这个指针指向第一个任务结构所在位置,也就是0x20030000处,那么就取出这个任务结构中的时间片字段,判断其是否为0,若为0,就进行以下的操作:保存用户态下的栈指针至当前任务结构,保存堆指针,并将搜索一下可以被调度的任务结构,并将此任务结构赋给当前任务指针,置需要进行任务切换标识,此标识同样是一个全局变量,但它是被赋了初值,会放在整个系统的DATA段中,返回do_timer函数。若不为0,就进行以下操作:

将时间片减一,返回do_timer函数接下来判断任务切换标识,若为0,则进行以下操作:

不需要进行任务切换,所有寄存器出栈(这里的栈指的是IRQ栈),重新开启中断,切换到用户模式,加载当前任务结构中的当前PC值字段,以退出中断处理程序若此标识为1,则执行以下操作:

就需要进行任务切换,让所有寄存器出栈(这里的栈指的是IRQ栈),将当前任务结构中的所有寄

存器的值恢复到相应寄存器中,将用户态下的栈指针恢复至当前任务结构栈指针,将堆指针恢复至

当前任务结构堆指针,并把需要进行任务切换标识恢复为0,重新开启中断,切换到用户模式,任务切换是通过加载PC值来实现的,也就是通过加载当前任务结构中的当前PC值字段,以退出中断处理程序

系统调用的实现

本系统是完全可以不实现系统调用的,因为没有实现内核态和用户态的保护,完全可以不实现自己的C库,所有的函数都像kmalloc之类的实现一样,在内核中直接写函数原型,但为了以后扩展,还是说一下系统调用,这里以malloc系统调用来实现

首先说明还有一个堆指针(前面在kmalloc时有一个堆指针,不过那个堆指针是为内核任务,中断处理所提供),这里这个堆指针是用于用户态的,它在系统初始化完成之前会赋上初值,其初值就是第一个任务结构所使用的堆的起始位置,也就是在内核所使用的堆加上64K的位置

函数库中的malloc函数实现步骤如下:

1.首先检测申请大小是否超出了4K,若超出4K,就返回错误

2.进行系统调用(这里用_syscall1,并只传递一个参数(所需分配大小)

系统调用函数_syscall1的实现:

1.将寄存器压入堆栈(这里的栈指向就是当前任务的栈)

2.将系统调用号1放至R0,参数放入R1

3.发出SWI指令以产生SWI中断(就是所说的软中断,陷阱)

此时系统发生中断,会进入SWI中断处理入口,下面说一下SWI入口函数的实现

1.取出R0的值,判断其值,进入相应的分支处理代码段

2.在此进入_malloc处理代码段,取出R1的值,然后再得到前面所说的当前堆指针,并申请对应数据块大小,置用于内存占用标识的相应字段,将当前堆指针放入R0,移动当前堆指针,改变当前任务结构的堆指针,切换到用户态,返回SWI中断系统调用_syscall1的返回处理:

为了简单,在从内核态返回用户态时,不再进行任务的重新调度,所以上面的步骤就相对简单

1.当从SWI中断返回后,系统就运行在了用户态,此时取出R0的值,并赋值给需要申请内存的指针

2.在用户态弹出寄存器,返回到上一层函数

malloc函数的返回,此时malloc函数直接返回指针就行了,整个malloc的流程就结束了,其它的系统调用同这个过程类似

到此为止,这个操作系统初步实现了,但好像什么事情都不能做,如果让它支持串口中断的话,或许可以做那么一点点事情,比如像单片机那样的功能,整个系统的难点就是中断处理和任务切换,在本例中,由于ARM不支持像0x86那样的CPU级的保护模式,所以进行任务切换的时候,就得自己通过加载PC值的方法来实现,呵,因为我想不到更好的办法,但这个办法有一个不好解决的地方,就是寄存器入栈和出栈的保护,在进入中断时,必须保护寄存器,但如果需要进行重新调度,就得从中断上下文切换到进程上下文中,如何从中断上下文切换到进程上下文呢??我在这里所采用的方法很笨拙:

1.首先让寄存器入栈

2.让寄存器保存至当前任务结构数组,被中断掉的进程的PC值保存至任务结构

3.处理timer中断

4.如果进行任务切换,寻找下一个可调度的进程,然后把当前任务结构指下刚搜索到的任务结构,让寄存器出栈,恢复当前任务结构里的值到寄存器,恢复堆栈指针,切换到用户态,通过加载当前任务结构的PC值来恢复被挂起的进程这里在中断上下文中使用了任务结构,这在LINUX上好像是不这样用的,中断上下文和进程上下文是两个不同的概念,中断上下文中不能访问进程上下文里的任务结构,我实在想不出有什么办法来实现进程调度了,所以请看到我这则文章的人提出好一点的方法

共 4 页   上一页1234
可能会用到的工具/仪表
相关文章
推荐文章
热门文章
章节目录
本站简介 | 意见建议 | 免责声明 | 版权声明 | 联系我们
CopyRight@2024-2039 嵌入式资源网
蜀ICP备2021025729号