这里总结一下进程相关的基本概念。
操作系统在计算机中担当着管家的身份,管理着计算机的软、硬件资源,当程序被加载到内存上变成进程时,操作系统就会为进程的运行做一系列准备工作,操作系统是如何管理进程呢?
有两个重要的步骤:1.描述进程 2.组织进程
1.描述进程:
进程的信息被放在一个数据结构(进程控制块)中,可以将这个数据结构看做进程属性的集合,简称PCB(Process Control Block)。
在linux中,描述进程的结构体叫 task_struct ,task_struct是linux内核的一种数据结构会被装载到RAM里。
task_struct的内容分类:
- 标示符:描述进程的唯一标示,区分其他进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
- 状态:进程的任务状态,退出代码,退出信号等
常见状态:
R运行状态(running):并不意味着进程在运行中,它表明进程可能是在运行中可能在运行队
列里。
S睡眠状态(sleeping):进程在等待事件完成(也叫称可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep):也称不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运⾏行。
X死亡状态(dead):这个状态只是一个返回状态。
Z僵尸状态(zombie):当进程退时,父进程没有读取到子进程退出的返回代码,就会产生僵死(尸)进程,僵尸进程会以终止状态保持在进程表中,并且一直等待父进程读取退出状态代码。
进程状态转换:
- 优先级:与其他进程比较,优先获得资源的权限
PRI,即进程的优先级,就是程序被CPU执⾏行的先后顺序,此值越小进程的优先级别越高,NI就是nice值了,表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice // PRI(old) 默认为80
当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19,一共40个级别。
- 程序计数器:记录程序中即将执行的下一条指令的地址
- 内存指针:程序代码和进程相关数据的指针,与其他进程共享内存的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据
- I/O状态信息:显示的I/O请求,分配给进程的I/O,被设备使用的文件列表
- 记账信息: 处理器时间的总和,使用的时钟总和,时间限制,记账号等
- 其他信息
2.组织进程:
进程以task_struct 链表 的形式存在内核中。
上面提到父子进程,进程在运行过程中往往会创建出子进程来协助执行功能,通过系统调用来创建子进程。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 int ret = fork();
8 if(ret < 0){
9 perror("fork");
10 return 1;
11 }
12 else if(ret == 0){ //创建的子进程
13 printf("This is child,pid:%d\n",getpid());
14 sleep(1);
15 }else{ // 父进程
16 printf("This is father,pid:%d\n",getppid());
17 sleep(1);
18 }
19 return 0;
20 }
当子进程退出时,父进程没有接受子进程的退出信息,子进程就会进入僵尸状态,一直保持停止状态等待。僵尸进程会导致内存泄漏,因此子进程退出时应多注意。
以下为模拟实现僵尸进程:
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main()
5 {
6 pid_t id = fork();
7 if(id < 0){
8 perror("fork");
9 return 1;
10 }else if(id > 0){ //father
11 printf("father[%d] is sleeping...\n",getpid());
12 sleep(5);
13 }else{ //child
14 printf("child[%d] is begin z...\n",getpid());
15 sleep(2);
16 exit(EXIT_SUCCESS);
17 }
18 return 0;
19 }
当父进程提前结束,此时没有父进程来回收子进程退出信息,子进程就变成孤儿进程,操作系统为了避免内存泄漏,会收养孤儿进程。
模拟如下:
1
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 pid_t id = fork();
8
9 if(id < 0){
10 perror("fork");
11 return 1;
12 }else if(id == 0){ // child
13 printf("this is child, pid:%d\n",getpid());
14 sleep(10);
15 }else{ //father
16 printf("this is father, pid:%d\n",getpid());
17 sleep(3);
18 exit(0);
19 }
20
21 return 0;
22 }
进程的特点:
- 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
- 并行:多个进程在多个CPU下分别,同时运行,这称之为并行。
- 并发:多个进程在一个CPU下采用进程切换的⽅方式,在一段时间之内,让多个进程都得以推进,称之为并发。
在多道程序系统中,进程的数量往往多于处理机的个数,进程争用处理机的情况就在所难免。处理机调度是对处理机进行分配,就是从就绪队列中,按照一定的算法(公平、高效)选择一个进程并将处理机分配给它运行,以实现进程并发地执行。就有了进程调度算法。
以下为操作系统常见的调度算法:
1.先来先服务 (FCFS,First Come First Served)
原理:进程按照它们请求CPU的顺序使用CPU,谁第一个排,谁就先被执行,在它执行的过程中,不会中断它。当其他人也想进入内存被执行,就要排队等着,如果在执行过程中出现一些事,停止执行,下一个补上,此时如果它又想执行,只能站到队尾重新等待。
优点:易于理解且实现简单,只需要一个队列(FIFO),且相当公平
缺点:比较有利于长进程,而不利于短进程,有利于CPU 繁忙的进程,而不利于I/O 繁忙的进程。
2.最短作业优先(SJF, Shortest Job First)
短作业优先(SJF, Shortest Job First)又称为“短进程优先”SPN(Shortest Process Next),这是对FCFS算法的改进,其目标是减少平均周转时间。
原理:对预计执行时间短的进程优先分派处理机,通常后来的短进程不抢先正在执行的进程。
优点:相比FCFS 算法,该算法改善了平均周转时间和平均带权周转时间,缩短进程的等待时间,提高系统的吞吐量。
缺点:对长进程非常不利,可能长时间得不到执行,且未能依据进程的紧迫程度来划分执行的优先级,以及难以准确估计进程的执行时间,从而影响调度性能。
3.最高响应比优先法 (HRRN,Highest Response Ratio Next)
最高响应比优先法(HRRN,Highest Response Ratio Next)是对FCFS方式和SJF方式的一种综合平衡。HRRN调度策略同时考虑每个作业的等待时间长短和估计需要的执行时间长短,从中选出响应比最高的作业投入执行。这样,即使是长作业,随着它等待时间的增加,W / T也就随着增加,也就有机会获得调度执行。
原理:响应比R定义如下: R =(W+T)/T = 1+W/T
其中T为该作业估计需要的执行时间,W为作业在后备状态队列中的等待时间。每当要进行作业调度时,系统计算每个作业的响应比,选择其中R最大者投入执行。
优点:由于长作业也有机会投入运行,在同一时间内处理的作业数显然要少于SJF法,从而采用HRRN方式时其吞吐量将小于采用SJF 法时的吞吐量。
缺点:由于每次调度前要计算响应比,系统开销也要相应增加。
4.时间片轮转算法(RR,Round-Robin)
该算法采用剥夺策略,时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
原理:让就绪进程以FCFS 的方式按时间片轮流使用CPU 的调度方式,即将系统中所有的就绪进程按照FCFS 原则,排成一个队列,每次调度时将CPU 分派给队首进程,让其执行一个时间片,时间片的长度从几个ms 到几百ms。在一个时间片结束时,发生时钟中断,调度程序暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前的队首进程,进程可以未使用完一个时间片,就出让CPU(如阻塞)。
优点:时间片轮转调度算法的特点是简单易行、平均响应时间短。
缺点:不利于处理紧急作业。在时间片轮转算法中,时间片的大小对系统性能的影响很大,因此时间片的大小应选择恰当。。
时间片大小的确定:
1.系统对响应时间的要求
2.就绪队列中进程的数目
3.系统的处理能力
5.多级反馈队列 (Multilevel Feedback Queue)
多级反馈队列调度算法是一种CPU处理机调度算法,UNIX操作系统采取的便是这种调度算法。
1.进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
2.首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
3.对于同一个队列中的各个进程,按照时间片轮转法调度。比如Q1队列的时间片为N,那么Q1中的作业在经历了N个时间片后若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
4.在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(抢占式)。
注:进程调度算法内容部分来自网上查阅。