本篇开始将进入《Linux内核设计与实现》的阅读阶段,本书算得上是Linux内核的入门级书,通过对本书的阅读,最后将实现对Linux操作系统有大致的理解,为后续深入学习Linux系统内核做铺垫,本篇作为系列开篇,将介绍Linux的进程管理与进程调度
进程管理
进程与线程
进程与线程之间的区别简直就是典中典了,都被问烂了,这里我们从Linux的角度来讲解进程与线程
进程是操作系统分配资源的基本单位
一个进程不单单只是这个程序,还包括这个进程被分配的内存地址空间,这个进程的进程描述符,内核中的一些数据信息等
线程是cpu调度的基本单位
在Linux系统中,并没有线程这个概念,从内核看来,他们全部都是进程(部分Unix系统中会认为线程是轻量级的进程)进程线程的区别就在于进程拥有自己的独立空间;而大量线程之间会公用同一块空间
进程描述符
在Linux系统中,每一个进程都有一个进程描述符,这个进程描述符里面包含了一个进程的所有信息,包括进程中打开的文件,地址,文件描述符等等
进程描述符被放到了一个双向链表中,这个双向链表被称为任务队列(task list)
进程的五种状态
- 每一个进程在某一时刻都是属于以下五种状态之一
- TASK_RUNNING:任务运行状态,从名字看来貌似是这个任务正在运行,但是要注意的是,这个状态包含了正在运行和在运行队列中等待运行
- TASK_INTERRUPTIBLE:任务可中断状态,从名字上看得出来,这个状态就是可以中断状态,这个状态其实就是阻塞状态,并且会等待一定条件的完成。一旦条件完成,这个进程就会被唤醒
- TASK_UNINTERRUPTIBLE:这个状态下的进程和可中断状态差不多,也是需要等待一定的条件完成,但是这个状态是无法被接收到的信号打断的,这个用的不多
- TASK_TRACED:这个状态下的进程就意味着这个进程被监视了
- TASK_STOPPED:这个状态下的进程处于停止执行状态,无法投入使用
进程的创建
在Linux系统中,进程的创建是使用的一个fork()方法,通过这个方法会创建一个子进程,这个子进程会复制父进程中的所有资源,他们之间唯三的区别就在于子进程与父进程的PID不同;子进程与父进程的PPID不同;子进程与父进程之间的某些统计资源不同(例如挂起的信号,这没有必要继承)
我们知道,在Linux操作系统中,进程之间的隔离级别是非常高的,但是我们的父子进程之间却是使用的同一份资源,那么怎么实现父子进程之间的隔离呢?使用的方法就是非常有名的写时复制 Copy On Write
写时复制是一个非常简单的机制,如果我们的父子进程之间在使用资源的时候,如果只是读这个资源,那么大家就一起和和气气地使用同一份资源;如果其中有进程想要修改这个资源的话,那么子进程就会将这份资源复制一份,然后将这个指针指向新复制的地址,这样两者之间就可以使用不同的资源了。
写时复制的好处很多,因为父子进程之间并不是所有的资源都需要修改的,使用写时复制可以大量节约资源
线程的创建和进程的创建没有太大区别,我们已经说过了,在Linux角度看来,是没有线程这个概念的,在他看来全部都是进程,创建线程的时候调用的方法是clone(),只不过要传入一些参数,这些参数代表了线程和进程之间需要共享的资源(顺带一提,clone其实就是fork方法,fork是用clone实现的)
通过对比参数和表格,我们可以发现,进程与线程之间共享的是:地址空间、文件系统资源、文件描述符、信号处理程序
内核线程
- 内核进程没啥好说的,就是独属于内核的线程,内核线程的诞生只能由内核线程来创建,内核线程是无法转换到用户态的
线程的死亡
- 进程死亡时会调用两个方法,一个是do_exit()和task_release()方法,这两个过程比较复杂,我们就不做过多探讨,这里主要研究的是僵尸进程和孤儿进程
- 僵尸进程:当子进程死亡后,父进程没有调用wait()方法,那么在父进程中就会维护这个子进程的PID,如同一个灵牌一样,这就是僵尸进程。僵尸进程的影响其实不是特别大,除非特别多的情况下
- 孤儿进程:当父进程死亡,子进程没有死亡,那么就会给子进程找一个养父,这个养父的第一选择是同线程组的线程,次要选择是init线程
进程调度
多任务模式
- 多任务模式就是为了能够尽可能地榨取CPU资源,让一个单核CPU产生出同时运行多个进程的错觉,多任务模式也分为两种
- 非抢占式:当一个进程得到了CPU资源的时候它如果不主动释放资源,那么没有一个进程可以获得资源,只能等进程自己让步(yield)这种模式异常恶心和麻烦
- 抢占式:一个进程执行的时间都是由调度程序(内核)来管理的,由内核来分配资源,调度程序可以在任何时间停止任何正在执行的进程,将资源分配给其他进程
进程的分类
- Linux按照进程的优先级对进程进行了分类
- 普通进程:普通进程使用的调度策略是CFS(2.6.23版本内核及以后),SCHED_NORMAL
- 实时进程:实时进程使用的是SCHED_FIFO、SCHED_RR,这里我们要声明的是,实时进程的优先级永远高于普通进程,只要有实时进程,那么就会优先执行实时进程
Linux进程调度的发展史
2.4 <
在2.4版本的内核及以前,linux的进程调度非常简单,甚至是简陋,没什么好说的
2.5
2.5版本的时候,进程调度发生了改变,这个时候的进程调度叫做O(1),这个进程调度模式非常简单,我们可以这样理解:每一个进程都有属于自己的nice值,当nic值为0的时候,这个进程拥有100ms的执行时间;当这个进程nice值为1的时候,这个进程拥有95ms的执行时间;当进程的nice值为18的时候这个进程拥有10ms执行时间;当进程nice值为19时,这个进程拥有5ms执行时间;nice值的范围是-20~19,这种算法的缺点我们一眼就看得出来,当nice值是0和1的时候,差距相当小;当nice值是18和19时,两个线程之间差距的执行时间就有2倍!
这种模式的优缺点非常明显,对服务器友好,对客户不友好,具体原因在以前的文章中提到过,这里不做赘述
2.6.23
这个版本的内核的进程调度策略就是最经典的进程调度策略,这个调度策略叫做CFS(Completely Fair Schedule)完全公平调度策略。当然,这个完全公平调度策略,也就是SCHED_NORMAL都是普通进程的调度策略。实时进程的调度策略是SCHED_FIFO、SCHED_RR
普通进程调度策略详解
- 普通进程的优先级被划分为-20~19,这个值叫做“nice”值,nice值越小,进程的优先级越高
- 内核分配CPU资源的方式是所有进程都有资格获得进程的执行机会,我们的内核会按照进程nice值的比较来分配内核的执行资源。假如我们CPU有一个10ms的执行资源,此时我们操作系统中有两个进程正在争用这个资源,那么我们就可以按照这两者的nice值之比来分配资源。
- 当然有人会提出一个说法:当线程趋近于无限大的时候,是不是每一个线程执行的时间都趋近于无限小;其实按照这个设计的思路应该是这样的,但是进程之间的切换是要消耗资源的,如果按照上面的说法的话,那么进程之间就只剩下线程切换了,这是不理想的,所以Linux内核提供了一个最短执行时间,哪怕被分配的时间没有达到这么大,那么也会执行这么长的时间,这个时间片是1ms
- 当然,我们上面提到的只是普通进程的单个执行时间,那么进程调度程序如何挑选下一个应该执行的进程呢?这里面引入了一个虚拟执行时间的概念(vruntime),每一个进程自己每一次的执行时间都会被累加下来,这个时间值叫做vruntime,Linux内核每一次都会挑选vruntime最短的进程来执行
- 以上就是普通进程的CFS调度策略,也是SCHED_NORMAL
实时进程调度策略
- 实时进程的调度策略非常简单,首先,实时进程也有优先度的区分,区分是0~99,和普通进程不同的是,普通进程的nice值越小,优先级越高;实时进程的优先值越高,优先度越高
- 内核在分配资源的时候,会让所有的实时进程都优先执行(优先于普通进程),而实时进程之间也是有优先级区别,那么就会优先执行优先度高的,直到优先度高的先执行完毕,后面再执行优先度低的,这个调度策略叫做SCHED_FIFO;如果两个实时进程之间的优先度一样,那么执行的策略就是轮询,两者平分CPU资源,这个调度策略叫做SCHED_RR
休眠与唤醒
- 在Linux系统中,并不是所有的线程都是想要执行的,某一些线程有可能正在等待同步资源,在得到同步资源之前是不需要CPU的执行资源的,这种进程就可以被放置到等待队列中,此时这个进程的状态就是TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE。这就是对CPU资源的合理利用
- 当一个进程满足了唤醒条件之后就会被唤醒,唤醒之后的进程是需要与当前正在执行的进程进行优先级的比较的
文档信息
- 本文作者:JunHua yin
- 本文链接:https://yin-JH.github.io/2021/04/06/%E9%98%85%E8%AF%BB-Linux%E5%86%85%E6%A0%B8%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0-%E8%A1%A5%E5%85%85Linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9F%A5%E8%AF%86%E7%B3%BB%E5%88%97(%E4%B8%80)%E4%B9%8B%E8%BF%9B%E7%A8%8B%E7%9A%84%E7%AE%A1%E7%90%86%E4%B8%8E%E8%B0%83%E5%BA%A6/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)