Linux系统一般有4个主要部分:
内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。部分层次结构如图1-1所示。
内核是操作系统的核心,主要包括进程管理、中断与异常的处理、文件管理等。Linux内核就像一个库,提供了一组面向系统的命令。系统调用对于应用程序来说,就像调用普通函数一样。
对于进程的管理,则涉及以下一系列问题:什么进程该执行?什么时候可以决定是否切换进程?下台的进程下次如何恢复上次的进度?
为了进程切换后能够恢复,需要在进程切换之前保存进程上下文。
一个进程的执行是在进程的上下文中执行。
当正在执行的进程由于某种原因要让出处理机时,系统要做进程上下文切换,以使另一个进程得以执行。
Linux采用基于优先级的调度算法,进程的优先级是动态的,调度程序周期性的调整他们的优先级,避免进程饥饿死。
Linux将进程分为实时进程和普通进程。在基于优先级的算法下实时进程的优先级高于普通进程。
普通进程采用时间片轮转。实时进程采用时间片轮转和FIFO。使用FIFO策略的进程一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
Linux进程调度时机有以下四种情况:
概括来讲进程切换包括以下两个步骤:
外设的处理速度一般慢于CPU,而CPU如果一直等待外部事件,则造成了资源浪费。所以能让设备在需要内核时主动通知内核,会是一个聪明的方式,这便是中断。
在Linux中,中断分为以下两大类。
可屏蔽中断:由I/O设备发出的中断请求(IRQ)产生
非可屏蔽中断:
不同于进程上下文,中断或异常处理程序执行的代码不是一个进程。它是一个内核控制路径,代表了中断发生时正在运行的进程执行。相比于进程上下文,它的内容更少,仅有几个寄存器。
中断包括以下步骤:
1、获取相关信息:确定与中断或者异常关联的向量i;从IDT表中的第i项门描述符;从GDT中查找段选择符所标识的段描述符
2、检查以下内容:中断是由授权的发生源发出的;是否发生了特权级的变化(一般指是否由用户态陷入了内核态。需要进程堆栈切换);是故障?是则用引起异常的指令地址修改cs 和eip寄存器的值。
3、入栈:在栈中保存eflags、cs和eip的内容;如果异常产生一个硬件出错码,则将它保存在栈中
4、设置寄存器:装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这对寄存器值给出中断或者异常处理程序的第一条指定的逻辑地址
中断处理包括以下步骤:
1、将中断向量入栈
2、保存所有其他寄存器
3、调用do_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的认识仅限于一些命令的使用。在学习了这门课程之后,通过理论与实践相结合的方式对于linux有了更深入的认识。
在此感谢孟老师和李老师的辛勤付出!
原文:https://www.cnblogs.com/zhangzing/p/13262668.html