课程采用了mykernel的代码进行讲解
下面就是对mykernel代码的分析
大体上是在 start_kernel 中调用了 time_init,然后调用了my_start_kernel
而在time_init中将my_timer_handler 作为处理时钟中断的函数,每1ms?调用一次?
在my_start_kernel中建立了10个task结构体,并初始化,并形成了循环链表.
然后将第一个task装载到进程中.装载的过程下面讲,然后就开始了A进程
注意:每个进程的task结构体中的ip成员都是一样的,都是my_process函数的地址,所以不管是哪个进程,都是跑的my_process,虽然跑的指令一样,但是跑的数据不一样
//此时内核所有的精力都在A进程中.除了偶尔接收一下时钟中断
//设计的思路就在于在时钟中断的中断处理函数中.将A进程环境保存,装载B进程.然后B进程就在跑,内核所有的精力都在B进程,除了接收一下时钟中断.
//所以,除了更底层的东西,我们需要关注的是
1/装载一个进程(分为两种,一种是-1 unrunnable ,一种是0 runnable)
2/保存一个进程环境(只有一种)
我们可以在函数中看到,不管是装载进程,还是保存进程环境,都是用汇编写的
所以我们要知道
1/为什么要用汇编写这些东西
1/汇编写这些东西改变了3个寄存器,难道用C语言不能改变吗?可以,用fork和exec
1/之前装载一个进程是用fork和exec族,为什么不用fork和exec?
1/fork和exec是怎么实现的?
2/内嵌汇编怎么写?请查阅 <AT&T汇编语言与GCC内嵌汇编简介>
装载一个进程
- -1 unrunnable
//装载一个进程 -1 unrunnable
1/设置%esp寄存器
2/设置%ebp寄存器
3/设置%eip寄存器
//而在第0个进程中,是这么做的
pid = 0;
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*/
);
//一步一步分析
movl %1,%esp
//这个是将task[pid].thread.ip stack[4095]的地址放入 esp ,因为数组是往高处增长的,所以高处的地址更大.
pushl %1
//压栈,将esp的值压栈到stack[4094]位置
//其实一般这个位置应该是 push %ebp ,但是写了push %1 ,所以就代表 %1 的值就是%ebp的值,这个是老的ebp的值
pushl %0
//压栈,将task[pid].thread.ip 即 my_process 的地址 压栈到stack[4093]位置
ret
//弹栈,将my_process的地址转入eip,并esp+4,此时esp指向stack[4094]
下一步就应该执行my_process
然而popl %ebp 这个什么时候做呢???
//这个永远不会做
//问题来了,既然%esp,%eip都设置了,那么%ebp呢?
进程0中没有设置%ebp,是不是这样子?
mykernel/myinterrupt.c中也有一个加载新进程的例子
/* switch to new process */
asm volatile(
//"pushl %%ebp\n\t" /* save ebp */
//"movl %%esp,%0\n\t" /* save esp */
//新进程
"movl %2,%%esp\n\t" /* restore esp */
"movl %2,%%ebp\n\t" /* restore ebp */
//"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
我把无关的注释掉了,
movl %2,%esp
movl %2,%ebp
看来%esp和%ebp是一样的,都是next->thread.sp,都是stack[4095]
pushl %3
ret
修改了%esp
//可以看出来,这个mykernel/myinterrupt.c 是一个完整的修改 3个寄存器 的例子.但是之前的mymain.c没有修改%ebp
- 0 runnable
asm volatile(
//"pushl %%ebp\n\t" /* save 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)
);
//这个是mykernel/myinterrupt.c中的例子,我把无关的注释掉了
可以看到,这里面并没有修改%ebp,为什么呢?
//这里虽然在栈顶,可是没有popl %ebp 得到 %ebp
//所以下次调用函数的时候 或者 ret的时候第一句就要执行 pushl %ebp ,那么这个ebp 是多少呢? 是上一个环境的ebp????
因为后面可以看到,对于runnable 进程,ebp被压栈了,所以ebp一直都在栈里面
而且还多了一个 1: 和 popl %%ebp 为什么呢?
1: 好像是个标号.
- 可见上面加载一个unrunnable进程 和 runnable进程的区别主要是 在加载一个新进程的环境中 对 ebp的加载 有区别
- 而且在装载0进程 和 装载 runnable 进程的时候,我认为 %ebp都有问题,是不是这样子???
保存一个进程环境
1/保存%esp寄存器
2/保存%ebp寄存器
3/保存%eip寄存器
mykernel/myinterrupt.c中也有两个加载新进程的例子,本质其实一样,我把无关的注释了
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
//新进程
//"movl %2,%%esp\n\t" /* restore esp */
//"movl %2,%%ebp\n\t" /* restore ebp */
"movl $1f,%1\n\t" /* save eip */
//"pushl %3\n\t"
//"ret\n\t" /* restore eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
//可见ebp被堆栈了,且放到了栈顶,所以下次恢复的时候,直接popl %ebp 就可以了
//esp 和eip被保存到task结构体里面了.
pushl %%ebp
movl %%esp,%0
movl $1f,%1
//问题1:为什将 1f(即当前的%eip),这个数值 1f 保存到 prev->thread.ip 中?这个数值代表什么?
注意:
//在系统中,中断是调度的前提条件,如果屏蔽了中断,那么就不会再有调度
可见,调度时由定时器中断的中断处理函数做的.
由此可见,该中断的优先级一定不能低,如果低了的话,被禁止了,那么就没有调度了