进程基本概念

一、Linux多任务机制


1、多任务机制简介


    多任务处理指的是用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为“任务”。相比于单任务的操作系统(例如早期的MS-DOS),当代绝大多数操作系统都支持多任务,功能增强了很多。

    但是,CPU(每个核心)在某一时刻只能执行一个任务,因此多任务操作系统必须解决CPU核心(单任务线性)与操作系统的任务(多任务并行)之间的矛盾。常见的解决方案是将CPU的运行分解成时间片(几十毫秒到上百毫秒不等),每个任务被分配不同的时间片来独占CPU进行运算。在该任务的时间片内,CPU被该任务独占,其他任务无法占用;在该任务的时间片外,CPU被其他任务独占,该任务也无法占用该CPU。由于CPU计算速度十分快且会频繁切换任务,因此用户感觉到当前操作系统是在“并行”的。

    因此,多任务操作系统需要解决各个任务间分配时间片的调度策略,对于某些重要的、耗时较长的任务需要多分配时间片,而对于不重要的、耗时较短的任务需要少分配时间片。


 

2.调度

进程都希望自己能够占用 CPU 进行工作,那么这涉及到进程上下文切换。

一旦操作系统把进程切换到运行状态,也就意味着该进程占用着 CPU 在执行,但是当操作系统把进程切换到其他状态时,那就不能在 CPU 中执行了,于是操作系统会选择下一个要运行的进程。

选择一个进程运行这一功能是在操作系统中完成的,通常称为调度程序scheduler)。

那到底什么时候调度进程,或以什么原则来调度进程呢?

调度时机

在进程的生命周期中,当进程从一个运行状态到另外一状态变化的时候,其实会触发一次调度。

比如,以下状态的变化都会触发操作系统的调度:

  • 从就绪态 -> 运行态:当进程被创建时,会进入到就绪队列,操作系统会从就绪队列选择一个进程运行;
  • 从运行态 -> 阻塞态:当进程发生 I/O 事件而阻塞时,操作系统必须另外一个进程运行;
  • 从运行态 -> 结束态:当进程退出结束后,操作系统得从就绪队列选择另外一个进程运行;

因为,这些状态变化的时候,操作系统需要考虑是否要让新的进程给 CPU 运行,或者是否让当前进程从 CPU 上退出来而换另一个进程运行。

另外,如果硬件时钟提供某个频率的周期性中断,那么可以根据如何处理时钟中断 ,把调度算法分为两类:

  • 非抢占式调度算法挑选一个进程,然后让该进程运行直到被阻塞,或者直到该进程退出,才会调用另外一个进程,也就是说不会理时钟中断这个事情。
  • 抢占式调度算法挑选一个进程,然后让该进程只运行某段时间,如果在该时段结束时,该进程仍然在运行时,则会把它挂起,接着调度程序从就绪队列挑选另外一个进程。这种抢占式调度处理,需要在时间间隔的末端发生时钟中断,以便把 CPU 控制返回给调度程序进行调度,也就是常说的时间片机制

调度原则

原则一:如果运行的程序,发生了 I/O 事件的请求,那 CPU 使用率必然会很低,因为此时进程在阻塞等待硬盘的数据返回。这样的过程,势必会造成 CPU 突然的空闲。所以,为了提高 CPU 利用率,在这种发送 I/O 事件致使 CPU 空闲的情况下,调度程序需要从就绪队列中选择一个进程来运行。

原则二:有的程序执行某个任务花费的时间会比较长,如果这个程序一直占用着 CPU,会造成系统吞吐量(CPU 在单位时间内完成的进程数量)的降低。所以,要提高系统的吞吐率,调度程序要权衡长任务和短任务进程的运行完成数量。

原则三:从进程开始到结束的过程中,实际上是包含两个时间,分别是进程运行时间和进程等待时间,这两个时间总和就称为周转时间。进程的周转时间越小越好,如果进程的等待时间很长而运行时间很短,那周转时间就很长,这不是我们所期望的,调度程序应该避免这种情况发生。

原则四:处于就绪队列的进程,也不能等太久,当然希望这个等待的时间越短越好,这样可以使得进程更快的在 CPU 中执行。所以,就绪队列中进程的等待时间也是调度程序所需要考虑的原则。

