@iar
2016-08-23T01:33:34.000000Z
字数 6675
阅读 82
Linux
OS
Note
作者:Sandy 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/learn/USTC-1000072000
依照学术诚信条款,我保证此回答为本人原创,所有回答中引用的外部材料已经做了出处标记。
第一步: 初始状态
第二步: push %ebp
第三步: movl %esp, %ebp
第四步: return x+y
__asm__ volatile(
汇编语句模版:
输出部分:
输入部分:
破坏描述部分);
int main()
{
*val1+val2=val3*/
unsigned int val1 = 1;
unsigned int val2 = 2;
unsigned int val3 = 0;
printf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);
asm volatile(
"movl $0,%%eax\n\t" /*clear %eax to 0*/
"addl %1,%%eax\n\t" /*%eax += val1*/
"addl %2,%%eax\n\t" /*%eax += val2*/
"movl %%eax,%0\n\t" /*val2 = %eax*/
: "=m" (val3) /* output =m mean only write output memory variable*/
: "c" (val1),"d" (val2) /*input c or d mean %ecx/%edx*/
);
printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);
return 0
}
分析如下:
=m
表示write-only的, 内存变量
. "c", "d"
分别表示用%ecx, %edx
保存val1, val2.在output/input_regs中都可以在每一个var前面使用限定符
. 例如"c", "d", "=m"
.
设置时间片的大小, 时间片用完时设置一下调度标志.
, 时钟中断发生1000次且my_need_sched为0, 则使能调度.myinterrupt.my_schedule里面的movl $1f,%1
中$1f
是forwarding 标号1的位置, 果然见到下面隔2行就是1:
. 但是问题是: 在else中也有movl $1f, %1
, 但是else里面的内联汇编中没有label 1! 其实老师在论坛里面回答了:
这里分几种情况:1)两个正在运行的进程之间切换;2)新进程执行或切换到一个新进程
对于2显然需要通过ret修改eip指向新进程的起点,而对于1需要先保存当前进程的eip(即movl $1f,%1\n\t
)然后恢复即将执行的下一个进程的eip,这里与2相同通过ret修改eip,只是这里eip指向的是1:\t
的位置即$1f,为什么呢?因为即将执行的下一个进程的eip曾经被切换出去过,怎么被切换出去的?即movl $1f, %1\n\t
。
这里有个鸡生蛋蛋生鸡的循环,理解起来稍动脑筋,新进程的执行就是现有鸡还是现有蛋的问题.
孟宁老师2015-7-28
而且segmentFault帖子里面也回答了, 我觉得这个更好理解!
对论坛上 else中
movl $1f,%1\n\t
的问题,看到老师的回复是$1f放到eip里使用才算生效吧,不能见$1f就找标号1
。我的理解是这样的:if中的自然不必说,else中代码只有进程第一次被执行的时候才会运行,此时将$1f存入prev->thread.ip,但当进程被重新调度执行的时候,此时进入了if代码块中,因此将执行if代码块中的1:标号处的代码,所以else中没有"1:"也就不奇怪了。
计算机硬件的3大法宝是: 存储程序计算机模型, 程序调用堆栈, 和中断
. 同样, 操作系统的"两把剑": 中断上下文和进程上下文的切换
.
入口: init/main.c的start_kernel(void)的最后call: myinterrupt中的mymain.c的my_start_kernel(void).
mykernel实现的是一个简单的时间片轮转多道程序. 重点是进程的启动和切换机制. 是由内联汇编实现的(eip处理以及函数堆栈的恢复与建立).
进入my_start_kernel
movl %1,%%esp
使得我们进入了task[0].stack开始asm的my_start_kernel的stack如下: ![00]
movl %1,%%esp\n\t
: 将task[0].thread.sp赋值给esp, 这一句将我们导引到task[0]的stack. 因为task[0].thread.sp初始值是task[0]堆栈的base.pushl %1\n\t
: 将esp压栈, 或者说ebp. pushl %0
: 将task[0].thread.ip压栈, 初始值是&my_process.ret
: 将esp指向的slot的value赋值给eip, 并回退esp. 从而开始process 0.当第一次满足调动, 则进入my_schedule. 和往常的函数调用一样,会再进入my_schedule的开头保存context.
进入my_schedule中, 先调度: next设置为下一个node, 即task[1], prev=task[0].
注意: 由于task[0]没有运行过, 所以初始状态时unrunnable. 所以进入else. 将next的状态设置为runnable, current_task为task[1]. 进入switch to new process的inline asm.
注意task[0]中的ebp, esp已经update. 因为function call, 以及内部变量. 注意此时还是位于task[0]的stack 所以如图所示.
pushl %%ebp
: 压栈ebpmovl %%esp,%0
: 保存esp到prev->thread.sp. 即task[0]->thread.sp. 这是为了在proc0切入proc1之前保护proc0的context. 从而在schedule会proc0的时候可以恢复现场. (Q: 为什么只把esp存到pcb.thread?)movl %2,%%esp
: 恢复esp为next->thread.sp.movl %2,%%ebp
: 恢复ebp为next->thread.sp. 此刻进入task[1]的stack. 注意thread.sp初始值为task[1]的栈顶.movl $1f,%1
: 这个是一个难点, 按照老师的回复:
它把label 1的地址保存到prev->thread.ip. 虽然是forwarding. 但实际上compile之后, 是在运行的时候才将forwarding作为寻找方向. 因为prev->thread.ip只有在if中才用到. 所以不会有问题. (Q: 实际上, 我在compile optimization中将o2改成o1则会出现找不到label的问题...)
pushl %3
: 将next->thread.ip保存起来, 即task[1]->thread.ip. 即初始值: &my_process.
ret
: 将my_process pop给eip. 此时开始process 1的my_process. 在一定时候之后, 满足调度条件. 准备process 1切到process 2. pushl %%ebp
: 保存ebpmovl %%esp, $0
: 保存esp到prev->thread.sp. ebp压栈, 而esp保存到pcb的thread.sp是为什么呢? movl %2,%%esp
: 读取esp. 将esp设置为next->thread.sp. 从而esp又回到了task[0]的堆栈. 注意这个值在从0->1的时候已经保存到PCB.thread中, 是指向task[0]的栈顶. 此处保存了old ebp.movl $1f,%1
: 保存eip, 即将eip指向label 1. pushl %3
: 将next->thread.ip压栈. 为进入process 0的process设置eip.ret
: 结合上一句, eip回到了process 0. 即label 1处, 于是popl ebp
, 所以一个个的退回old ebp, 回到了my_start_kernel中inline asm的ret后的popl ebp
.process 0->process 1->process 2->process 3->process 0-> ...