1、嵌入式操作系统,陈香兰 Spring 2007 中国科学技术大学计算机系,xlanchen2007.6.11,Embedded Operating Systems,2,上周一,嵌入式Linux开发技术 嵌入式Linux开发综述 Linux的配置和编译 根文件系统及其制作,xlanchen2007.6.11,Embedded Operating Systems,3,上周二,基于i386体系结构的Linux启动代码分析 linux/arch/i386/boot/bootsect.S linux/arch/i386/boot/setup.S linux/arch/i386/boot/compress
2、ed/head.S linux/arch/i386/kernel/head.S linux/arch/init/main.c,xlanchen2007.6.11,Embedded Operating Systems,4,本次课,基于i386体系结构的Linux操作系统内核分析 一些基本概念 堆栈 用户态/内核态 虚拟内存 内存寻址,基于i386体系结构的Linux内核分析:一些预备知识,xlanchen2007.6.11,xlanchen2007.6.11,Embedded Operating Systems,6,声明,本课内容涉及到的Linux的内核分析,是基于Linux2.4.18内核源代
3、码的,具有一定的典型性,但不一定适用于所有其他的Linux内核版本,xlanchen2007.6.11,Embedded Operating Systems,7,操作系统的基本概念,任何计算机系统都包含一个基本的程序集合,称为操作系统。 内核(进程管理,进程调度,进程间通讯机制,内存管理,中断异常处理,文件系统,I/O系统,网络部分) 其他程序(例如函数库,shell程序等等) 操作系统的目的 与硬件交互,管理所有的硬件资源 为用户程序(应用程序)提供一个良好的执行环境,xlanchen2007.6.11,Embedded Operating Systems,8,一个典型的Linux操作系统的
4、结构,用户应用程序,System call,对硬件资源的管理,Shell,lib,Kernel implementation,xlanchen2007.6.11,Embedded Operating Systems,9,最简单也是最复杂的操作,在控制台下输入ls命令,Shell程序分析输入参数,确定这是ls命令,调用系统调用fork生成一个shell本身的拷贝,什么是系统调用?,为什么我们敲击键盘就会在终端上显示?,fork是什么? 为什么要调用fork?,中断的概念,终端控制台设备驱动的概念,保护模式和实模式,内存保护,内核态用户态相关问题,进程的描述,进程的创建。COW技术,系统调用是怎么
5、实现的?,软中断、异常的概念。陷阱门,系统门,调用exec系统调用将ls的可执行文件装入内存,内存管理模块,进程的地址空间,分页机制,文件系统,从系统调用返回,如何做到正确的返回?,堆栈的维护,寄存器的保存与恢复,Shell和ls都得以执行,进程的调度,运行队列等待队列的维护,xlanchen2007.6.11,Embedded Operating Systems,10,一些基本但很重要的概念,堆栈 内核态 vs 用户态 虚拟内存,xlanchen2007.6.11,Embedded Operating Systems,11,堆栈,堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间 函数
6、调用框架 传递参数 保存返回地址 提供局部变量空间 等等 C语言编译器对堆栈的使用有一套的规则 了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础,xlanchen2007.6.11,Embedded Operating Systems,12,堆栈寄存器和堆栈操作,堆栈相关的寄存器 esp,堆栈指针(stack pointer) ebp,基址指针(base pointer) 堆栈操作 push栈顶地址减少4个字节(32位) pop栈顶地址增加4个字节 ebp在C语言中用作记录当前函数调用基址,esp,ebp,高地址,低地址,esp,xlanchen2007.6.11,
7、Embedded Operating Systems,13,利用堆栈实现函数调用和返回,其他关键寄存器 cs : eip:总是指向下一条的指令地址 顺序执行:总是指向地址连续的下一条指令 跳转/分支:执行这样的指令的时候,cs : eip的值会根据程序需要被修改 call:将当前cs : eip的值压入栈顶,cs : eip指向被调用函数的入口地址 ret:从栈顶弹出原来保存在这里的cs : eip的值,放入cs : eip中 发生中断时? ?,xlanchen2007.6.11,Embedded Operating Systems,14,/ 调用者 call target ,/建立被调用者函
8、数的堆栈框架pushl %ebpmovl %esp, %ebp /拆除被调用者函数的堆栈框架movl %ebp,%esppopl %ebp ret,/被调用者函数体/do sth. ,call指令: 1)将下一条指令的地址A保存在栈顶 2)设置eip指向被调用程序代码开始处,将地址A恢复到eip中,xlanchen2007.6.11,Embedded Operating Systems,15,函数堆栈框架的形成,call xxx 执行call之前 执行call时,cs : eip原来的值指向call下一条指令,该值被保存到栈顶,然后cs : eip的值指向xxx的入口地址 进入xxx 第一条指
9、令: pushl %ebp 第二条指令: movl %esp, %ebp 函数体中的常规操作,可能会压栈、出栈 退出xxx movl %ebp,%esp popl %ebp ret,esp,ebp,高地址,低地址,cs : eip,esp,ebp,esp,ebp,esp,xlanchen2007.6.11,Embedded Operating Systems,16,C语言中还使用堆栈进行 参数的传递 局部变量的使用,xlanchen2007.6.11,Embedded Operating Systems,17,一段小程序,源文件:test.c 这是一个很简单的C程序 main函数中调用了函数p
10、1和p2 首先使用gcc生成test.c的可执行文件test 然后使用objdump S获得test的反汇编文件,xlanchen2007.6.11,Embedded Operating Systems,18,观察p2的堆栈框架,从test的反汇编文件中找到p2的反汇编代码 int p2(int x,int y) push %ebp mov %esp,%ebp return x+y; mov 0 xc(%ebp),%eax add 0 x8(%ebp),%eax pop %ebp ret,建立框架,拆除框架,ebp,esp,ebp,调用者 堆栈 框架,esp,ebp,y,x,高地址,低地址,x
11、lanchen2007.6.11,Embedded Operating Systems,19,观察main函数是如何传递参数给p2的, z=p2(x,y); pushl 0 xfffffff8(%ebp) pushl 0 xfffffff4(%ebp) call 804839b add $0 x8,%esp mov %eax,0 xfffffffc(%ebp) printf(%d=%d+%dn,z,x,y); pushl 0 xfffffff8(%ebp) pushl 0 xfffffff4(%ebp) pushl 0 xfffffffc(%ebp) push $0 x8048510 call
12、 80482b0 ,p2的返回值是如何返回给main的?,调用者 堆栈 框架,esp,ebp,y的值,x的值,高地址,低地址,被调用者 堆栈 框架,ebp,cs:eip,esp,ebp,esp,esp,xlanchen2007.6.11,Embedded Operating Systems,20,ebp,观察main中的局部变量,int main(void) push %ebp mov %esp,%ebp sub $0 x18,%esp char c=a; movb $0 x61,0 xfffffff3(%ebp) int x,y,z; x=1; movl $0 x1,0 xfffffff4(
13、%ebp) y=2; movl $0 x2,0 xfffffff8(%ebp) ,调用者,ebp,esp,ebp,esp,esp,c=a,x=1,y=2,高地址,低地址,xlanchen2007.6.11,Embedded Operating Systems,21,eip,eip,eip,eip,观察程序运行时堆栈的变化,main p1(c) p2(x,y) ,p1,p2,main,p2,p1,程序的代码段,堆栈,eip,esp,main堆栈,c,eip,eip,eip,p1的堆栈,esp,eip,eip,eip,x,y,eip,p2堆栈,eip,xlanchen2007.6.11,Embed
14、ded Operating Systems,22,另一段小程序和前一段小程序稍有不同,在这个小程序中,main函数中调用了函数p2,而在p2的执行过程中又调用了函数p1,xlanchen2007.6.11,Embedded Operating Systems,23,观察程序运行时堆栈的变化,eip,eip,eip,eip,main p2(x,y) ,p1,p2 p1(c) ,main,p2,p1,程序的代码段,堆栈,eip,esp,main堆栈,esp,eip,eip,x,y,eip,p2堆栈,eip,eip,eip,eip,c,eip,p1堆栈,esp,xlanchen2007.6.11,E
15、mbedded Operating Systems,24,观察堆栈在内核中的使用,在内核代码中经常有这样的函数,它的参数是struct pt_regs *regs 可以往回一层层的寻找这个参数是怎么传递过来的,最后我们可以发现最源头的函数使用了这样的参数struct pt_regs regs 比如void do_IRQ(struct pt_regs regs) 如果再进一步寻找是谁调用了这个do_IRQ,我们会发现只是一条简单的汇编语句 call do_IRQ,xlanchen2007.6.11,Embedded Operating Systems,25,为什么要有pt_regs结构,用户态
16、vs 内核态 寄存器上下文 从用户态切换到内核态时 必须保存用户态的寄存器上下文 要保存哪些? 保存在哪里? 中断/int指令会在堆栈上保存一些寄存器的值 如:用户态栈顶地址、当时的状态字、当时的cs:eip的值,xlanchen2007.6.11,Embedded Operating Systems,26,pt_regs结构,xlanchen2007.6.11,Embedded Operating Systems,27,SAVE_ALL和RESTORE_ALL,xlanchen2007.6.11,Embedded Operating Systems,28,do_IRQ的调用方式,仔细阅读一下
17、与之相连的汇编码 pushl $n-256 SAVE_ALL call do_IRQ jmp ret_from_intr,xlanchen2007.6.11,Embedded Operating Systems,29,do_IRQ的函数定义方式,regparm(x) x!=0:告诉gcc不通过堆栈而通过寄存器传。x是参数个数,寄存器依此使用EAX,EDX,ECX 而asmlinkage则使得编译器不通过寄存器(x=0)而 使用堆栈传递参数,因此,do_IRQ将栈顶的内容看成pt_regs 结构的参数,在必要时可以通过访问这里 的内容获得信息,xlanchen2007.6.11,Embedded
18、 Operating Systems,30,用户态和内核态的概念,Why? 假定不区分 用户直接修改操作系统的数据 用户直接调用操作系统的内部函数 用户直接操作外设 用户任意读/写物理内存,xlanchen2007.6.11,Embedded Operating Systems,31,因此,要区分用户态和内核态: 禁止用户程序和底层硬件直接打交道 (最简单的例子,如果用户程序往硬件控制寄存器写入不恰当的值,可能导致硬件无法正常工作) 禁止用户程序访问任意的物理内存 (否则可能会破坏其他程序的正常执行,如果对核心内核所在的地址空间写入数据的话,会导致系统崩溃),xlanchen2007.6.11
19、,Embedded Operating Systems,32,什么是用户态和内核态? 一般现代CPU都有几种不同的指令执行级别 在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态 而在相应的低级别执行状态下,代码的掌控范围会受到限制。只能在对应级别允许的范围内活动 举例:intel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级分别来表示内核态和用户态,xlanchen2007.6.11,Embedded Operating Systems,33,如何区分一段代码是核心态还是用户态 cs寄存器的最低两位表明了当前代码的特权级
20、 CPU每条指令的读取都是通过cs:eip这两个寄存器:其中cs是代码段选择寄存器,eip是偏移量寄存器。 上述判断由硬件完成 一般来说在Linux中,地址空间是一个显著的标志:0 xc0000000以上的地址空间只能在内核态下访问,0 x000000000 xbfffffff的地址空间在两种状态下都可以访问 注意:这里所说的地址空间是逻辑地址而不是物理地址,xlanchen2007.6.11,Embedded Operating Systems,34,虚拟内存,物理内存有限,是一种稀缺资源 局部性原理 空间局部性 时间局部性 按需调页 页框 利用磁盘上的交换空间,xlanchen2007.6
21、.11,Embedded Operating Systems,35,进程的虚拟地址空间 独立的地址空间(32位,4GB),每个进程一个 在Linux中,3G以上是内核空间,3G以下是用户空间 4G的进程地址空间使用进程私有的二级页表进行地址转换(虚拟地址物理地址) 页面大小:4KB 页目录、页表 若对应的内容在内存中,则对应的二级页表项记录相应的物理页框信息 否则根据需要进行装载或者出错处理,xlanchen2007.6.11,Embedded Operating Systems,36,进程调度后,执行一个新的被调度的进程之前,要先进行页表切换 Linux中的内核空间 每个进程3G以上的空间用
22、作内核空间 从用户地址空间进入内核地址空间不经过页表切换 而是通过中断/异常/系统调用入口(也只能如此),xlanchen2007.6.11,Embedded Operating Systems,37,站在CPU执行指令的角度,CPU,eip,esp,0 xc0000000,c=gets(),main,some action,进程管理,wait keyborad queue,进程x,进程x,idle,intr,8259,keyboard,中断处理,Wakeup progress,内核其他模块,esp,eip esp cs ds等等,esp,系统调用处理,idtr,xlanchen2007.6.
23、11,Embedded Operating Systems,38,从内存的角度来看,物理内存,0 x00000000,内核代码 内核静态数据,0 x00400000,0 x20000000,用户代码或数据,0 xc0000000,虚拟空间,(512M),(3G),在Linux中,物理内存 总是被映射在3G以上 的空间中, 若物理内存过大,需 使用其他的映射技术,0 x00000000,0 xe0000000,0 xffffffff,xlanchen2007.6.11,Embedded Operating Systems,39,作业5:,C语言中堆栈的作用是什么? 为什么要有内核态与用户态的区别,