Linux内核设计的艺术 阅读笔记

Linux0.11内核.正文部分记录过程,引用部分记录知识点和理解.

main函数之前的功能

加载操作系统

经典流程:
计算机加电设置cs:ip为0xffff0,运行ROM中的BIOS,BIOS初始化中断向量表和一些硬件设备,加载0盘0道1扇区的引导程序bootsect到0x7c00处.bootsect是与操作系统配套的,规划物理内存,加载操作系统,设置根设备为软盘.

Linux0.11要求系统必须存在一个根文件系统,其他文件系统挂接其上.因此Linux的启动需要两部分数据,即系统内核镜像和根文件系统.(kernel pwn中的bzimage和文件系统(比如busybox提供的))

保护模式

关闭中断.设置全局描述符表(GDT),打开A20gate实现32位寻址(实模式下寻址范围为低1MB).CR0PE标志置1,enable 保护模式,GDT启用,段基址寄存器以选择子形式使用.

设置段寄存器以适应保护模式,建立内核页目录表,建立中断描述符表(IDT),开启分页模式.以ret模式进入main函数.

环境设备初始化及激活进程0

初始化根设备和硬盘.设置缓冲区.

除内核代码及其数据所占物理空间之外,其余物理内存主要分为3部分,主内存区(进程代码运行的空间),缓冲区(主机与外设进行数据交互的中转站),虚拟盘(可以缓存外设的数据,提高效率).

建立内存管理基址mmu,将中断异常与IDT挂接(其实这里才是建立中断描述符表吧).开启部分中断.

初始化块设备请求项结构

Linux0.11将外设分为两类:
块设备:将存储空间等分为若干同样大小的小存储空间块,每个块有块号,可以独立,随机读写,如硬盘,软盘.
字符设备:以字符为单位进行IO通信,如键盘,命令行显示器.

进程想与块设备沟通,必须经过主机内存中的缓冲区.请求项管理结构就是管理缓冲区中缓冲块与块设备上逻辑块之间读写关系的数据结构.是以一个数组,同时也维护一个请求链表.

挂接交互界面相关外设的中断服务例程.如串行口,显示器,键盘.

初始化进程0,设置task_struct,使进程0具有处理系统调用的能力,设置时钟中断做好进程切换的准备工作.

最后以iret(唯一的从高权限到低权限的方式)的方式切换到3特权级,之前运行的内核控制流正式成为进程0.

进程1的创建及执行

进程0通过fork创建进程1,设置相关数据结构,切换到进程1.

通常有以下两种情况可以产生进程切换:

  1. 允许进程运行的时间结束.每个进程在创建时都被赋予了有限的时间片,当时间片减少到0便发生进程切换.
  2. 进程的运行停止.比如等待外设的数据,等待其他程序运行的结果,进程主动停止一段时间或进程执行完毕等等.

进程1安装硬盘文件系统.格式化虚拟盘(使其具有文件系统相关内容,引导块,超级块,格式化之后虚拟盘正式成为一个块设备)并更换根设备为虚拟盘.

操作系统中的文件系统可以大致分为两部分,一部分在操作系统内核中,另一部分在硬盘,软盘,虚拟盘中.
文件系统用inode(i结点)来管理文件,一个inode管理一个文件.文件的路径在操作系统中由目录文件中的目录项管理,一个目录项对应一级路径,目录文件也是文件,也由一个inode管理.一个目录文件挂在另一个目录文件的目录项上,就成了父子目录的关系.所有的文件最终挂接成一个树形结构,树根i节点就叫这个文件系统的根i节点.一个逻辑设备(一个物理设备可分成多个逻辑设备)只能包含一个这样的树形结构.

加载文件系统最重要的标志,就是把一个逻辑设备上的文件系统的根i节点,关联到另一个文件系统的i节点,也就是常说的mount.可以联系一下之前容器中bind mount的操作.

一个文件系统必须挂接在另一个文件系统上,必然就存在一个根文件系统,Linux0.11中的super_block[8]中保存的根设备的超级块对应的文件系统便是根文件系统.

进程2的创建及执行

加载完根文件系统之后,进程1(其实是kernel)具备了对设备文件的访问能力.使用open和dup打开/dev/tty0文件作为标准输入,输出,错误文件.

进程1创建进程2并切换到进程2.进程2关闭标准输入重新以/etc/rc为标准输入,execve运行/bin/sh的shell程序.相当于shell的初始化,执行/etc/rc里的预置命令,比如创建进程并加载/etc/update程序

update进程将缓冲区中的数据同步到外设.该程序每隔一段时间就会被唤醒,完成同步工作后挂起等待下一次唤醒.

update进程挂起后控制流回到进程2,进程2完成善后工作后退出.

进程1继续执行,以/dev/tty0为标准输入重建shell.shell在等待输入后挂起,当接收到键盘中断,shell进程从字符缓冲队列(其实就是tty0的内容)读取指令数据,并完成相应操作.

至此系统进入怠速状态.

文件操作

安装文件系统就是在根文件系统的基础上,把硬盘中的文件系统安装在根文件系统上,使操作系统也具备以文件形式与硬盘进行数据交互的能力.

安装文件系统分三步:
1)将硬盘的超级块读取出来并载入super_block[8].
2)将虚拟盘上指定的i节点读出,加载到系统的inode_table[32]中.
(是不是意味着最多只能挂载8个文件系统(设备),挂载点最多32个?)
3) 将硬盘上的超级块挂接到指定的i节点上.

进程间通信

管道机制

操作系统在内存中为每个管道开辟一页内存,为这一页内存赋予文件的属性,这一页内存由两个进程共享,但不会分配给任何进程,只由内核掌控.

从技术上看,管道就是一页内存.
1)文件属性:创建管道相当于创建(并打开)一个文件,进程对管道的访问形式与访问文件相同.
2)减少页属性:该页不映射到进程的地址空间内.无法被进程以内存形式访问.

信号机制

发送信号:
1)进程通过调用特定的库函数给另一个进程发送信号,另一种方式是用户通过键盘输入信息产生键盘中断后,中断服务例程给进程发送信号.发送信号的实质是设置信号位图上的信号位.(这也是为何未处理的相同信号会丢失的原因)

系统检测进程接收到的信号:
进程并不能检测收到的信号,该工作由内核完成.

  1. 在系统调用返回之前检测当前进程是否接收到信号.
  2. 时钟中断产生后,其中断处理例程执行结束之前检测.

处理信号.当用户程序不需要处理信号时,信号处理函数完全不参与用户进程的执行,当用户进程需要处理信号时,进程的程序将暂时停止执行,转而去执行信号处理函数,执行完毕后将从暂停的现场继续执行.

这里处理信号的方式挺有意思的,解决了我看CSAPP时的一个疑问.

在系统调用返回前或时钟中断返回前,先把内核栈中保存的寄存器备份在当前进程的用户栈中,修改内核栈中的寄存器使得iret之后跳转到用户空间的信号处理函数.处理完成后,再通过前面备份在用户空间的指令和数据,返回用户空间执行.

这里又有一个问题,既然已经回到用户态了,那即使在用户栈上布置的暂停现场的状态,也无法通过ret指令完成状态(如edi等寄存器)的恢复.原来在信号处理函数完成后,从栈上弹出的返回地址是一个restorer函数的地址,它将完成用户进程状态的恢复并再次ret到用户进程.

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信