Linux课程学习总结报告
精简的Linux系统概念模型
在Linux系统分析这门课中,我们主要学习了中断&异常,时钟,进程管理,系统调用,驱动程序,文件系统,内核根文件挂载等方面的内容。
一、中断&异常
一次中断的过程:
1. 中断控制器会监视IRQ 中断信号。将信号转换成具体的中断向量,传到CPU的INTR引脚。(与Linux设备关系)
2. CPU会在运行下一条指令之前。检查是否发生中断。
3. 如果有中断的话CPU应答中断时会去查找对应的中断服务例程。确定其是否合法。
4. 若合法则进行中断处理。首先在内核栈中保存以前的ss和esp值,装载新的ss和esp寄存器。
5. 保存eflags、cs和eip的值。
6. 中断处理
7. 中断、异常处理完后,产生iret指令。
8. 装载cs、eip、eflags。返回到用户态。或者如果有中断嵌套的话。返回到上一个中断嵌套。
二、Linux时钟
主要用于时间片的计时,进程运行时间,和操作系统的墙上时间。
- 实时时钟 RTC:独立于CPU,单独供电,IRQ周期性中断
- 时间戳计数器 TSC (最精确)
- 可编程间隔定时器 PIT (可编程中断)
Linux在启动时首先会去读RTC,RTC是存储在单独的一个结构里,有单独的供电。在系统关闭的情况下,也在进行计时。
Linux启动过程中会注册时钟源。根据优先级来选择具体的时钟源,优先级为(pit、tsc)
- 相对时间:记录从系统启动到当前时刻,系统产生的滴答数
- 墙上时间:系统当前时间(xtime)该变量记录了现实世界中的年月日 格式的时间
三、进程管理和调度
- 创建:clone,fork,vfork
- 执行:exec
- 终止:exit
fork完全复制,clone选择性复制,exec执行新的进程
进程的创建
其中,init_task为第⼀个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码方式固定下来的。
除此之外,所有其他进程的初始化都是通过do_fork复制父进程的⽅式初始化的。
1号和2号进程的创建是start_kernel初始化到最后由rest_ init通过kernel_thread创建了两个内核线程:
- kernel_init,最终把⽤户态的进程init给启动起来,是所有用户进程的祖先;
- kthreadd内核线程,kthreadd内核线程是所有内核线程的祖先,负责管理所有内核线程。
execve与fork的区别与联系?
- fork和其他系统调⽤不同之处是它在陷⼊内核态之后有两次返回,第⼀次返回到原来的⽗进程的位 置继续向下执⾏,这和其他的系统调⽤是⼀样的。在⼦进程中fork也返回了⼀次,会返回到⼀个特定的点 ——ret_from_fork,通过内核构造的堆栈环境,它可以正常系统调⽤返回到⽤户态。
- 当前的可执⾏程序在执⾏,执⾏到execve系统调⽤时陷⼊内核态,在内核里面用do_execve加载可执行文件,把当前进程的可执行程序给覆盖掉。当execve系统调⽤返回时,返回的已经不是原来的那个可执⾏程序了,⽽是新的可执⾏程序。
四、系统调用过程
1. 程序运行过程中使用了某个C库函数,触发int $0x80或者syscall产生了系统调用
2. entry_SYSCALL_64/32,swags操作,保存现场
3. 调用do_syscall_64,在syscall_table中查找具体的系统调用
4. 执行
5. 恢复现场
五、设备驱动程序
Linux具有一切皆文件的特性,所以其实,设备驱动是依赖于Linux文件系统来注册的。
Linux使用设备号来标识设备文件。
设备会注册到IRQ中,会产生中断信号给中断控制器。
Linux设备
- 字符设备
- 块设备
- 网络设备
磁盘结构
- 引导控制块
- 盘控制块(超级快)
- FCB(文件控制块)
- 目录结构
linux对设备的访问是通过对文件的操作来实现的,
访问设备需要:驱动程序+设备文件
在驱动程序中主要完成以下工作:
1)获取并注册设备号
2)新建、初始化并添加cdev结构体
3)进行其他初始化
对于设备文件来说:
1)根据设备号,创建设备文件
2)创建完成就可以open了,open后返回fd,利用fd进行read\write等操作
六、文件系统
虚拟文件系统(VFS)
- 向上,对应用层 (的System Call) 提供一个标准的文件操作接口 (如read/write);
- 对下,对文件系统提供一个标准的接口,兼容多种文件系统可以方便的移植到Linux上;
当进程要读某个文件时,进行系统调用read,产生一个中断INT80,对应第120个中断向量,找到中断处理程序,带上系统调用号。访问文件系统中的文件。
fd数组(文件描述符)
0 1 2 分别是:标志输入,标准输出,标准错误
打开文件的过程
- 根据给定的文件路径名搜索目录结构
- 文件的FCB复制到内存里的系统打开文件表(还有文件计数)里
- 在进程打开文件表里创建一个指针字段,指向系统打开文件表里相应的表项
- 打开文件系统调用返回指向进程打开文件表的指针,文件的操作以后都通过这个指针
七、根文件系统挂载mount
挂载前,向VFS注册,填写文件系统注册表数据结构file_system_type
安装根文件系统
- 安装rootfs虚拟单文件系统,该文件系统仅提供一个作为初始安装点的空目录 init_mount_tree
- 内核在空目录上安装实际根文件系统,替换rootfs
initrd的两种格式:
- 传统 -image-initrd
- cpio格式
八、进程地址空间
函数调用堆栈:
当执行调用 call 时,堆栈指针esp 递减4个字节(32位),
并且调用后的指令地址(返回地址)被写入现在由esp引用的存储器位置,
换句话说,返回地址被压入栈。
然后将 指令指针eip 设置指定为要调用的操作数的地址,并从该地址继续执行。
ret 恰恰相反。简单的ret不会占用任何操作数。
处理器首先从esp中包含的内存地址中读取值,然后将esp增加4个字节,它会从堆栈中弹出返回地址。
eip设置为此值,并从该地址继续执行。
当函数调用发生时,进程地址空间中栈帧的变化:
首先,把函数调用的参数压栈,然后eip(返回地址)压栈,ebp(栈底)压栈。
接下来,更新ebp的值为esp的值(栈对齐),
将esp减少一个特定的值(与调用函数内部申请空间相关),为调用函数获取一定的栈空间。
举例验证模型——“扫地机器人”
在Linux中,扫地机器人 这个外设被看成是一个设备文件。Linux系统通过打开这个文件,对它进行read() write()
操作,从而转化成设备驱动程序对设备的操作。
- 程序运行,尝试读取文件,触发
read
系统调用,中断,进入内核态 - 保存中断上下文,进入中断处理函数
- 到达
VFS
层次,sys_read()
会根据fd在进程打开文件表中找到相应的系统打开文件表 - 返回文件描述符
- 恢复中断上下文
- 此后,用户通过文件描述符对设备文件进行的读写操作,会通过
VFS
的sys_read
,sys_write
,等函数,转化为对设备的操作
这样,Linux系统就通过对文件的简单读写,完成了对外设的操作,如下图:
心得体会&改进建议
孟老师的班级博客模式很好,很利于同学之间互相帮助、互相学习。做实验不会的时候有参考很重要,不然容易卡住。而且期末复习的时候有参考,很利于学习总结。我个人写技术博客也是从孟老师带着进入博客园开始的。
李老师上课很认真、很充实、很硬核。建议是: 平时布置一些作业,方便加深对上课内容的理解,更加透彻地学习。还有建议以后上课时候,语速放慢一些,不然不容易跟上讲课的节奏。
经过本次课程的学习,更加深入理解Linux内核的原理以及实践。以前对于Linux可能更多的是在操作系统的理论层次,这次亲身体验了Linux的具体实验流程,包括深入系统内核调用,对操作系统如何运行有了更深的理解。