王宣 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第一部分:环境搭建 环境可以直接用实验楼提供的环境,也可以按照孟宁老师github上的步骤来搭建,我是在自己的ubuntu上搭建的。 第二部分:实验分析 首先上截图 代码分析: 首先mymain.c里面的 #define MAX_TASK_NUM 10 //进程控制块的数目 #define KERNEL_STACK_SIZE 1024*8 //内核栈的大小 #define PRIORITY_MAX 30 //进程优先级 struct Thread { unsigned long ip;//指令寄存器eip的值(我的理解是进程切换时, //比如地址5,地址6,如果进程未被切换那么执行5处的代码后, //接着就执行6处的代码,但在执行5后要切换进程.此时eip的值是6处代码的地址) //那么此时把eip中的值放到当前进程的ip中,等下次轮到该进程执行时再使用 unsigned long sp;//保存进程将要切换时,当前进程esp的值,即栈顶地址 }; 进程控制块结构的定义 typedef struct PCB{ int pid; // 存放进程号 volatile long state;//存放进程的状态 char stack[KERNEL_STACK_SIZE];//内核栈 struct Thread thread; unsigned long task_entry;//进程执行的入口地址 struct PCB *next;//下一个的进程的进程控制块地址 unsigned long priority;// 进程优先级 //todo add other attrubte of process control block }tPCB; 上面是进程控制块的定义 下面是进程控制块的初始化等 __init my_start_kernel(void)函数创建若干个进程控制块 每个进程都有一个进程控制块PCB,里面存储进程的进程号,状态,进程的入口地址,ip的值,内核栈,内核栈栈顶的地址等 void __init my_start_kernel(void) { int pid = 0; //先初始化第一个进程控制块 task[pid].pid = pid;//填入自身的进程号 task[pid].state = 0;//状态设为0 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//这里设置ip和程序入口地址 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//内核栈的地址 task[pid].next = &task[pid];//填入自身地址 for(pid=1;pid<MAX_TASK_NUM;pid++) { memcpy(&task[pid],&task[0],sizeof(tPCB)); task[pid].pid = pid; task[pid].state = -1; task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].priority=get_rand(PRIORITY_MAX);//优先级参数是根据内核的随机数生成的 } task[MAX_TASK_NUM-1].next=&task[0];//相当于在尾节点填入首节点的地址,形成环 //这里开始执行0号进程 pid = 0; my_current_task = &task[pid];//my_current_task表示过当前运行进程,将0号进程赋值给它 asm volatile( "movl %1,%%esp\n\t" //%1指下面的"d" (task[pid].thread.sp),将当前进程内核栈的栈顶地址赋值给寄存器esp "pushl %1\n\t" //将sp的值压入当前进程内核栈中?感觉这里有点不对,好像应该插入 的是"movl %1,%%ebp\n\t" //因为0号进程还未运行时,ebp和esp的值是相同的,所以也应该对ebp赋值,如果是 //"pushl %1\n\t",那么下面的ret\n\t 弹回到eip的值就是sp的值,而不是ip的值了 "pushl %0\n\t" //将ip的值压栈,也就是0号进程的入口地址 "ret\n\t" //将0号进程的入口地址弹回到eip中 "popl %%ebp\n\t"//将之前压入的sp pop到ebp中 : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); 所以void __init my_start_kernel(void)中就是对进程先初始化,在启动进程 接着就是执行了 void my_process(void) { int i = 0; while(1) { i++; if(i%10000000 == 0) { if(my_need_sched == 1) { my_need_sched = 0; sand_priority(); my_schedule(); } } } } 因为进程初始化的时候 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 填入的入口地址是my_process的地址,这个函数里面有个while死循环,当i一直自增,而此时中断函数 void my_timer_handler(void) { #if 1 if(time_count%2000 == 0 && my_need_sched != 1) { my_need_sched = 1; } time_count ++ ; #endif return; } 中断函数里面的my_need_sched 初始化值为0,所以只要time_count自增到2000的倍数时, my_need_sched的值就变为1,这个时候my_process函数里面满足条件i%10000000 == 0 就执行下面代码
my_need_sched = 0;//将my_need_sched再次变为0,使中断功能继续 sand_priority();//设置优先级 根据以上函数设置进程优先级 my_schedule(); 接着是my_schedule()函数 get_next()函数就是一个for循环比较得出优先级最高的进程地址,然后返回 接着就正式进行进程切换 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ {//这个if里面的条件表示当前将要执行的进程已经运行过 asm volatile( "pushl %%ebp\n\t" //将当前ebp压入自己的内核栈中 "movl %%esp,%0\n\t" //将当前的esp保存到prev->thread.sp中, "movl %2,%%esp\n\t" //将将要运行的进程的sp放到esp中,即栈顶指针 "movl $1f,%1\n\t" //保存指令寄存器的值到当前进程的ip中,即prev->thread.ip "pushl %3\n\t" //先将将要执行的进程的ip,压入当前进程的栈中 "ret\n\t" //将刚才压到栈中的ip弹回到eip寄存器中 "1:\t" //下一进程从这里开始 "popl %%ebp\n\t" //这里应该是下一进程的栈顶指针popl回ebp中 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); my_current_task = next; } else { //将要切换的进程还未运行过的情况 next->state = 0; my_current_task = next; //下面的汇编进行进程切换,保存现场// asm volatile( "pushl %%ebp\n\t" //将ebp压入当前进程栈中 "movl %%esp,%0\n\t" //将当前esp的值存到当前进程的sp中,为下次运行做准备 "movl %2,%%esp\n\t" //将将要执行的进程的sp值放到esp中, "movl %2,%%ebp\n\t" //将将要执行的进程的sp的值放到ebp中 "movl $1f,%1\n\t" //存储当前eip的值,也就是当前进程下次执行的代码的地址存到当前进程的ip中 "pushl %3\n\t" "ret\n\t" //上面两句是将将要执行的ip赋值到eip寄存器中 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; } 主要的代码分析到这里就差不多结束了 第三部分:总结 之前对进程切换很迷糊,尤其对PCB这个东西,看fork()的时候,虽然能够理解,但是内部是怎么保存运行现场,怎么切换的,一直很晕,翻过那些讲linux的书, 然而看着就晕。。。这次通过这个精简过的程序代码,之前的很多的迷糊都明白了,虽然有些地方还是有些疑问,相信接下来的学习过程中会一点点的弄明白。
一个简单的时间片轮转多道程序内核代码的分析
最新推荐文章于 2020-11-27 14:23:44 发布