课程学习总结报告
前言
请您根据本课程所学内容总结梳理出一个精简的Linux系统概念模型,最大程度统摄整顿本课程及相关的知识信息,模型应该是逻辑上可以运转的、自洽的,并举例某一两个具体例子(比如读写文件、分配内存、使用I/O驱动某个硬件等)纳入模型中验证模型。
谈谈您对课程的心得体会,改进建议等。
一、总结梳理出一个精简的Linux系统概念模型
Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。

"三大法宝"和"两把宝剑"
这是孟老师上课中的两个生动比喻,三大法宝:存储程序计算器、中断、函数调用栈;两把宝剑:中断上下文和进程中断上下文。
(1)中断管理
内核的一个主要功能就是处理硬件外设I/O,中断信号提供了一种特殊的方式, 使得CPU转去运行正常程序之外的代码,中断会改变处理器执行指令的顺序, 通常与CPU芯片内部或外部硬件电路产生的电信号相对应。
初始化过程:
- 系统加载 start_kernel 开始,调用了 trap.c 中的 trap_init()函数对 中 断 进 行 初 始 化 。 并且调用同一文件下的set_trap_gate()/set_system_gate()/set_intr_gate()等对中断描述符进行初始化。
- 在进入保护模式之前, IDT 再次通过 setup_idt()函数进行初始化,在这里使用了 ignore_int()函数,是为了保护未初始化完成时发生异常不出错。 然后调用 init_IRQ()函数,把中断描述附表的中断处理代码段地址设在在 interrupt 数组中,该数组指向同一个函数处理 common_interrup。
明确中断发生时, CPU 硬件级的中断信号处理过程 :
1. 确定与中断或异常相关联的向量 i
2. 读取 idtr 寄存器的值,找到 IDT 的基址,通过查询 IDT,找到第 i 项对应的内容。
3. 从 gdtr 寄存器获得 GDT 的基地址,并在 GDT 中查找,以读取IDT 表项中的段选择符所标识的段描述符。
4. 确定中断是由授权的发生源发出的。
中断:需要比较 CPL 和 GDT 中的 DPL。中断处理程序的特
权不能低于引起中断的程序的特权。
编程异常:需要比较 CPL 和 IDT 中的 DPL。
5. 检查是否发生了特权级的变化,一般指的是用户态陷入内核态。
如果是用户态陷入内核态,控制单元要使用新的特权级堆栈。
保存 ss 和 esp,并用新的堆栈的值填充。
6. 如果是故障,用引起故障的指令修改 cs 和 eip,以便异常处理后再次执行。
7. 在堆栈中保存 eflags/cs/eip 的内容。
8. 如果有硬件出错码,则保存。
9. 使用 IDT 中第 i 项中的段描述符和偏移量填充 cs 和 eip。
(2)文件系统
地基(低层)由一排排的文件柜组成,井然有序。文件柜里放置着“文件”--电脑中的文件。左上角,有一只向前挂着421号牌的小企鹅。它表示这PID(进程ID)为421的进程,他正在查看文件柜中的文件,这代表系统中正有一个进程在访问文件系统。在右下角有一只小狗,他是看门狗,这代表对文件系统的监控,可以说整个linux系统都是构建在文件系统之上的,Linux支持多种文件系统,包括ext2、ext3、vfat等等。Linux采用虚拟文件系统VFS来达到支持多种文件系统格式的目标。VFS为各类文件系统提供一个统一的操作界面和编程接口。向上提供统一的编程接口,向下对各种文件系统进行兼容。