原则五:对于鼠标、键盘这种交互式比较强的应用,我们当然希望它的响应时间越快越好,否则就会影响用户体验了。所以,对于交互式比较强的应用,响应时间也是调度程序需要考虑的原则。

针对上面的五种调度原则,总结成如下:

五种调度原则:

  • CPU 利用率:调度程序应确保 CPU 是始终匆忙的状态,这可提高 CPU 的利用率;
  • 系统吞吐量:吞吐量表示的是单位时间内 CPU 完成进程的数量,长作业的进程会占用较长的 CPU 资源,因此会降低吞吐量,相反,短作业的进程会提升系统吞吐量;
  • 周转时间:周转时间是进程运行和阻塞时间总和,一个进程的周转时间越小越好;
  • 等待时间:这个等待时间不是阻塞状态的时间,而是进程处于就绪队列的时间,等待的时间越长,用户越不满意;
  • 响应时间:用户提交请求到系统第一次产生响应所花费的时间,在交互式系统中,响应时间是衡量调度算法好坏的主要标准。

说白了,这么多调度原则,目的就是要使得进程要「快」。

调度算法

不同的调度算法适用的场景也是不同的。

接下来,说说在单核 CPU 系统中常见的调度算法。

01 先来先服务调度算法

最简单的一个调度算法,就是非抢占式的先来先服务(First Come First Severd, FCFS)算法了。

顾名思义,先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。

这似乎很公平,但是当一个长作业先运行了,那么后面的短作业等待的时间就会很长,不利于短作业。

FCFS 对长作业有利,适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。

02 最短作业优先调度算法

最短作业优先(Shortest Job First, SJF)调度算法同样也是顾名思义,它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。

这显然对长作业不利,很容易造成一种极端现象。

比如,一个长作业在就绪队列等待运行,而这个就绪队列有非常多的短作业,那么就会使得长作业不断的往后推,周转时间变长,致使长作业长期不会被运行。

03 高响应比优先调度算法

前面的「先来先服务调度算法」和「最短作业优先调度算法」都没有很好的权衡短作业和长作业。

那么,高响应比优先 (Highest Response Ratio Next, HRRN)调度算法主要是权衡了短作业和长作业。

每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行,「响应比优先级」的计算公式:

从上面的公式,可以发现:

  • 如果两个进程的「等待时间」相同时,「要求的服务时间」越短,「响应比」就越高,这样短作业的进程容易被选中运行;
  • 如果两个进程「要求的服务时间」相同时,「等待时间」越长,「响应比」就越高,这就兼顾到了长作业进程,因为进程的响应比可以随时间等待的增加而提高,当其等待时间足够长时,其响应比便可以升到很高,从而获得运行的机会;
04 时间片轮转调度算法

最古老、最简单、最公平且使用最广的算法就是时间片轮转(Round Robin, RR)调度算法。 。

每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。

  • 如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配另外一个进程;
  • 如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;

另外,时间片的长度就是一个很关键的点:

  • 如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率;
  • 如果设得太长又可能引起对短作业进程的响应时间变长。将

通常时间片设为 20ms~50ms 通常是一个比较合理的折中值。

05 最高优先级调度算法

前面的「时间片轮转算法」做了个假设,即让所有的进程同等重要,也不偏袒谁,大家的运行时间都一样。

但是,对于多用户计算机系统就有不同的看法了,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级(Highest Priority First,HPF)调度算法

进程的优先级可以分为,静态优先级或动态优先级:

  • 静态优先级:创建进程时候,就已经确定了优先级了,然后整个运行时间优先级都不会变化;
  • 动态优先级:根据进程的动态变化调整优先级,比如如果进程运行时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是随着时间的推移增加等待进程的优先级

该算法也有两种处理优先级高的方法,非抢占式和抢占式:

  • 非抢占式:当就绪队列中出现优先级高的进程,运行完当前进程,再选择优先级高的进程。
  • 抢占式:当就绪队列中出现优先级高的进程,当前进程挂起,调度优先级高的进程运行。

但是依然有缺点,可能会导致低优先级的进程永远不会运行。

06 多级反馈队列调度算法

多级反馈队列(Multilevel Feedback Queue)调度算法是「时间片轮转算法」和「最高优先级算法」的综合和发展。

