趣谈Linux操作系统笔记02(对应06-08)

0 计算机的工作模式

0.1 硬件图和计算机的逻辑图

0.2 cpu的组成

  1. 运算单元
  2. 数据单元
  3. 控制单元

1 x86架构

1.1 历史

x86泛指一系列由英特尔公司开发处理器的架构,这类处理器最早为1978年面市的“Intel 8086”CPU。
该系列较早期的处理器名称是以数字来表示80x86。由于以“86”作为结尾,包括Intel 8086、80186、80286、80386以及80486,因此其架构被称为“x86”。由于数字并不能作为注册商标,因此Intel及其竞争者均在新一代处理器使用可注册的名称,如Pentium。现时英特尔将其称为IA-32,全名为“Intel Architecture, 32-bit”,一般情形下指代32位的架构。

1.2 8086的原理

8086的cpu包含以下几个部分:

  • 数据单元:AX、BX、CX、DX、SP、BP、SI、DI寄存器
  • 控制单元:IP(指令指针寄存器,指向代码段中下一条指令的位置 )、CS(代码段寄存器(Code Segment Register),通过它可以找到代码在内存中的位置)、DS(数据段的寄存器,通过它可以找到数据在内存中的位置)、SS(栈寄存器)、ES

    1.3 课堂练习

    Q: mov, call, ret, jmp, int, add, or, xor, shl, ahr, push, pop, inc, dec, sub, cmp指令含义?
    A:
  • mov: 将第二个操作数(源操作数)复制到第一个操作数(目标操作数)。源操作数可以是立即值,通用寄存器,段寄存器或存储器位置; 目标寄存器可以是通用寄存器,段寄存器或存储器位置。两个操作数必须大小相同,可以是字节,字,双字或四字。MOV指令不能用于加载CS寄存器。尝试这样做会导致无效的操作码异常(#UD)。要加载CS寄存器,请使用far JMP,CALL或RET指令。
  • call : 保存将堆栈和分支上的信息链接到使用目标操作数指定的调用过程的过程。目标操作数指定被调用过程中第一条指令的地址。操作数可以是立即值,通用寄存器或存储器位置。
  • ret : 将程序控制流转移到位于堆栈顶部的返回地址。地址通常由CALL指令放在堆栈上,并返回到CALL指令后面的指令。
  • jmp : 将程序控制流转移到指令流中的不同点而不记录返回信息。
    int : 调用中断
  • add : 添加目标操作数(第一个操作数)和源操作数(第二个操作数),然后将结果存储在目标操作数中。目标操作数可以是寄存器或存储器位置; 源操作数可以是立即数,寄存器或存储器位置。(但是,在一条指令中不能使用两个存储器操作数。)当立即值用作操作数时,它将符号扩展为目标操作数格式的长度。ADD指令执行整数加法。它评估有符号和无符号整数操作数的结果,并设置CF和OF标志,分别表示有符号或无符号结果中的进位(溢出)。SF标志指示签名结果的符号
  • or : 在目标(第一个)和源(第二个)操作数之间执行按位包含OR运算,并将结果存储在目标操作数位置。源操作数可以是立即数,寄存器或存储器位置; 目标操作数可以是寄存器或存储器位置。(但是,在一条指令中不能使用两个存储器操作数。)如果第一和第二操作数的相应位都为0,则OR指令结果的每一位都设置为0; 否则,每个位都设置为1。
  • xor : 对目标(第一个)和源(第二个)操作数执行按位异或(XOR)运算,并将结果存储在目标操作数位置。源操作数可以是立即数,寄存器或存储器位置; 目标操作数可以是寄存器或存储器位置。(但是,一条指令不能使用两个存储器操作数。)如果操作数的相应位不同,则结果的每一位都为1; 如果相应的位相同,则每个位为0。
  • shl : 逻辑左移
  • shr :逻辑右移
  • push : 堆栈指针寄存器的值递减,然后将源操作数存储在堆栈顶部
  • pop: 将值从堆栈顶部加载到使用目标操作数(或显式操作码)指定的位置,然后递增堆栈指针。目标操作数可以是通用寄存器,存储器位置或段寄存器。
  • inc: 将1添加到目标操作数,同时保留CF标志的状态。目标操作数可以是寄存器或存储器位置。该指令允许更新循环计数器而不会干扰CF标志。(使用立即操作数为1的ADD指令执行更新CF标志的增量操作。)
  • dec: 从目标操作数中减去1,同时保留CF标志的状态。目标操作数可以是寄存器或存储器位置。该指令允许更新循环计数器而不会干扰CF标志。(要执行更新CF标志的减量操作,请使用立即操作数为1的SUB指令。)
  • sub : 从第一个操作数(目标操作数)中减去第二个操作数(源操作数),并将结果存储在目标操作数中。目标操作数可以是寄存器或存储器位置; 源操作数可以是立即数,寄存器或内存位置。(但是,在一条指令中不能使用两个存储器操作数。)当立即值用作操作数时,它将符号扩展为目标操作数格式的长度。SUB指令执行整数减法。它评估有符号和无符号整数操作数的结果,并设置OF和CF标志,分别表示有符号或无符号结果中的溢出。SF标志指示签名结果的符号。
  • cmp: 比较第一个源操作数和第二个源操作数,并根据结果设置EFLAGS寄存器中的状态标志。通过从第一个操作数中减去第二个操作数,然后以与SUB指令相同的方式设置状态标志来执行比较。当立即值用作操作数时,它将符号扩展为第一个操作数的长度。

2 Linux内核启动过程(bootloader)

2.1 启动过程描述

  1. 主板加电,cpu从rom中读取BIOS执行(通过将CS 设置为 0xFFFF,将 IP 设置为 0x0000,跳转到ROM中进行初始化)
  2. BIOS做完计算机硬件自检和初始化后,会选择一个启动设备(例如软盘、硬盘、光盘等)
  3. 将 boot.img 从启动设备的主引导扇区(MBR)加载到内存中的 0x7c00(从现在开始进入bootloader,grub2为例)
  4. boot.img加载core.img的第一个扇区diskboot.img(diskboot.img复杂加载core.img的其余模块)
  5. diskboot.img通过启用分段和分页将系统从实模式切换到保护模式增大以寻址空间(为了解压缩lzma_decompress.img)
  6. 解压缩kernel.img,跳转到kernel.img开始执行
  7. grub load config解析操作系统配置文件(grub.conf),经过一系列的系统配置初始化函数,最后调用grub_command_execute启动内核

2.2 启动过程小结图



3 内核初始化

3.1 内核初始化流程

  1. 调用内核初始化函数start_kernel()
  2. 初始化进程列表,创建0号进程,这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程,是进程列表的第一个
  3. 调用trap_init()初始化中断门
  4. 调用mm_init()初始化内存管理模块
  5. 调用sched_init()初始化调度模块
  6. 调用vfs_caches_init()初始化基于内存的文件系统rootfs
  7. 调用rest_init()来做其他方面的初始化

    3.2 系统调用流程

3.3 rest_init()

  • 调用kernel_thread(kernel_init, NULL, CLONE_FS)创建第1号进程(用户态所有进程的祖先)
  • 调用kernel_thread(kernel_init, NULL, CLONE_FS|CLONE_FILES)创建2号进程(用户态所有进程的祖先)
这里的函数 kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

3.4 小结流程

4 引用链接

x86
x86 and amd64 instruction reference