这是第二周的报告。本周的实验是:完成一个简单的时间片轮转多道程序内核代码,代码见视频中或从mykernel找。linux
老师已经为咱们搭好了实验的环境——linux3.9.4下一个极其迷你的系统。咱们不用去关心别的东西,只须要知道这个迷你系统从my_start_kernel
函数开始,系统时钟中断会执行my_timer_handler
函数。剩下的留给咱们本身发挥。同时,实验要写的代码已经给出,因此完成这个实验的难度不大。实验的关键是理解所给的代码为何要这么写,也就是理解程序如何切换。git
mypcb.h
定义进程的属性和信息。github
#define MAX_TASK_NUM 10 //这个系统最多十个进程 #define KERNEL_STACK_SIZE 1024*8 //每一个进程的栈的大小 //进程的各类状态 #define MY_RUNNING 1 #define MY_SLEEP 2 #define MY_DEAD 3 //用于进程调度时,保存它栈地址和代码地址 struct Thread { unsigned long ip; unsigned long sp; }; typedef struct PCB { int pid; //进程总有个编号吧? volatile long state; char stack[KERNEL_STACK_SIZE]; struct Thread thread; unsigned long entry; //进程第一次执行开始的地方 struct PCB *next; //用于构造进程链表 unsigned long priority; //暂时没用到 }tPCB; void my_schedule(void);
总的来讲my_start_kernel
就是建立各个进程,而且进入0号进程。函数
#include "mypcb.h" //PCB结构信息 tPCB my_task[MAX_TASK_NUM]; //建立若干个PCB。也就是建立若干个任务 tPCB *my_current = NULL; //用来表示当前任务的指针。 //是否要进行程序切换的标记。1为须要,0相反。 //这个变量由my_timer_handler和my_process修改 extern volatile int my_need_sched; //建立的进程都执行这个函数 void my_process(void) { unsigned count = 0; unsigned slice = sizeof(count); while (1) { count += 1; //程序执行必定时间后检查是否要进行程序切换,不须要则输出信息 if (!(count<<slice)) { if (my_need_sched == 1) { my_need_sched = 0; my_schedule(); } else { printk(KERN_NOTICE "process %d is running\n", my_current->pid); } } } } //迷你系统从这个函数开始执行。 void __init my_start_kernel(void) { int i; /* init task 0 */ //初始化第一个进程,0号 my_task[0].pid = 0; my_task[0].state = MY_RUNNING; my_task[0].thread.sp = (unsigned long)&my_task[0].stac[KERNEL_STACK_SIZE-1]; //设置程序的入口,也就是my_process的地址 my_task[0].entry = my_task[0].thread.ip = (unsigned long)my_process; //环形链表 my_task[0].next = &my_task[0]; /* then init other "processes" */ //初始化其余进程。 for (i = 1; i < MAX_TASK_NUM; i++) { memcpy(&my_task[i], &my_task[0], sizeof(tPCB)); my_task[i].pid = i; my_task[i].state = MY_SLEEP; //只有0号醒着,其它都睡着了 my_task[i].thread.sp += (unsigned long)sizeof(tPCB); /* to make the list a big loop */ //环形,链表最后一个元素指向第一个元素 my_task[i].next = my_task[i-1].next; my_task[i-1].next = &my_task[i]; } /* going to switch to task 0! */ printk(KERN_NOTICE "main going to switch to task 0\n"); //好紧张,要开始切换了。 my_current = &my_task[0]; asm volatile( "movl %1, %%esp\n\t" //将esp和ebp设置为任务0的栈 "movl %1, %%ebp\n\t" "pushl %0\n\t" //不能直接修改eip,因此先将任务0的地址入栈 "ret\n\t" //再经过ret赋值给eip : :"c"(my_task[0].thread.ip), "d"(my_task[0].thread.sp) ); }
myinterupt.c
实现了时钟中断处理和程序调度。oop
//记录时钟中断了多少次 volatile unsigned long time_count = 0; volatile int my_need_sched = 0; //当前进程的PCB extern tPCB *my_current; /* * Called by timer interrupt. */ //每次时钟中断都会执行它 void my_timer_handler(void) { time_count += 1; //若是若干次中断后须要程序调度,修改my_need_sched。 if ((!(time_count<<COUNT_SLICE)) && my_need_sched != 1) { my_need_sched = 1; //my_process会检查它是否为1,如果,就执行下面的调度程序(程序主动调度) } } // void my_schedule(void) { tPCB *next = my_current->next; //将要执行的进程 tPCB *pre = my_current; //错误检查 if (!next) { printk(KERN_NOTICE "switch to NULL\n"); while (1); } printk(KERN_NOTICE "task %d is going to task %d\n", my_current->pid, next->pid); my_current = next; if (next->state == MY_RUNNING) { //若是要切换执行的程序已经醒了 asm volatile ( "pushl %%ebp\n\t" //保存当前ebp,esp,eip "movl %%esp, %0\n\t" "movl $1f, %1\n\t" "movl %2, %%esp\n\t" //切换到将要运行的栈 "pushl %3\n\t" //不能直接修改eip,因此经过给压栈再ret方式赋值 "ret\n\t" //切换eip。如今已是另一个进程了 "1:\n\t" //切换后从这里开始 "popl %%ebp\n\t" //恢复这个进程被切换以前的ebp :"=m"(pre->thread.sp), "=m"(pre->thread.ip) :"m"(next->thread.sp), "m"(next->thread.ip) ); } else if (next->state == MY_SLEEP) { //若是要切换执行的程序尚未运行过。过程和上面的切换差很少 next->state = MY_RUNNING; //先叫醒它 asm volatile ( "pushl %%ebp\n\t" "movl %%esp, %0\n\t" "movl $1f, %1\n\t" "movl %2, %%esp\n\t" "movl %2, %%ebp\n\t //新进程的栈是空的,因此要设置ebp "pushl %3\n\t" "ret\n\t" "1:\n\t" //被切换的进程下次执行从这里开始,而刚被叫醒的进程从my_process开始。 "popl %%ebp\n\t" //这个被切换的进程须要恢复ebp :"=m"(pre->thread.sp), "=m"(pre->thread.ip) : "m"(next->thread.sp), "m"(next->thread.ip) ); } }
进程从0到9,又从9到0循环执行。spa
了解了进程如何进行初始化和切换:实质就是寄存器和栈的切换。
问题:切换不须要保存当前的eax等通用寄存器吗?指针
xxtsmooc
原创做品转载请注明出处
《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000code