顾名思义:

  • 「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。
  • 「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;

来看看,它是如何工作的:

  • 设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短
  • 新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成;
  • 当较高优先级的队列为空,才调度较低优先级的队列中的进程运行。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行;

可以发现,对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。

看的迷迷糊糊?那我拿去银行办业务的例子,把上面的调度算法串起来,你还不懂,你锤我!

办理业务的客户相当于进程,银行窗口工作人员相当于 CPU。

现在,假设这个银行只有一个窗口(单核 CPU ),那么工作人员一次只能处理一个业务。

那么最简单的处理方式,就是先来的先处理,后面来的就乖乖排队,这就是先来先服务(FCFS)调度算法。但是万一先来的这位老哥是来贷款的,这一谈就好几个小时,一直占用着窗口,这样后面的人只能干等,或许后面的人只是想简单的取个钱,几分钟就能搞定,却因为前面老哥办长业务而要等几个小时,你说气不气人?

有客户抱怨了,那我们就要改进,我们干脆优先给那些几分钟就能搞定的人办理业务,这就是短作业优先(SJF)调度算法。听起来不错,但是依然还是有个极端情况,万一办理短业务的人非常的多,这会导致长业务的人一直得不到服务,万一这个长业务是个大客户,那不就捡了芝麻丢了西瓜

那就公平起见,现在窗口工作人员规定,每个人我只处理 10 分钟。如果 10 分钟之内处理完,就马上换下一个人。如果没处理完,依然换下一个人,但是客户自己得记住办理到哪个步骤了。这个也就是时间片轮转(RR)调度算法。但是如果时间片设置过短,那么就会造成大量的上下文切换,增大了系统开销。如果时间片过长,相当于退化成退化成 FCFS 算法了。

既然公平也可能存在问题,那银行就对客户分等级,分为普通客户、VIP 客户、SVIP 客户。只要高优先级的客户一来,就第一时间处理这个客户,这就是最高优先级(HPF)调度算法。但依然也会有极端的问题,万一当天来的全是高级客户,那普通客户不是没有被服务的机会,不把普通客户当人是吗?那我们把优先级改成动态的,如果客户办理业务时间增加,则降低其优先级,如果客户等待时间增加,则升高其优先级。

那有没有兼顾到公平和效率的方式呢?这里介绍一种算法,考虑的还算充分的,多级反馈队列(MFQ)调度算法,它是时间片轮转算法和优先级算法的综合和发展。它的工作方式:

  • 银行设置了多个排队(就绪)队列,每个队列都有不同的优先级,各个队列优先级从高到低,同时每个队列执行时间片的长度也不同,优先级越高的时间片越短
  • 新客户(进程)来了,先进入第一级队列的末尾,按先来先服务原则排队等待被叫号(运行)。如果时间片用完客户的业务还没办理完成,则让客户进入到下一级队列的末尾,以此类推,直至客户业务办理完成。
  • 当第一级队列没人排队时,就会叫号二级队列的客户。如果客户办理业务过程中,有新的客户加入到较高优先级的队列,那么此时办理中的客户需要停止办理,回到原队列的末尾等待再次叫号,因为要把窗口让给刚进入较高优先级队列的客户。

可以发现,对于要办理短业务的客户来说,可以很快的轮到并解决。对于要办理长业务的客户,一下子解决不了,就可以放到下一个队列,虽然等待的时间稍微变长了,但是轮到自己的办理时间也变长了,也可以接受,不会造成极端的现象,可以说是综合上面几种算法的优点。

总结:

/***************一些常见的操作系统的任务(进程)调度算法*****************/

1)先来先服务(First Come First Served,简称FCFS)调度算法:最简单的任务/进程调度算法,该调度算法每次从当前运行进程的后备作业队列中选择一个或多个任务/进程并将其调入内存,分配资源。从表面上看,该算法对所有任务/进程都是公平的,不过该算法的缺点在于若有较长的作业流程的任务/进程正在工作,短作业流程的任务/进程需要等待很长时间。显然,该算法简单但总体效率较低,而且该调度算法对长运行时间作业有利,但对短运行时间作业不利。