Virtual File System这一层,是为了隐藏各个设备之间的实现细节,向上提供统一的接口,试想,当我们通过mkfs.xxx系列命令创建了很多不同的文件系统,但这些文件系统都有各自的API接口,而用户想要的是,不管你是什么API,他们只关心mount/umount,或open/close等操作。
所以,VFS就把这些不同的文件系统做一个抽象,提供统一的API访问接口,这样,用户空间就不用关心不同文件系统中不一样的API了。VFS所提供的这些统一的API,再经过System Call包装一下,用户空间就可以经过SCI的系统调用来操作不同的文件系统。
(3)进程
进程概念
从用户角度:进程就是一个正在运行中的程序。
操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块
进程的描述信息有:标识符PID,进程状态,优先级,程序计数器,上下文数据,内存指针,IO状态信息,记账信息。都需要操作系统进行调度。
进程创建
Linux中非常重要的函数——fork(),它从已存在的进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h> pid_t fork(void); 返回值: 父进程:返回值大于0,子进程的pid 子进程:返回值等于0
关于fork函数需要理解,每当调用一次fork函数时,会返回两个两次。一次是在调用进程中(父进程)返回一次,返回值是新派生的进程的进程ID。一次是在子进程中返回,返回值是0,代表当前进程为子进程。如果返回-1,那么则代表在创建子进程的过程中出现了错误。
进程状态
进程状态一般有:就绪态,阻塞态,运行态
在Linux下:R运行状态,S睡眠状态,D磁盘休眠状态,T停止状态,X死亡状态
二、举例验证模型
(1)文件读写
例如读写文件,在读写文件之前,必须使用oepn打开一个文件,打开文件首先open会执行到C库,C库里有INT $0x80指令,然后在中断向量表中找到128项,中断向量表里有中断描述符,可以找到中断处理程序入口,128项是系统调用处理函数,进入系统调用处理函数,保存现场,系统调用号存储eax中,根据系统调用号执行系统调用表中对应那一项的函数。系统调用表是相应的函数指针,这里会执行sys_open。sys_open进行命令查找,找到文件控制块,根据不同文件类型,调用文件打开函数,文件打开函数会创建一个系统文件打开表file,file的很多内容来自文件控制块,填完之后,进程也有一个进程文件打开表,这个结构里面有fd数组,fd数组是指针,找个空闲的,把它指向之前已经创建的file结构,最后返回那一项的索引号,即fd。
当进程使用read系统调用读这个文件,就会根据fd数组的下标 找到fd数组的对应项,然后找到指向之前创建的file结构的指针,再找到这个file结构,最后找到file结构里的file_operations里的具体的read函数来读取文件。
当进程使用write系统调用写数据到这个文件时也会根据fd数组的下标 找到fd数组的对应项,然后找到之前创建的file结构的指针,再找到这个file结构,进而找到file结构里的file_operations里的具体的write函数来写文件。
(2)中断过程
-
在执行eip时,先检查上一次是否发生异常或者中断
-
获取对应的中断向量,这些中断向量都是由厂家或个人预先设置好的
-
从gdtr中获取gdt的基地址,利用中断向量里的段选择符获取对应代码段的段描述符
-
加上中断向量里的偏移量字段,就可以找到例程的线性地址
-
判断是否合法:
-
中断处理程序的权限要不小于引起中断的程序的权限
-
-
判断特权等级是否发生变化(即,是否从用户态进入内核态):
-
是:
-
读tr寄存器, 访问运行进程的tss段(tss段里保存进程的内核栈地址)
-
用与新特权级(内核)相关的栈段和栈指针装载ss和esp寄存器。 这些值可以在进程的tss段中找到,切换到内核栈了
-
在新的栈中保存ss和esp以前的值, 这些值指明了与旧特权级相关的栈的逻辑地址 ,方便返回用户态
-
此时已经切换到进程的内核栈
- 若发生的是故障, 用引起异常的指令地址修改cs和eip寄存器的值, 以使得这条指令在异常处理结束后能被再次执行
-
-
-
在栈中保存eflags(程序状态字)、 cs和eip的内容 (被中断进程的内核栈)(中断上下文)
-
如果异常产生一个硬件出错码z, 则将它保存在栈中
-
装载cs和eip寄存器, 其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。 这对寄存器值给出中断或者异常处理程序的第一条指定的逻辑地址 (硬件级保存现场)
-
进一步保存现场——通用寄存器saveall(软件级保存现场)
-
例程处理
-
软件级回复现场
-
iret——弹出eflags,eip,cs
-
判断权限合法性(是内核到内核,还是内核到用户)
-
从栈中装载ss、esp寄存器
三、心得体会和改进建议
心得体会
疫情期间全部改为线上授课,刚开始时候,线上授课的效果还不错,时间久了自己就有点松懈了,可能看不到其他同学都在干什么,没有动力,听着听着就走神了。不过好在有回放,在自己计划好后,可以根据自己的时间来进行学习,做实验的过程较为费力,因为自己之前没怎么接触过这方面的知识,但是参加大家的博客园还是受益匪浅的,可以参考许多人的实验过程,最终是可以做出来的。
改进建议
在复习的时候,打开ppt一看,哇感觉好多东西,感觉以后可不可以突出一些重点,抓住重点多讲一些,这样可以在学完之后有更清晰的脉络,也可以知道重点所在,现在复习的感觉就是知识好多,框架有些组织不起来。
浙公网安备 33010602011771号