《Linux 内核设计的艺术》学习笔记

当年学操作系统欠下的都是要还的……

Posted by WYX on November 16, 2022

《Linux 内核设计的艺术》学习笔记

2022/11/16:感觉这种大砖头书一刷不太够,经常看不懂,回头再二刷……

第一章:从开机到执行 main.c

  • 开机上电硬件启动 BIOS(此时为 16 位实模式

  • BIOS 执行:

    • 加载中断向量表和中断服务程序

    • 调中断 int 0x19 加载第一扇区 bootsect.s(引导程序)

    • 加载四个扇区和扇区内容到内存

  • bootsect.s 执行:

    • 调中断 int 0x13 加载 setup.s 程序到内存

    • 调中断 int 0x13 加载 system.s 程序到内存

  • setup.s 执行:

    • 读机器系统数据

    • 关中断,将 system 移动到内存起始(覆盖 BIOS 中断 )

    • 设置中断描述符表和全局描述符表,将专用寄存器(IDTR, GDTR)指向表

    • 打开 A20 实现 32 位寻址

    • 重编程两块 8259 芯片将 CPU 工作模式设为保护模式

  • head.s 执行:

    • 将寄存器转换为保护模式,设置页表,跳入 main.c 程序

第二章:从 main.c 到怠速

  • main.c 执行:

    • 设置进程管理数据结构,如进程槽 task[],进程的 task_struct 等

    • 复制根设备号、硬盘参数表

    • 根据物理内存大小规划使用

    • 设置外设虚拟盘

    • 初始化内存管理结构

    • 挂接异常处理中断

    • 准备使进程 0 能在 32 位保护模式下工作

      • 初始化块设备请求项管理 request[]

      • 挂接串行口、键盘、显示器中断

      • 设置开机启动时间

    • 创建进程 0

    • 进程 0 执行:

      • 挂接进程 0 的数据结构和全局描述符表

      • 设置时钟中断

      • 挂接 system_call 和中断描述符表

      • 从内核态转换到用户态,调用 fork() 函数创建进程 1

      • 在进程槽 task[] 为进程 1 申请空间

      • 复制进程信息、管理结构 task_struct 到进程 1

      • 设置进程 1 的线性地址空间、物理页面

      • 调用系统中断 pause() 切换到进程 1

    • 进程 1 执行:

      • 设置硬盘管理相关数据结构

      • 用虚拟盘代替软盘作为根设备

      • 用虚拟盘中的数据加载根文件系统

      • 从虚拟盘读取根文件系统的超级块,加载到超级块管理结构

      • 虚拟盘中读 i 节点,加载到 i 节点管理结构

    • 进程 1 执行:

      • 挂接进程 1 task_struct 的 filp[0] 和文件管理结构 file_table[0]

      • 解析 /dev/tty 文件路径

      • 找到 dev 目录的 tty0,载入 i 节点表中

      • 复制文件句柄建立关系,设定标准终端输出设备为 tty0

      • 进程 1 调用 fork() 函数创建进程 2

      • 发生时钟中断,中断返回,切换到进程 2

    • 进程 2 执行:

      • 为 shell 程序的执行做配置、环境准备(检测 shell 程序所在文件、调整进程 2 管理信息、地址值,调整 shell 程序第一条指令)

      • 执行 shell 第一条指令,引发缺页中断

      • 将 shell 程序载入新页

    • shell 进程执行:

      • 创建新进程 update

      • 执行 /etc/rc 文件

      • 调用 exit() 退出,切换去执行 update 进程

        update 进程的主要任务是定期同步缓冲区和外设,因为 Linux 默认写操作是先写缓冲区再定期写回外设

    • 重建 shell 进程

      • 两次创建的区别:第一次 file[0] 挂载普通文件 /etc/rc,第二次挂载 tty0

      • 自此操作系统支持用户和计算机交互

第三章:安装文件系统

  • 用户在 shell 输入 mount /dev/hd1 /mnt 开始安装文件系统

  • 系统调用 sys_mount() (代码见 /fs/super.c)执行:

    • 获取硬盘设备号、获取虚拟盘挂接点

    • 获取设备文件超级块,载入到超级块管理结构 super_block[8]

    • 挂接硬盘设备文件超级块和 mnt 目录文件指定的 i 节点

第四章:文件操作

  • 文件系统的数据结构:

    • 事先把硬盘存储空间人为分成 1KB 的块

    • 用 i 节点数据结构管理属于同一个文件的所有块,i 节点和文件 1:1 映射

    • 用 i 节点位图管理 i 节点的使用情况

    • 用逻辑块位图管理硬盘块使用情况

    • 用超级块管理上述 3 种数据结构

  • 使用方式:

    • 找硬盘空闲块:超级块 -> 逻辑块位图 -> 数据块

    • 找文件数据块:超级块 -> i 节点位图 -> i 节点表中的 i 节点 -> 数据块

第五章:用户进程和内存管理

用户进程的运行

  • 创建:

    • 调用 fork() 函数,产生 int 0x80 软中断,执行 sys_fork 系统调用

    • 再用 find_empty_process() 函数申请进程号和进程槽空位

    • copy_process() 函数复制原进程信息(管理结构、文件结构)并调整当前进程的管理结构

    • copy_mem() 函数复制原进程页表

    • copy_process() 函数关联新进程和全局描述符表 GDT

    • 最后 copy_process() 函数将新进程设为就绪态

  • 加载:

    • 调用 execve() 函数进行准备,包括 i 节点读取、参数、环境变量、栈指针,对进程管理结构进行调整,设置 EIP 使进程真正开始执行

    • 根据代码需要,在后续执行、加载程序时产生缺页中断,申请新页面并加载要读取的代码

  • 退出:

    • 调用 exit() 函数(映射到系统调用 sys_exit() 函数)

    • 进程自身释放进程代码和数据占用的物理内存,解除进程和可执行文件间的关系

    • 释放进程管理结构 task_struct 占用的物理内存,退出进程槽

多个用户进程在内存中如何同时工作?

  • 任何时间都只有一个进程执行,一个进程执行完会调用 shedule() 函数进行切换

  • Linux 页写保护实现:父子进程需要写同一个共享页面时,系统会在主内存申请空闲页面做备份供父进程和子进程分别使用(un_wp_page() 函数)

  • 用户进程和内核的隔离:内核中限制了用户进程的线性地址空间只能在 64MB 到 4GB 的范围,不能进入物理内存 0~16MB 的区域。

第六章:多个进程同时操作文件

  • n 个进程操作同一个文件:

    • 进程打开文件时调用 open() 函数

      • open() 函数映射到 sys_open() 函数,该函数会在 file_table 申请空闲表项目,让进程管理结构和文件 i 节点挂接
    • 进程读取文件时调用 read() 函数

      • read() 函数映射到 sys_read() 函数,最后调 bread() 读一个数据块进缓冲区

      • 读进缓冲区时调用 wait_on_buffer() 函数挂起这个进程,如果缓冲块已经加锁则进入 sleep_on() 函数,让该进程进入不可中断等待态

        调度策略可参考 进程管理:一文读懂Linux内核中的任务间调度策略 ,代码实现看 schedule() 函数和 /kernel/shed.c 的 sleep_on() 函数

  • 进程能直接将数据写入缓冲块的充要条件:缓冲块空闲且没有未写回硬盘的脏数据

    • 如果有空闲但未写回的数据,就会先写回

第七章:管道、信号机制

管道

  • 代码可见 /fs/pipe.c

  • 允许两个进程同时操作一个内存页面,一个写入一个读出

  • 本质是一块被操作系统按文件管理的内存页面

信号

  • 类似于局部的中断,在进程执行时,一旦其他进程接受到信号,就先暂停去执行这个进程的信号处理

    • 发送信号的两种方式:
      1. 调用特定库函数 signal()

      2. 用户产生键盘中断

      原理都是设置 task_struct 中的成员 signal,signal 按位存储每个进程接收到的信号

    • 操作系统检测信号的两种方式:
      1. 在系统调用返回之前检测

      2. 在时钟中断产生后检测

第八章:操作系统的设计指导思想

主从机制:操作系统和应用程序/操作系统内核和用户进程之间的关系,需要严格约束特权级和边界,进程就是接受操作系统组织、管理、协调的程序