2)短作业优先(Shortest Job First,简称SJF)调度算法:该调度算法每次从当前运行进程的后备作业队列中选择一个或多个运行时间最短的任务/进程,将其调入内存,分配资源。由于作业在未运行时无法事先知道实际运行时间的长短,因此该算法需要作业在提交申请的同时附带该作业运行时间的估算值。显然,该调度算法对短运行时间作业有利,但对长运行时间作业不利。

3)优先级调度算法:该调度算法基于需要运行的任务/进程的紧迫程度来进行调度,每次从当前运行进程的后备作业队列中选择一个或多个优先级最高的任务/进程并将其调入内存,分配资源。根据新的更高优先级进程能否抢夺当前正在执行的进程,可将该调度算法分为非剥夺式(无法打断)/可剥夺式(可以打断)两类。

4)最高响应比(Highest Response_ratio Next,简称HRN)调度算法:该调度算法是FCFS和SJF的一种综合平衡,响应比R的计算方法为:

R=(等待时间+预估运行时间)/预估运行时间

由此我们可以看出:

    1.当等待时间相同时,则预估运行时间越短,响应比越高,此时接近SJF,有利于短作业

    2.当预估运行时间相同时,则响应比由其等待时间决定,等待时间越长,响应比越高,此时接近FCFS

    3.对于较长运行时间的作业,作业的响应比可以随着作业的等待时间增加而逐渐提高,这样就可以一定程度克服进程调度的不公平的情况

5)时间片轮转调度算法:适用于分时系统,在这种算法中,将CPU的运行时间分解为时间片(几十毫秒到上百毫秒不等),每个进程都只能在对应的时间片内执行。时间片过后,即使该进程仍未完成也必须释放资源给下一个就绪的进程,被剥夺资源的进程重新排队等候再次运行。这种调度算法较为公平,不过若时间片切换过于频繁,则系统资源的开销会很大,因此选取合适的时间片是十分重要的。

6)多级反馈队列调度算法:该算法集合了上述所有算法的综合优点,通过动态调配进程优先级和时间片大小,可以实现兼顾多方面的系统任务/进程,同时无需事先预估任务/进程的运行时间。

/***************一些常见的操作系统的任务(进程)调度算法end**************/
 

3、任务

//任务、进程、线程的关系

任务是一个逻辑概念,指一个软件完成的活动,或者软件为了完成该活动/实现某个目的进行的一系列操作。通常情况下一个任务是一个程序的一次运行,一个任务可以包含一个或多个独立功能的子任务,通常情况下独立的子任务是进程或线程。


二、进程的概念

/************进程与程序的区别*******************/

进程与程序的区别有几点:

1)程序是静态的,它是保存在磁盘上的一些指令的有序集合,没有任何执行的概念;进程是动态的,它是程序执行的过程,包括创建、调度、消亡。

2)进程是一个独立的可调度的任务,是一个抽象实体,当系统在执行某个程序时,系统会分配和释放各种需要的资源。进程不仅包括程序的指令和数据,还包括程序计数器值、CPU寄存器值以及存储数据的堆栈等。

3)进程是一个程序的一次执行的过程。

4)进程是程序执行和资源管理的最小单位。

/************进程与程序的区别end****************/


 什么是进程?

进程(Process):进程是指一个具有独立功能的程序在某个数据集合上的一次动态执行的过程,进程是操作系统进行资源分配和调度的基本单元进程是程序执行和资源管理的最小单位

Linux内核只认识进程 且内核是通过task_struct来管理进程的
首先,进程是一个动态过程而不是静态实物其次,进程就是程序的一次运行过程,一个静态的可执行程序a.out的一次运行过程(./a.out去运行到结束)就是一个进程。内核中构建了一种数据结构task_struct,又称进程控制块PCB(process control block),用来专门管理一个进程。

进程,主要包含三个元素
o. 一个可以执行的程序;
o. 和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);
o. 程序的执行上下文(execution context)。


*Linux下的进程结构--关于进程控制块(PCB Processing Control Block)                                                                                       (task_struct)


在广义上,所有的进程信息被放在一个叫做进程控制块的数据结构task_struct中,可以理解为进程属性的集合。


进程控制块:
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体
task_struct是Linux内核的⼀一种数据结构,它会被装载到RAM⾥里并且包含着进程的信息。


每个进程都把它的信息放在 task_struct 这个数据结构⾥里,task_struct 包含了这些内容: 


