黄二玉+《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
预备知识
内嵌汇编
内嵌汇编语法:
_asm_(
汇编语句模板:
输出部分:
输入部分:
破坏描述部分);
同时”asm”也可以由“asm”来代替,“asm”是”asm“的别名。在”asm”后面有时也会加上”volatile“表示编译器不要优化代码,后面的指令保留原样,“volatile”是它的别名,在这里值得注意的是无论“asm“还是“volatile”中的每个下划线都不是一个单独的下划线拼成的,在括号里面的便是汇编指令。
例如,有如下一段程序:
int main(void)
{
int input,output,temp;
input=temp=1;
_asm_ _volatile_(
"movl $0,%%eax\n\t" /* eax=0*/
"movl %2,%%eax\n\t" /* eax=input */
"movl %%eax,%0\n\t " /* output=eax*/
:"=m"(output),"=m"(temp)
:"r"(input)
:"eax");
printf("%d %d\n",temp,output);
return 0;
}
这段会输出什么?标号6中的$0表示立即数0,把输出部分,输入部分中的变量进行编号依次为0,1,2,…,因此%0表示标号9的output,%1表示temp,%2表示input。
指令的跳转
cup中的ip寄存器总是存放了下一条要执行的指令,更重要的是ip寄存器的内容无法直接修改,但是可以间接的被修改,比如使用jmp,ret指令,如果要在c语言中实现跳转到指定的位置,可以借助栈和ret指令来实现,先看ret指令的作用:
- ret指令:想当于pop ip,把栈顶元素放到ip寄存器中。
为了实现跳转到指定位置(i.e 地址a),可以把a压栈,然后利用ret指令,push a; ret
,在ret指令执行完后ip寄存器指向的位置就是a了。
保存和恢复
当一个函数切换到另一个函数时需要保存前一个函数的信息,比如说临时变量,全局变量,因此需要在另一个函数开始执行的时候保存前一个函数的sp,bp中的数值,需要注意的是为什么不是进程而是函数?因为一个进程可以分为代码段,数据段,堆栈段等,而每个进程分配的堆栈段是不同的,因此进程切换时不仅仅保存sp,bp,还需要保存ss,cs,flag等等状态的信息,而在一个程序的函数间进行切换,代码段,数据段,和堆栈段都是一样的,只是从堆栈区分配给函数的栈不同,因此要保留每个函数自己的栈基址。
保存:
push bp; //bp入栈
mov sp,bp; //开始一个新的栈
恢复:
mov bp,sp; //使sp指向存储了前一个bp的单元
pop bp; // 把前一个基址恢复
例子
这个例子是借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断,里面的汇编代码如下:
mymain.c
上面的汇编代码主要是实现ip的转移myinterrupt.c
模拟进程之间的切换
运行程序输出如下: