20135302魏静静Linux内核分析第二周学习总结

操作系统是如何工作的

1. 小结:计算机是怎样工作的

三个法宝

存储程序计算机、函数调用堆栈、中断机制

两把宝剑

中断上下文、进程上下文的切换

2. 堆栈

  • 堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间。
    • 函数条用框架
    • 传递参数
    • 保存返回地址
    • 提供局部变量空间...
  • 堆栈相关寄存器:

    esp:堆栈指针——指向系统栈最上面一个栈帧的栈顶 ebp: 基址指针——指向系统栈最上面一个栈帧的底部 cs:eip:指令寄存器——指向下一条等待执行的指令地址
  • 堆栈相关操作:
    • push :栈顶地址减少4个字节
    • pop: 栈顶地址增加4个字节
    • call:将当前cs:eip值压栈,cs:eip指向被调函数入口地址。
    • ret:从栈顶弹出原来保存在此的cs:eip值,放入cs:eip中。
    • leave:当调用函数调用时,一般都有这两条指令pushl %ebpmovl %esp,%ebp,leave是这两条指令的反操作。

3. 函数调用约定

函数调用约定 参数传递顺序 负责清理参数占用的堆栈
__pascal 从左到右 调用者
__stdcall 从右到左 被调函数
__cdecl 从右到左 调用者
      • 调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。

        Windows中C/C++程序的缺省函数调用约定是__cdecl
        linux中gcc默认用的规则是__stdcall

4、C代码中嵌入汇编代码的写法

     __asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)
  •  汇编语句模板
       汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或“\n\t”分开。指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1,…,%9。
  •  输出部分
       输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。
  • 输入部分
       输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
  •  破坏描述部分
       破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有“memory”。
  • 限制字符
       限制字符有很多种,有些是与特定体系结构相关,它们的作用是指示编译器如何处理其后的C语言变量与指令操作数之间的关系。 

 

常用限制字符
分类 限定符 描述
通用寄存器 “a” 将输入变量放入eax
“b” 将输入变量放入ebx
“c” 将输入变量放入ecx
“d” 将输入变量放入edx
“s” 将输入变量放入esi
“d” 将输入变量放入edi
“q” 将输入变量放入eax,ebx,ecx,edx中的一个
“r” 将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个
“A” 把eax和edx合成一个64 位的寄存器(use long longs)
内存 “m” 内存变量
“o” 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址
“V” 操作数为内存变量,但寻址方式不是偏移量类型
“ ” 操作数为内存变量,但寻址方式为自动增量
“p” 操作数是一个合法的内存地址(指针)
寄存器或内存 “g” 将输入变量放入eax,ebx,ecx,edx中的一个或者作为内存变量
“X” 操作数可以是任何类型
立即数 “I” 0-31之间的立即数(用于32位移位指令)
“J” 0-63之间的立即数(用于64位移位指令)
“N” 0-255之间的立即数(用于out指令)
“n” 立即数
“p” 立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n”而不是“i”
匹配 & 该输出操作数不能使用过和输入操作数相同的寄存器
操作数类型 “=” 操作数在指令中是只写的(输出操作数)
“+” 操作数在指令中是读写类型的(输入输出操作数)
浮点数 “f” 浮点寄存器
“t” 第一个浮点寄存器
“u” 第二个浮点寄存器
“G” 标准的80387浮点常数
其它 % 该操作数可以和下一个操作数交换位置
# 部分注释,从该字符到其后的逗号之间所有字母被忽略
* 表示如果选用寄存器,则其后的字母被忽略

5.mykernel实验

  1. cd LinuxKernel/linux-3.9.4
  2. qemu -kernel arch/x86/boot/bzImage

     然后cd mykernel 可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c

  • 进程的启动
/*mymain.c*/ /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; asm volatile( "movl %1,%%esp\n\t" /* 将进程的sp赋给esp寄存器 */ "pushl %1\n\t" /* ebp入栈:因为在这里栈为空,esp=ebp,所以push的%1就是esp就是ebp。*/ "pushl %0\n\t" /* 进程入口ip入栈 */ "ret\n\t" /* 把进程入口ip赋给eip,即从这之后0号进程启动。*/ "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ );
  • 进程的切换:
   (1).下一个进程next->state == 0 即正在执行时
/*myinterrupt.c*/ //两个正在运行的进程之间做进程上下文切换 if(next->state == 0) /* state值的含义:-1表示没有执行,0表示正在执行,>0表示停止,这里为0,即进程正在执行 */ { /* 以下是进程切换关键代码 */ asm volatile( "pushl %%ebp\n\t" /* 把当前进程的ebp保存*/ "movl %%esp,%0\n\t" /* 把当前进程的esp赋值到sp中保存下来*/ "movl %2,%%esp\n\t" /* 把下一个进程的sp放到esp中*/ "movl $1f,%1\n\t" /* 把eip保存起来,$1f指接下来的标号1:的位置*/ "pushl %3\n\t" /*把下一个进程的eip保存起来*/ "ret\n\t" /* 还原eip */ "1:\t" /* 标号1,下一进程从此开始 */ "popl %%ebp\n\t" : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); }
 (2).进程是一个新进程,还从未执行过
/*myinterrupt.c*/ /*这段代码是当进程从未执行过时,所执行的动作,即启动一个进程 next->state = 0; /* 首先要把进程置为运行时状态,作为当前正在执行的进程 */ my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); /* 进程切换时的提示,从当前进程切换至下一进程*/ asm volatile( //混合编程 "pushl %%ebp\n\t" /* 保存ebp */ "movl %%esp,%0\n\t" /* 保存esp */ "movl %2,%%esp\n\t" /* 将下一进程的sp存入esp */ "movl %2,%%ebp\n\t" /* 将下一进程的bp存入ebp,因为栈空,所以esp和ebp指向同一位置 */ "movl $1f,%1\n\t" /* 保存eip */ "pushl %3\n\t" /*保存当前进程入口 */ "ret\n\t" /* 还原eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); 
  • 实验截图

 20135302魏静静Linux内核分析第二周学习总结_第1张图片

你可能感兴趣的:(20135302魏静静Linux内核分析第二周学习总结)