路过的小游侠+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验二 时间片轮转多道程序
本周学习了一个简单的操作系统内核的工作模式。首先深入理解了函数调用堆栈的原理,函数开始使用enter初始化,结束使用leave恢复之前的状态。
并且总结了计算机操作系统的运行基本原理,即三大法宝:冯洛伊曼结构之存储程序计算机,高级语言基础之函数调用堆栈,以及实现多进程的中断。
实验:简单的时间片轮转多道程序的内核代码的实现
mypcb.h mymain.c myinterrupt.c这三个是mengning老师在git上的内核代码。
头文件mypcb.h
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8
struct Thread {
unsigned long ip;
unsigned long sp;
};
typedef struct PCB{
int pid;
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
char stack[KERNEL_STACK_SIZE];
/* CPU-specific state of this task */
struct Thread thread;
unsigned long task_entry;
struct PCB *next;
}tPCB;
void myschedule(void);
头文件定义了结构体Thread:用来保存指令指针eip和栈顶指针esp,PCB中有 pid进程号,state状态(-1未运行,0运行中,大于1停止),栈stack,以及线程thread,
task_entry入口地址以及next指针。
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>
#include "mypcb.h"
tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;
void my_process(void);
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];//线程调度状态不变,next指向自身。
/*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( //内嵌汇编,进程进栈,然后ret,使eip指向ebp完成进程的启动
"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*/
);
}
void my_process(void) //运行任务处理函数,循环一千万次,打印出自己pid检查my_need_schedule的标志,决定是否需要切换进程。
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 0)
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
if(my_need_sched == 1)
{
my_need_sched = 0;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
myinterupt.c 包含了两个函数 my_timer_handler(void)和my_schedule(void),
mytimerhandler 负责时钟中断处理,myschedule负责进程的切换
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;
}
在timecount为1000倍数时候,把my_need_sched置为1,此时在函数myprocess中符合if条件,调用my_schedule();
void my_schedule(void)
若下一个进程状态是可运行的,即就绪,则保存当前进程的上下文,将 ebp 压栈, esp 和 eip 的内容保存到 Thread 结构体中,从下一个进程控制块的 Thread 中取出 sp 和 ip 放到 esp 和 eip 中,从栈中弹出 ebp,完成进程切换。
若下一个进程不可运行,先要将其标记为可运行,然后再进行进程的上下文切换,这里与上面有一点不同的地方是此进程没有运行过,栈是空的,栈顶指针和栈基指针相同,所以是把 Thread 中的 sp 赋给 ebp。