进程调度算法
在操作系统中,用户进程一般都多于处理器数目,因此将导致他们相互争夺cpu资源
进程调度算法就是按一定的策略,动态的cpu资源分配给处于就绪队列中的某一个进程使之运行
进程调度任务
- 保存当前进程的现场信息
- 根据算法条件选取进程
- 将处理器分配给进程
先来先服务调度算法
按照进程的进入就绪状态的队列先后次序,分派cpu资源
(非抢占式)当前进程执行完毕或者阻塞,让出cpu资源
FCFS(先到先服务)调度算法属于不可剥夺算法
特点
- 算法简单
- 效率低
- 有利于cpu繁忙型作业,不利于I/O繁忙型作业
短作业优先(SJF)调度算法
短作业(进程)优先调度算法是指对短作业(进程)优先调度的算法。
缺点
- 该算法对长作业不利,SJF调度算法中长作业的周转时间会增加。更严重的是,如果有一长作业进入系统的后备队列,由于调度程序总是优先调度那些 (即使是后进来的)短作业,将导致长作业长期不被调度(“饥饿”现象,注意区分“死锁”。后者是系统环形等待,前者是调度策略问题)。
- 该算法完全未考虑作业的紧迫程度,因而不能保证紧迫性作业会被及时处理。
- 由于作业的长短只是根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度。
优先级调度算法
优先级调度算法又称优先权调度算法,该算法既可以用于作业调度,也可以用于进程调度,该算法中的优先级用于描述作业运行的紧迫程度。
在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。
在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。
根据新的更高优先级进程能否抢占正在执行的进程分类:
- 非剥夺式优先级调度算法。当某一个进程正在处理机上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于其自身的原因而主动让出处理机时(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。
- 剥夺式优先级调度算法。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
而根据进程创建后其优先级是否可以改变,可以将进程优先级分为以下两种:
- 静态优先级。优先级是在创建进程时确定的,且在进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求。
- 动态优先级。在进程运行过程中,根据进程情况的变化动态调整优先级。动态调整优先级的主要依据为进程占有CPU时间的长短、就绪进程等待CPU时间的长短。
高响应比优先调度算法
高响应比优先调度算法主要用于作业调度,该算法是对FCFS调度算法和SJF调度算法的一种综合平衡,同时考虑每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
响应比的变化规律可描述为:
响应比Rp = 等待时间+要求服务时间 / 要求服务时间
根据公式可知:
- 当作业的等待时间相同时,则要求服务时间越短,其响应比越高,有利于短作业。
- 当要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是先来先服务。
- 对于长作业,作业的响应比可以随等待时间的增加而提高,当其等待时间足够长时,其响应比便可升到很高,从而也可获得处理机。克服了饥饿状态,兼顾了长作业。
时间片轮转调度算法
时间片轮转调度算法主要适用于分时系统。
在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列,进程调度程序总是选择就绪队列中第一个进程执行,即先来先服务的原则,但仅能运行一个时间片,如100ms。在使用完一个时间片后,即使进程并未完成其运行,它也必须释放出(被剥夺)处理机给下一个就绪的进程,而被剥夺的进程返回到就绪队列的末尾重新排队,等候再次运行。
在时间片轮转调度算法中,时间片的大小对系统性能的影响很大。如果时间片足够大,以至于所有进程都能在一个时间片内执行完毕,则时间片轮转调度算法就退化为先来先服务调度算法。如果时间片很小,那么处理机将在进程间过于频繁切换,使处理机的开销增大,而真正用于运行用户进程的时间将减少。因此时间片的大小应选择适当。
时间片的长短通常由以下因素确定:系统的响应时间、就绪队列中的进程数目和系统的处理能力。
多级反馈队列调度算法(集合了前几种算法的优点)
多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合和发展,如图2-5 所示。通过动态调整进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。例如,为提高系统吞吐量和缩短平均周转时间而照顾短进程;为获得较好的I/O设备利用率和缩短响应时间而照顾I/O型进程;同时,也不必事先估计进程的执行时间。
多级反馈队列调度算法的实现思想如下:
- 应设置多个就绪队列,并为各个队列赋予不同的优先级,第1级队列的优先级最高,第2级队列次之,其余队列的优先级逐次降低。
- 赋予各个队列中进程执行时间片的大小也各不相同,在优先级越高的队列中,每个进程的运行时间片就越小。例如,第2级队列的时间片要比第1级队列的时间片长一倍,
……第i+1级队列的时间片要比第i级队列的时间片长一倍。- 当一个新进程进入内存后,首先将它放入第1级队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第2级队列的末尾,再同样地按FCFS
原则等待调度执行;如果它在第2级队列中运行一个时间片后仍未完成,再以同样的方法放入第3级队列……如此下去,当一个长进程从第1级队列依次降到第
n 级队列后,在第 n 级队列中便釆用时间片轮转的方式运行。- 仅当第1级队列为空时,调度程序才调度第2级队列中的进程运行;仅当第1 ~ (i-1)级队列均为空时,才会调度第i级队列中的进程运行。如果处理机正在执行第i级队列中的某进程时,又有新进程进入优先级较高的队列(第 1~
(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i级队列的末尾,把处理机分配给新到的更高优先级的进程。
多级反馈队列的优势有:
- 终端型作业用户:短作业优先。
- 短批处理作业用户:周转时间较短。
- 长批处理作业用户:经过前面几个队列得到部分执行,不会长期得不到处理。
task_struct
概念:
进程是处于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称。注意,程序并不是进程,实际上两个或多个进程不仅有可能执行同一程序,而且还有可能共享地址空间等资源。
Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息。它定义在linux-2.6.38.8/include/linux/sched.h文件中。
位置: ./usr/src/kernels/3.10.0-693.el7.x86_64/include/linux/sched.h
task_struct结构体中的主要信息:
1、进程状态:记录进程是处于运行状态还是等待状态
2、调度信息:进程由哪个函数调度,具体怎样调度等
3、进程之间的通讯状况
4、进程之间的亲属关系:在父进程和子进程之间有task_struct类型的指针,将父进程和子进程联系起来
5、时间数据信息:每个进程执行所占用CPU的时间
6、进程的标志
7、进程的标识符:该进程唯一的标识符用来区别其他进程
8、信号处理信息
9、文件信息:可以进行读写操作的一些文件的信息
10、页面管理信息
11、优先级:相对于其他进程的优先级
12、ptrace系统调用
13、虚拟内存处理
使用代码模拟实现僵尸进程, 孤儿进程的场景.
僵尸进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
printf("1==fa=%d\n",getpid());
pid_t q=fork();
if(q<0){
printf("fork error!");
return -1;
}else if(q>0){
printf("fa=%d\n",getpid());
sleep(20);
}else {
printf("ch=%d\n",getpid());
sleep(10);
exit(EXIT_SUCCESS);
}
return 0;
}
孤儿进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
printf("1==fa=%d\n",getpid());
pid_t q=fork();
if(q<0){
printf("fork error!");
return -1;
}else if(q>0){
printf("fa=%d\n",getpid());
sleep(5);
exit(0);
}else {
printf("ch=%d\n",getpid());
sleep(10);
}
return 0;
}
sentenv和export
setenv
作为setenv函数
作用:
增加或者修改环境变量。
注意:
通过此函数并不能添加或修改 shell 进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止后再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
头文件:#include<stdlib.h>
注:
stdlib.h在Linux和Windows中略不同,比如setenv函数是用在linux中的,在Windows中没有setenv函数而用putenv来代替
函数声明:int setenv(const char *name,const char * value,int overwrite);
函数说明:
setenv()用来改变或增加环境变量的内容。
参数name为环境变量名称字符串。
参数value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。
如果没有此环境变量则无论overwrite为何值均添加此环境变量。
若环境变量存在,当overwrite不为0时,原内容会被改为参数value所指的变量内容;
当overwrite为0时,则参数value会被忽略。
返回值执行成功则返回0,有错误发生时返回-1。
相关函数:
getenv,putenv,unsetenv
作为Linux中setenv命令
Linux中的功能:查询或显示环境变量
语法:setenv [变量名称] [变量值]
setenv用于在C shell设置环境变量的值
用法:setenv ENVVAR value
ENVVAR 为所要设置的环境变量的名。value为所要设置的环境变量的值
例:setenv PATH "/bin:/usr/bin:usr/sbin:"
设置环境path的搜索路径为/bin,/usr/bin
以及/usr/sbin
export
Linux中的功能:设置或显示环境变量(比如我们要用一个命令,但这个命令的执行文件不在当前目录,这样我们每次用的时候必须制定执行文件的目录,麻烦,在代码中先执行export,这个相当于告诉程序,执行某某东西时,需要的文件或什么东西在这些目录里)
说明:
在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该次登陆操作。
语法:export [-fnp] [变量名称] = [变量设置值]
参数说明:
-f 代表[变量名称]中为函数名称。
-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p 列出所有的shell赋予程序的环境变量。 延伸:export设置环境变量是暂时的,只在本次登录中有效,可修改如下文件来使命令长久有效。
注意:
1、执行脚本时是在一个子shell环境运行的,脚本执行完后该子shell自动退出;
2、一个shell中的系统环境变量才会被复制到子shell中(用export定义的变量);
3、一个shell中的系统环境变量只对该shell或者它的子shell有效,该shell结束时变量消失(并不能返回到父shell中)。
4、不用export定义的变量只对该shell有效,对子shell也是无效的。
一个变量创建时,它不会自动的为在它之后创建的shell进程所知。而命令export可以向后面的shell传递变量的值。
当一个shell脚本调用并执行时,它不会自动得到原来脚本(调用者)里定义的变量的访问权,除非这些变量已经被显示地设置为可用。
export命令可以用于传递一个或多个变量的值到任何后续脚本。
export设置环境变量是暂时的,只在本次登录中有效,若想要使得开机时自动加载这个环境变量免除以后每次设置,可将其写入/etc/re.local