Linux课程学习总结
一、基础结构
冯诺依曼结构
冯·诺依曼体系结构就是各种计算机体系结构需要遵从的⼀个“客观规律”,了解它是理解Linux操作系统的基础。
冯·诺依曼体系结构的核⼼是存储程序计算机:计算机各个部件之间通过总线通信,CPU不断取出指令进行运行,如同一个for循环,并通过总线与内存、外设交换数据。
linux基本结构
Linux系统一般有4个主要部分:
内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。部分层次结构如下图所示。
内核是操作系统的核心,主要包括进程管理、中断与异常的处理、文件管理等。Linux内核就像一个库,提供了一组面向系统的命令。系统调用对于应用程序来说,就像调用普通函数一样。
二、进程管理
对于进程的管理,则涉及以下一系列问题:什么进程该执行?什么时候可以决定是否切换进程?下台的进程下次如何恢复上次的进度?
进程上下文
为了进程切换后能够恢复,需要在进程切换之前保存进程上下文。
一个进程的执行是在进程的上下文中执行。
当正在执行的进程由于某种原因要让出处理机时,系统要做进程上下文切换,以使另一个进程得以执行。
调度算法
Linux采用基于优先级的调度算法,进程的优先级是动态的,调度程序周期性的调整他们的优先级,避免进程饥饿死。
Linux将进程分为实时进程和普通进程。在基于优先级的算法下实时进程的优先级高于普通进程。
普通进程采用时间片轮转。实时进程采用时间片轮转和FIFO。使用FIFO策略的进程一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
调度时机
Linux进程调度时机有以下四种情况:
- 进程状态发生变化时
- 当前进程时间片用完时
- 进程从系统调用返回到用户态时
- 中断处理后,进程返回到用户态时
进程的栈
linux系统为每个用户进程分配了两个栈:用户栈和内核栈。当一个进程在用户空间执行时,系统使用用户栈;当在内核空间执行时,系统使用内核栈。
进程切换过程
概括来讲进程切换包括以下两个步骤:
- 切换页全局目录以安装一个新的地址空间:切换全局页表context_switch()
- 切换内核态堆栈和硬件上下文:switch_to和__switch_to()
三、中断与异常
外设的处理速度一般慢于CPU,而CPU如果一直等待外部事件,则造成了资源浪费。所以能让设备在需要内核时主动通知内核,会是一个聪明的方式,这便是中断。
中断分类
在Linux中,中断分为以下两大类。
可屏蔽中断:由I/O设备发出的中断请求(IRQ)产生
非可屏蔽中断:
- 处理器检测异常:CPU执行指令时探测到反常条件时产生,如溢出。包括故障(fault),陷阱(trap),异常中止(abort)
- 编程异常:也叫“软中断”。由编程者请求产生的,如系统调用。
中断上下文
不同于进程上下文,中断或异常处理程序执行的代码不是一个进程。它是一个内核控制路径,代表了中断发生时正在运行的进程执行。相比于进程上下文,它的内容更少,仅有几个寄存器。
中断流程
中断包括以下步骤:
1、获取相关信息:确定与中断或者异常关联的向量i;从IDT表中的第i项门描述符;从GDT中查找段选择符所标识的段描述符
2、检查以下内容:中断是由授权的发生源发出的;是否发生了特权级的变化(一般指是否由用户态陷入了内核态。需要进程堆栈切换);是故障?是则用引起异常的指令地址修改cs 和eip寄存器的值。
3、入栈:在栈中保存eflags、cs和eip的内容;如果异常产生一个硬件出错码,则将它保存在栈中
4、设置寄存器:装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这对寄存器值给出中断或者异常处理程序的第一条指定的逻辑地址
中断处理流程
中断处理包括以下步骤:
1、将中断向量入栈
2、保存所有其他寄存器
3、调用do_IRQ,主要包含以下内容:
- 取得对应的中断向量
- 调用中断处理句柄,对8259,就是handle_level_irq
- 应答PIC的中断,并禁用这条IRQ线。(为串行处理同类型中断)
- 调用handle_IRQ_event()执行中断服务例程,例如 timer_interrupt
- 通知PIC重新激活这条IRQ线,允许处理同类型中断
4、跳转到ret_from_intr
异常处理程序依次执行以下操作:
1、在内核堆栈中保存大多数寄存器的内容。
2、调用C语言的函数
3、通过ret_from_exception()从异常处理程序退出。
中断处理函数直接在被中断进程的内核栈内执行,即所谓的中断上下文。
系统调用
系统调用本质上是一个中断,通过int 0x80指令触发。其中断处理程序根据用户设置的系统调用号找到相应的系统调用函数。
四、文件管理
对于计算机来说,所谓的数据就是0和1的序列。这样的一个序列可以存储在内存中,但内存中的数据会随着关机而消失。为了将数据长久保存,我们把数据存储在光盘或者硬盘中。根据我们的需要,我们通常会将数据分开保存到文件这样一个个的小单位中(所谓的小,是相对于所有的数据而言)。但如果数据只能组织为文件的话,而不能分类的话,文件还是会杂乱无章。每次我们搜索某一个文件,就要一个文件又一个文件地检查,太过麻烦。文件系统(file system)是就是文件在逻辑上组织形式,它以一种更加清晰的方式来存放各个文件。
虚拟文件系统
在Linux中,通过虚拟文件系统,用户可以统一的使用系统调用来对不同类型文件进行操作。
文件的操作
通过open()系统调用可以打开一个文件,对应内核调用服务例程为sys_open ( )函数。所谓打开文件实质上是在进程与文件之间建立连接,而打开文件描述符唯一地标识着这个连接。
文件的读写与关闭也有类似的对应系统调用。
五、时间管理
时钟
Linux上有两种时钟,一个是由主板电池驱动的RTC(real time clock)时钟,另一个是内核时钟,有软件来根据时间中断进行计数。内核时钟在系统关机时不存在,操作系统启动时读取RTC时间进行同步,并在系统关机时将时间写回RTC。
相对时间和墙上时间
xtime(相对时间)是从系统中取得的时间,一般是从某一历史时刻开始到现在的时间,即操作系统上显示的时期,它的精度是微秒。
jiffies(墙上时间)是记录着从电脑开机到现在总共的时钟中断次数。jiffies取决于系统的频率,单位是 Hz,是周期的倒数,周期一般是一秒钟中断产生的次数。LINUX系统时钟频率是一个常数HZ来决定的, 通常HZ=100(Linux内核从2.5版内核开始把频率从100调高到1000),那么他的精度就是10ms。也就是说每10ms一次中断。所以一般来说Linux的精确度是10毫秒。
内核一般通过jiffies值来获取当前时间。尽管该数值表示的是自上次系统启动到当前的时间间隔,但因为驱动程序的生命期只限于系统的运行期 (uptime),所以也是可行的。驱动程序利用jiffies的当前值来计算不同事件间的时间间隔。硬件给内核提供一个系统定时器用以计算和管理时间, 内核通过编程预设系统定时器的频率(即上面所说的HZ=100)。节拍率(tick rate),每一个周期称作一个tick(节拍)。jiffies是内核中的一个全巨变量。系统启动一来产生的节拍数。譬如,如果计算系统运行了多长时间,可以用jiffies/tick rate 来计算。
六、实例分析:打开一个文件
某一进程使用open()系统调用访问文件时,过程如下:
1、首先会把系统调用号(0x05)放入eax。接着通过int 80开启中断。
2、根据向量号在 IDT中找到对应的描述符,进行特权级检查,允许调用。然后硬件将切换到内核栈 。接着根据中断描述符的 segment selector 在 GDT找到对应的段描述符,从段描述符拿到段的基址,加载到 cs 。将 offset 加载到 eip。最后硬件将 ss / sp / eflags / cs / ip / error code 依次压到内核栈。
3、之后,关闭中断,将当前栈指针保存到 eax ,调用 do_int80_syscall_32 => do_syscall_32_irqs_on:
do_syscall_32_irqs_on的参数就是先前被压入栈的寄存器值。首先取出系统调用号,从系统调用表中取出对应的处理函数。
4、open()的调用号是 0x05 ,调用了 sys_open,最终调用 do_sys_open:
在 do_sys_open 中调用 getname() 将处于用户态的文件名拷到内核态,然后通过 get_unused_fd_flags 获取一个没用过的文件描述符,然后 do_filp_open 创建 struct file ,fd_install将fd 和struct file 绑定(task_struct->files->fdt[fd] = file),然后返回fd。至此,进程与文件的关联建立。
5、从系统调用处理程序返回后,就会去检查当前进程是否处于就绪态、进程时间片是否用完,如果不在就绪态或者时间片已用完,那么就会去调用进程调度程序schedule(),转去执行其他进程。否则就会开始执行ret_from_sys_call,执行一些系统调用的后处理工作。
七、影响程序性能执行的因素
对于Linux整体而言,影响程序性能执行的因素CPU,内存,磁盘I/O,网络宽带和应用程序
CPU:同一程序在单处理器和多处理器中的执行性能有着很大的影响。从进程和线程角度来分析,同一个程序的多个线程在多个处理器上协同运行,极大提高了程序的执行效率。而单个CPU的系统下,程序只能在一个处理器上面执行,执行效率相比多处理器系统显然不足。
内存:对于需要在内存中读写数据的程序,数据在内存中的组织形式和程序的读写方式对程序的执行性能有着很大的影响。比如行优先和列优先之间的矛盾。还有就是运行内存的大小,如果运存的大小不足于将整个程序装入,导致在需要的时候只能频繁进行页面的换入换出,这将浪费CPU的运行时间,导致程序执行性能下降
磁盘I/O:磁盘I/O的读写速度也会影响程序的执行,读写速度越慢,程序所需等待的时间只能增多,影响了整个程序的执行时间
网络宽带:在需要网络数据进行交互的程序中,网络带宽影响了数据的获取和发送后的反馈;在分布式系统中,多个Linux系统通过网络协同运行,网络带宽和网络拥塞程度必定会影响到各个Linux系统之间的协同运行,最终导致程序执行性能的下降
应用程序:对于应用程序本身,程序内部的算法效率也是会影响到执行性能,实现同一个功能有不同的方法,开发者应该着手于系统的性能,优化好各功能代码之间的框架,对应用程序进行性能优化,使整个应用程序能够该效率执行。
八、心得体会,改进建议
通过本学期对于Linux系统的深入的学习,将之前操作系统中的知识,投入到Linux的操作系统的实际对比中,发现了理论和现实的相同和差距,同时更加深刻的明白了操作系统的关键的部分的具体实现方式:例如进程管理、系统调用等知识点。总的来说,这学期的Linux课程于我来说受益匪浅,同时也感谢孟老师和李老师不辞辛劳的教学。