徐洁原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
参照https://github.com/mengning/mykernel的源码,在用实验楼的虚拟机www.shiyanlou.com中进行实验。
在实验楼已经搭建好的环境中开始实验,会看到mykernel目录下有mymain.c、myinterrupt.c等源文件。将实现简单时间片轮转的多道程序内核代码拷贝到mykernel目录下,并重新编译。
运行如上图所示,实现了时间片的多道程序轮转,process0、1、2、3顺序切换。
1./linux/mykernel/mypcb.h主要定义了结构体Thread、tPCB。
2.分析/linux/mykernel/myinterrupt.c
my_timer_handler定义了时间片中断函数,通过time_count来统计时间片的长度,my_need_sched!=1,我们可以通过修改if条件来改变该process的运行时间。
接着来看看进程调度函数my_schedule()。
先定义next、prev,判断next是否处于可运行状态,处于runnable状态开始进程切换。分析这段内嵌汇编代码,先保存ebp在prev内核栈,将esp存入prev->thread.sp。再将next-thread.sp置于esp中,内核栈切换到next了。把标号1后的指令地址存入prev->thread.ip,保证下一次被调度运行时可切入prev。再把next->thread.ip存入next内核栈,该地址指向上一次next被调度时标号1后的popl指令所在地址。执行ret切换至next进程指令开始的地址,执行next进程。
如果该next处于不可运行状态,则将next的state置为0,将my_current_task指向next进程,分析内嵌汇编代码,先保存ebp至prev内核栈,esp存入prev->thread.sp,将next->thread.sp存入esp,再将esp指向ebp,即创建了next内核栈,把标号1后的指令地址存入prev->thread.ip以便下一次被调度可以切入。next->thread.ip存入next内核栈,返回至next栈指令开始的地址,开始执行next进程。
3.分析 /linux/mykernel/mymain.c
Void __init my_start_kernel(void)完成对0号进程的初始化,并创建更多的子进程。
用内联汇编代码如上所示,执行0进程。
判断my_need_sched是否等于1,不为1时,执行my_sched(),进行进程调度。
总结:
通过mykernel源码分析,结合《深入理解Linux内核》,对进程的创建和调度有了新的认识。内核创建0号进程,即设置进程PID=0,填充进程描述符的内容。0号进程在内核中创建1号进程(内核态),1号进程(内核态)执行init()完成内核的部分初始化工作及系统配置,init()调用execve()从文件/etc/inittab中加载可执行程序init并变成1号进程(用户态),即init进程。0号进程在引导结束就成为了CPU上的idle进程,在没有其他进程运行时才占用CPU,用于节能。而进程创建主要就是通过系统调用fork创建新进程。
进程的切换即内核栈的切换,进程先从用户态切换到内核态,再进行内核栈的切换,最后从内核态切回用户态,从而实现进程的切换。从源码中可以看出进程切换采用参数化上下文。并且Linux把线程描述符和内核态的进程堆栈紧凑的存放在一个单独为进程分配的存储区域,内核很容易从esp的值获得当前在CPU上正在运行的进程的thread_info结构的地址,进而获得该进程的进程描述符,这都利于提高进程调度的效率。
从进程创建,进程切换可以看出内核源码的精妙,高效率的进程管理机制可以有效改善整个操作系统的工作。