前言
这是第二周的报告。本周的实验是:完成一个简单的时间片轮转多道程序内核代码,代码见视频中或从mykernel找。
老师已经为我们搭好了实验的环境——linux3.9.4下一个极其迷你的系统。我们不用去关心别的东西,只需要知道这个迷你系统从my_start_kernel
函数开始,系统时钟中断会执行my_timer_handler
函数。剩下的留给我们自己发挥。同时,实验要写的代码已经给出,所以完成这个实验的难度不大。实验的关键是理解所给的代码为什么要这么写,也就是理解程序如何切换。
代码与分析
mypcb.h
定义进程的属性和信息。
#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
实现了时钟中断处理和程序调度。
//记录时钟中断了多少次
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循环执行。
总结
了解了进程如何进行初始化和切换:实质就是寄存器和栈的切换。
问题:切换不需要保存当前的eax等通用寄存器吗?
xxtsmooc
原创作品转载请注明出处
《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。