精简的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() 操作,从而转化成设备驱动程序对设备的操作。

  1. 程序运行,尝试读取文件,触发read系统调用,中断,进入内核态
  2. 保存中断上下文,进入中断处理函数
  3. 到达VFS层次,sys_read()会根据fd在进程打开文件表中找到相应的系统打开文件表
  4. 返回文件描述符
  5. 恢复中断上下文
  6. 此后,用户通过文件描述符对设备文件进行的读写操作,会通过VFSsys_readsys_write,等函数,转化为对设备的操作

这样,Linux系统就通过对文件的简单读写,完成了对外设的操作,如下图:

 

 

 


  

心得体会&改进建议

孟老师的班级博客模式很好,很利于同学之间互相帮助、互相学习。做实验不会的时候有参考很重要,不然容易卡住。而且期末复习的时候有参考,很利于学习总结。我个人写技术博客也是从孟老师带着进入博客园开始的。

李老师上课很认真、很充实、很硬核。建议是: 平时布置一些作业,方便加深对上课内容的理解,更加透彻地学习。还有建议以后上课时候,语速放慢一些,不然不容易跟上讲课的节奏。

经过本次课程的学习,更加深入理解Linux内核的原理以及实践。以前对于Linux可能更多的是在操作系统的理论层次,这次亲身体验了Linux的具体实验流程,包括深入系统内核调用,对操作系统如何运行有了更深的理解。

 

版权声明:本文为qyf2199原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/qyf2199/p/13273572.html