一.基于时间片轮转调度代码的解读
代码结构主要由三个文件组成:
1.mypcb.h
2.myinterrupt.c
3.mymain.c
1.进程控制块(mypcb.h)
/* CPU-specific state of this task */ struct Thread{ unsigned long ip; //eip,程序入口地址 unsigned long sp; //堆栈esp栈顶地址 }; typedef struct PCB{ int pid; //进程pid号 volatile long state; //进程运行状态,-1 unrunnable,0 runnable,>0 stopped char stack[KERNEL_STACK_SIZE]; //进程的栈空间 /* CPU-specific state of this task */ struct Thread thread; //CPU的相关状态 unsigned long task_entry; //进程运行对应的函数 struct PCB *next; //下一个进程块地址 }tPCB; void my_schedule(void);
这里进程控制块(PCB)是采用链表的形式链接起来的。
2.进程的切换(myinterrupt.c)
extern tPCB task[MAX_TASK_NUM]; //进程控制块数组 extern tPCB *my_current_task; //当前对应的进程控制块 extern volatile int my_need_sched; //标志字段,来表示是否需要对进程进行调度 //时钟中断,周期性调用这个函数 void my_timer_handler(void){ #if 1 if(time_count%1000==0 && my_need_sched!=1){ printk(KERN_NOTICE">>>my_timer_handler here<<<\n"); my_need_sched=1; } time_count++; #endif return; }
接下来是进程上下文切换最关键的代码(my_schedule函数),采用的是内嵌汇编代码
当下一个进程的状态是正在运行时,则
if(next->state==0){// -1 unrunnable,0 runnable,>0 stopped /*switch to next process */ asm volatile( "pushl %%ebp\n\t" //保存当前进程的栈基址指针 ebp "movl %%esp,%0\n\t" //保存当前进程的栈顶指针 esp "movl %2,%%esp\n\t" //回复下一个进程的栈顶指针 esp "movl $1f,%1\n\t" //将当前进程的下一个指令地址保存到thread.ip中 "pushl %3\n\t" //将下一个进程的指令执行地址入栈 "ret\n\t" //ip出栈, "1:\t" //next process start here "popl %%ebp\n\t" //构建下一个进程的堆栈 :"=m"(prev->thread.sp),"=m"(prev->thread.ip) :"m"(next->thread.sp),"m"(next->thread.ip) ); }
若下一个进程是新的进程,还没有执行过,基本方式与上面差不多,也是基于内嵌汇编方式实现上下文切换。
3.内核代码运行(mymain.c)
内核从my_start_kernel开始执行,my_start_kernel主要是进行进程控制块的初始化,同时启动0号进程。
void __init my_start_kernel(void) { int pid = 0; int i; /* Initialize process 0*/ task[pid].pid = pid; task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid]; /*fork more process */ for(i=1;i<MAX_TASK_NUM;i++) { memcpy(&task[i],&task[0],sizeof(tPCB)); task[i].pid = i; task[i].state = -1; task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; task[i].next = task[i-1].next; task[i-1].next = &task[i]; } /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; asm volatile( "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ "pushl %1\n\t" /* push ebp */ "pushl %0\n\t" /* push task[pid].thread.ip */ "ret\n\t" /* pop task[pid].thread.ip to eip */ "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); }
然后0号进程开始执行my_process函数。
4.程序运行的结果
二.实验总结
通过完成这个简单的时间片轮转多道程序的实验,让我更加深刻的理解了进程上下文切换的原理,以及内核启动的初始化过程。