XV6 进程调度分析

进程调度主要代码在proc.c 另外trap.c syscall.c sysproc.c vm.c spinlock.c等文件中有相关部分。

主要特性总结如下:

    0、中断可以保证单CPU中代码段的原子性,自旋则实现多CPU之间的互斥。
    1、系统先自构造一个init进程的数据保存起来待运行,构造init进程需要调用userinit()函数,构造其他进程则需当前进程调用sys_fork()(实际为fork()函数),这两个函数都需要使用allocproc()函数来分配进程号进程内核栈等操作。不同之处在于userinit()是手工填入了进程的其他信息(段,寄存器,装入运行程序代码);fork()函数则是直接复制了父进程的大部分信息(没有实现写时复制),并设置返回值。
    2、每个CPU初始化完毕后就载入scheduler函数开始进程调度,scheduler函数是个死循环,不停的通过内循环遍历所有进程状态,由于遍历的过程中可能会改变进程状态,所以,一次遍历的内循环外需要加锁保护(多个CPU之间保护)。
    3、一旦scheduler函数发现有待运行的进程就调用swithuvm来载入这个进程段数据,并且把这个进程设置为运行中,调用swtch()来切换进程的内核态上下文,swtch()返回后就到了forkret()(新进程)或者sched()(yield,sleep,exit这种会导致进程切换的函数中调用的sched),具体哪个函数要看swtch切换的进程上下文状态而定。如果没有发现待执行进程则到第6步。
    4、进程从swtch()返回后,要把scheduler()加的锁释放掉,然后返回到系统调用或者中断返回处,进入用户态运行。
    5、如果运行中遇到时间片切换(则立刻调用yield()放弃CPU)或者上述导致进程切换的中断系统调用,进程则会从用户态进入内核态的对应函数。获得进程列表锁,然后根据当前函数来设置运行状态,再调用sched()函数检查进程状态,检查ok后,sched会调用swtch()把当前内核态切换到scheduler()中,这时候scheduler的状态就像从第3步的swtch()函数返回出来一样(swtch()所造成的静态代码连续,执行代码交错,超级无敌的函数间切换,天衣无缝),继续执行第3步的操作。
    6、scheduler()内循环执行了一遍。然后释放进程列表的锁,这是考虑到没有进程待执行,但另外的CPU需要进行进程切换。如果不释放进程列表所,另外的CPU就没有进行进程切换,导致CPU空转锁死。然后开始新一次的外循环,外循环开始的时候会开起中断,是为了防止没有进程待执行,但有进程在IO阻塞的时候,关闭中断使得进程永远都不会唤醒,从而导致CPU中断锁死。
    7、使用sleep()和wake()作为进程通信原语,但没有实现信号量,对临界资源的访问需要用自旋锁协助。
    8、使用exit()/kill()和wait()实现进程销毁,因为子进程无法回收自己的内核栈以及页目录等,所以交给父进程的wait()来处理。

你可能感兴趣的:(XV6 进程调度分析)