《Linux内核分析》mooc第二周笔记
作者:30erli
原创作品转载请注明出处 + 《Linux内核分析》MOOC课程
理解运行mykernel,它提供了初始化好了的CPU从my_start_kernel开始执行,并提供了时钟中断机制,周期性执行my_time_handler中断处理程序,执行完中断后总是可以返回到my_start_kernel中断位置继续执行。中断保存现场和恢复现场的细节都处理好了,mykernel就是一个逻辑上的硬件平台。
要实现一个自己写的时间片轮转调度内核,以现在的水平来说,还是很困难的。现在能读懂别人写好的代码,也能增加认识。 本次实验一共只有4个进程,进程0启动,进程0>进程1>进程2>进程3>进程0循环周期调度切换。切换过程中伴随保护现场,恢复现场汇编操作。
实验效果:


部分关键代码:
//最大进程数
#define MAX_TASK_NUM4
//每个进程内核堆栈的大小
#define KERNEL_STACK_SIZE 1024*8
/* CPU-specific state of this task */
struct Thread {
unsigned long ip;//用于暂存CPU的eip寄存器值
unsigned long sp;//用于暂存CPU的esp寄存器值
};
//定义进程控制块
//进程在操作系统中都有一个户口,用于表示这个进程。
//这个户口操作系统被称为PCB(进程控制块),在本实验中具体实现是struct PCB数据结构
typedef struct PCB{
int pid; //进程的ID
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);
extern tPCB task[MAX_TASK_NUM];//外部变量声明
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;//tick计数
/*
* Called by timer interrupt. //这个说明不太清楚
* it runs in the name of current running process,
* so it use kernel stack of current running process
*/
void my_timer_handler(void)
{
#if 1
if(time_count%1000 == 0 && my_need_sched != 1)//tick计数1000次,并且当前进程调度状态非置位。
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");//打印中断提示
my_need_sched = 1;//当前调度状态置位
}
time_count ++ ; //tick计数
#endif
return;
}
void my_schedule(void)
{
tPCB * next;//下一个进程控制块指针
tPCB * prev;//当前进程的前一个进程控制块指针
if(my_current_task == NULL
|| my_current_task->next == NULL)//这段代码根本不会执行。当前进程控制块指针为空,或者下一个进程控制块指针为空,跳出my_schedule
{
return;
}
printk(KERN_NOTICE ">>>my_schedule<<<\n");//打印调度提示
/* schedule */
next = my_current_task->next;//下一个进程控制块,暂存next
prev = my_current_task;//当前进程控制块,暂存prev
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped *///如果下一个进程控制块为可一执行的。
{
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp *///ebp压栈
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
"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)
);
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
}
else //初始化的时候进程控制块1,2,3的state==-1
{
next->state = 0; //将下一个进程控制块进程状态修改为可运行状态
my_current_task = next; //把下一个进程控制块指针赋值给当前进程指针
printk(KERN_NOTICE ">>>1switch %d to %d<<<\n",prev->pid,next->pid);//打印进程切换提示
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp *///压栈ebp
"movl %%esp,%0\n\t" /* save esp *///将当前esp状态,存入上一个进程控制块
"movl %2,%%esp\n\t" /* restore esp *///
"movl %2,%%ebp\n\t" /* restore ebp *///movl %esp,%ebp esp=thread.sp
"movl $1f,%1\n\t" /* save eip */ //把标号1处的内存地址,存入上一个进程控制块。
"pushl %3\n\t" //把当前进程的eip压栈
"ret\n\t" /* restore eip *///执行当前的进程代码。
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}
tPCB task[MAX_TASK_NUM];//定义一个进程控制块结构体数组。
tPCB * my_current_task = NULL;//初始化当前进程指针为NULL
volatile int my_need_sched = 0;//
void my_process(void);
void __init my_start_kernel(void)//初始化CPU之后,首先执行该函数
{
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;//进程0的入口地址为my_process在内存中的地址,同时将入口地址保存在进程0的thread.ip中,为进程切换做准备
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//取进程0内核栈顶地址为esp,并保存在进程0控制块的thread.sp中
task[pid].next = &task[pid];//进程控制块0的下一个进程控制块---暂时---赋值为它自己
/*fork more process */
for(i=1;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[0],sizeof(tPCB));//复制进程控制块0的所有值到进程控制块1,2...MAX_TASK_NUM。
task[i].pid = i; //修改进程控制块i的进程id为i
task[i].state = -1; //修改进程控制块i的进程状态为-1即不可运行状态
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];//取进程i内核栈顶地址为esp,并保存进程i控制块的thread.sp中。
task[i].next = task[i-1].next; //
task[i-1].next = &task[i]; //进程控制块0下一个指向进程控制块1然后依次1>2>3>0
}
/* start process 0 by task[0] */
pid = 0; //从进程0开始执行
my_current_task = &task[pid]; //把进程控制块0的地址,赋值给当前进程指针
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp *///把进程控制块0的thread.sp(实际是进程控制块0内核堆栈的最高地址)赋值给esp寄存器,
"pushl %1\n\t" /* push ebp */ //把thread.sp压栈
"pushl %0\n\t" /* push task[pid].thread.ip *///把进程控制块0的程序入口地址压栈即myprocess在内存中的地址。
"ret\n\t" /* pop task[pid].thread.ip to eip *///把刚压入栈的程序入口地址即myprocess弹出到eip寄存器。下一步就进入my_process开始执行了。
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
}
void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%300000000 == 0)//每10000000次i自加,执行一次下面代码段
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);//打印当前进程id
if(my_need_sched == 1) //如果该进程调度状态置位(在时钟周期中断中进行置位),则复位状态,执行调度程序。
{
my_need_sched = 0;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);//打印调度之后,当前执行的进程的id
}
}
}

浙公网安备 33010602011771号