一.计算机系统工作的基本原理
计算机系统的基本工作原理其实就是"三大法宝"和"两把宝剑"
1. 计算机的"三大法宝"
计算机有"三大法宝", 分别是存储程序计算机, 函数调用堆栈,和中断.
1)存储程序计算机,冯诺依曼体系结构是很重要的需要理解的一环
- 上图很好的展示了存储程序计算机的工作原理
-
? 在这里,我们可以把CPU抽象成?个for循环,因为它总是在执?next instruction(下?条指令),然后从内存?取下?条指令来执?。从这个?度来看,内存保存指令和数据,CPU负责解释和执?这些指令,它们通过总线连接起来。这?揭示了计算机可以?动化执?程序的原理。
? 计算机的存储体系中最关键的是内存(Memory),是存储程序计算机模型中两个关键部件之?。每个内存单元有?个地址(Address),内存地址是从0开始编号的整数,CPU通过地址找到相应的内存单元,取其中的指令或者读写其中的数据。?个地址所对应的内存单元不能存很多东?,只能存?个字节,指令或int、?oat等多字节的数据保存在内存中要占?连续的多个字节的地址,这种情况下指令或数据的地址是它所占内存单元的起始字节的地址。
2)函数调用堆栈框架
堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间, 在这个堆栈上保存了函数调用框架, 传递的参数, 保存的返回地址, 函数内部 的局部变量等等参数.
一个函数调用动作是通过call
指令来完成的.一个完成的函数调用过程主要分为以下三步:
- call XXX
- 执行call之前
- 执行call时, cs:eip的原来的值指向call指令的下一条指令, 该值被保存到栈顶, 然后cs:eip的值指向XXX的入口地址, 即函数返回时是返回到call指令的下一条指令, 在C语言代码中代表着函数调用代码的 下一条代码的起始.
- 进入XXX
- pushl %ebp
- movl %esp, %ebp
- XXX的函数体
- 退出XXX
- movel %ebp, %esp
- popl %ebp
- ret
上述过程大致可以用如下的图来描述:
3)中断
中断最初?于避免CPU轮询I/O设备,就绪状态发?时让I/O设备主动通过中断信号通知CPU,??提?了CPU在输?输出上的?作效率,这就是硬件中断(外部中断)。后来随着中断适?范围扩?, ? 如解决机器运?过程出现的异常情况以及系统调?的实现等,这就产?了软件中断(内部中断),也称为异常,软件中断?分为故障(fault)和陷阱(trap)。
-
? 简而言之,在没有中断机制之前,计算机只能?个程序?个程序地执?,也就是批处理,??法多个程序并发?作。有了中断机制,CPU帮我们做了?件事情,就是当?个中断信号发?时,CPU把当前正在执?的进程X的CS:RIP寄存器和RSP寄存器等都压栈到了?个叫内核堆栈的地?,然后把CS:RIP指向?个中断处理程序的??,做保存现场的?作,然后去执?其他进程?如Y,等重新回来时再恢复现场,即恢复CS:RIP寄存器和RSP寄存器等到CPU上继续执?原进程X。显然中断机制在计算机系统中发挥着关键作?。
中断在定义上可以分为传统意义上的中断(硬件中断)和异常, 而
中断又分为:
- 可屏蔽中断(Maskableinterrupt)
- 非屏蔽中断(Nonmaskableinterrupt)
异常又分为:
-
处理器探测异常
由CPU执行指令时探测到一个反常条件时产生,如溢出、除0错等.根据发生异常时保存在内核堆栈中的eip的值可以进一步分为:
-
故障(fault)
eip=引起故障的指令的地址. 这种异常通常可以纠正,处理完异常时,该指令被重新执行. 例如缺页异常
-
陷阱(trap)
eip=随后要执行的指令的地址
-
异常终止
eip=???.发生严重的错误。eip值无效,只有强制终止受影响的进程
-
-
编程异常
这类异常通常由编程者发出的特定请求产生,通常由int类指令触发, 例如系统调用
关于中断和异常的处理流程, 又可以分为硬件处理流程以及软件处理流程
假定: 内核已经初始化, CPU在保护模式下运行, 此时发生了中断或者异常
-
中断和异常的硬件处理
- 确定与中断和异常相关的联的向量i
- 读取idtr寄存器指向的IDT表中的第i项
- 从gdtr寄存器中获得GDT的基地址, 并在GDT中查找, 以读取IDT表项中的段选择符标识的段描述符
- 确定中断时由授权的发生源发出的
- 检查是否发生了特权级的变化, 一般指的是是否由用户态陷入了内核态, 如果由用户态陷入了内核态, 需要使用新的特权级相关的堆栈
- 若发生的是故障, 用引起异常的指令地址修改cs和eip的值, 以使得这条指令在异常处理结束后能再次被执行
- 在栈中保存eflags, cs, 和eip的 内容
- 如果异常产生一个硬件出错吗, 则将他保存在栈中
- 装载cs和eip寄存器, 其值分别是IDT表中第i项门中描述符的段选择符和偏移量字段
经过上述的硬件级处理过后, 内核态的堆栈会发生变化, 但是由用户态进入中断和由内核态进入中断又不太一样, 如下图所示
-
中断和异常的软件级处理
- 在内核态堆栈中保存大多数寄存器的内容, 进一步保存上下文, 也就是pt_regs数据结构中描述的相关寄存器
- 调用C语言函数
- 通过ret_from_exception()从异常处理程序退出
经过上述处理之后, 进程堆栈进一步发生了变化:
2. 计算机的两大宝剑
计算有"两大宝剑", 分别是中断上下文和进程上下文
-
进程上下文
当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称 为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的 所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结 构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程 的执行。
-
中断上下文
“ 中断上下文”,其实可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境).需要注意的是, 中断上下文没有自己的上下文环境, 它占用的是被中断进程的上下文.
二.一个精简的Linux系统概念模型
? 假定现在内核已经初始化, CPU在保护模式下运行,且运行在用户态. 此时发生了中断或者异常, CPU首先回去读取idtr寄存器指向的IDT表中的第i项并从gdtr寄存器中获得GDT的基地址, 在GDT中查找, 以读取IDT表项中的段选择符标识的段描述符, 随后在栈中保存eflags, cs, 和eip的 内容.接着, CPU装载cs和eip寄存器. 然后进入中断软件级处理过程, 进程会切换到内核态并启用内核态堆栈, 在内核态堆栈上保存原来的cs:eip值, 接着, CPU进一步保存上下文, 也就是pt_regs数据结构中描述的寄存器.这些都保存完了之后, 进入中断处理例程. 处理完了之后, 检查如果此时没有新的优先级更高的中断到来, 便恢复寄存器并返回到用户态.
以用户读一个文件为例
? 当用户点击打开文件时, 会调用库函数中的read()函数, 而在这个函数中会触发软中断指令, CPU正式陷入到内核态, 进程堆栈也会切换到相应的内核态堆栈.接着, 进入异常处理的硬件级流程, 在这个过程中, CPU在栈中保存eflags, cs, 和eip的 内容, 随后进入异常处理的软件级流程, 在这个过程中, CPU会进一步保存上下文. 与这个过程相对应的是: CPU读取idtr寄存器指向的IDT表中的第128项,此时可以得到0x80中断门, 通过这个中断门CPU可以得到中断处理例程. 接着, 中断处理例程会到系统调用表中找到read()函数相对应的VFS虚拟文件系统中的系统调用sys_read()
到达VFS层次后, sys_read()会根据fd在进程打开文件表中找到相应的系统打开文件表(File数据结构), 然后执行系统打开文件表中的file operations中具体的操作.
文件打开之后, 将pt_regs数据结构中描述的寄存器以及cs:eip寄存器恢复, 此时进程又重新恢复到了用户态, 并沿着read()函数的下一条语句继续执行.上述具体的过程可以看看Linux文件系统的逻辑结构.如下图,
三.心得体会&改进意见
其实一开始的时候上课老走神,有些地方不是很懂。然后后面就去看了一下老师的Linux内核解析,庖丁解牛那本,讲的是真的好啊,一下就把课上不懂得部分弄懂了,然后才深刻理解到老师上课说的内容。
其实我发现老师得PPT大部分都是来自己庖丁解牛的,上课内容基本一致。当然课程给我的收获非常大,非常感谢孟老师!感觉孟老师的汇编代码啥的还是很好理解的,没啥可以改进的了,除了实验比较麻烦,做实验我配了起码3次环境。。。很多次都出现虚拟机报废的问题,不知道是不是版本不同的原因。至于后面的李老师上的课,代码是真的难懂。。。上课速度也比较快,导致很难接收,毕竟我不是偏嵌入式的。建议李老师多做点图片,生动一点讲解,PPT全是代码太枯燥了。