1.进程标示符 (PID): 描述本进程的唯⼀一标示符,⽤用来区别其他进程。(父进程标示符是

                                                                                                                              PPID)
2.状态 :任务状态,退出代码,退出信号等。
3.优先级 :相对于其他进程的优先级。
4.程序计数器:程序中即将被执⾏行的下一条指令的地址。
5.内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6.上下文数据:进程执行时处理器的寄存器中的数据。
7.I/O状态信息:包括显⽰示的I/O请求,分配给进程的I/O设备和被进程使⽤用的⽂文件列表。
8.记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
保存进程信息的数据结构叫做 task_struct,并且可以在 include/linux/sched.h ⾥里找到它。
所有运⾏行在系统⾥里的进程都以 task_struct 链表的形式存在内核⾥里。
进程的信息可以通过 /proc 系统⽂文件夹查看。比如 要获取PID为400的进程信息,你需要查看       /proc/400  这个⽂文件夹。大i多数进程信息同样可以使⽤用top和ps这些⽤用户级⼯工具来获取。

因为Linux系统是一个多任务的操作系统,所以操作系统必须采取某种调度算法将处理器合理地分配给正在等待的进程。内核将所有进程存放在一个双向循环链表中,该链表的每一项都是task_struct类型的结构体,称为进程控制块。task_struct结构体内容很多,它能完整描述一个进程,如进程的状态、进程的基本信息、进程的标识符、内存相关信息、父进程信息、与该进程相关的终端信息、当前工作目录、当前打开的文件、所接收的信号等。

/***************task_struct结构体部分成员简介***********************/

//该结构体在/usr/src/linux-headers-3.2.0-29/include/linux/sched.h文件内,大约位于文件中部(1227行)

//不同虚拟机内该文件位置可能不同

1、进程状态

    volatile long state;

state成员用于描述进程的状态,可能的取值如下:

    TASK_RUNNING            进程正在运行或准备运行

    TASK_INTERRUPTIBLE        进程处在阻塞(睡眠)状态,等待某些事件发生。若被唤醒,则转变成TASK_RUNNING状态

    TAST_UNINTERRUPTIBLE    与前者类似,不过不会接收信号

    __TASK_STOPPED            进程被停止

    __TASK_TRACED            进程被debugger等进程监视

    EXIT_ZOMBIE                进程被终止,但是其父进程还未使用wait()函数族函数回收

    EXIT_DEAD                进程最终退出的状态

2、进程标识符

    pid_t pid;                进程标识符

    pid_t tgid;                线程组标识符(thread group id)

    其中pid表示进程标识符,在默认情况下,PID的取值范围是0~32767,即系统内进程最多有32767个。tgid表示的是线程组标识符,在内核运行多进程/多线程任务时,对于一个进程内的不同线程来说,每个线程都有不同的pid,但是有统一的tgid,线程组的领头线程的pid与tgid相同。当我们使用getpid()函数获取当前运行进程的进程号时,实际getpid()函数的返回值是tgid的值而不是pid的值。

3、表示进程亲属关系

    struct task_struct *real_parent;

    struct task_struct *parent;

    struct list_head children;

    struct list_head sibling;

    struct task_struct *group_leader;

其中

    real_parent                指向父进程,如果创建它的父进程已经不存在,则会指向init进程(PID为1的进程)

    parent                    指向父进程,当进程被终止时必须向父进程发送信号。通常该值与real_parent相同

    children                链表头结点,该链表内的元素都是该进程的子进程

    sibling                    当前进程的兄弟进程,该成员用于将当前进程信息插入到它的兄弟进程的链表内

    group_leader            指向所在进程组的领头进程

4、进程调度优先级

    int prio,static_prio,normal_prio;

    unsigned int rt_priority;

其中

    prio                    保存该进程的动态优先级

    static_prio                保存该进程的静态优先级,范围为MAX_RT_PRIO到MAX_PRIO-1(100~139),值越大优先级越低

    normal_prio                取决于静态优先级与进程调度策略

    rt_priority                保存该进程的实时优先级,范围为0到MAX_RT_PRIO-1(0~99),值越大优先级越低

5、运行时间

    cputime_t utime,stime;

二者都用于记录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值