进程的调度算法
百度百科调度算法词条:调度算法
调度算法是指根据系统的资源分配策略所规定的资源分配算法。
常见的有:
- 先来先服务(FCFS)
最简单的调度算法,也称为先进先出或严格排队方案。当每个进程就绪后,它加入就绪队列。当前正运行的进程执行完毕之后,选择在就绪队列中存在时间最长的进程执行。可以用于作业/进程调度。先来先去服务比较适合于长作业/进程,不利于短作业/进程。 - 时间片轮转法(RR)
以一个周期性间隔产生时钟中断,当中断发生后,当前正在运行的进程被置于就绪队列中,然后基于先来先去服务策略选择下一个就绪作业的运行。 - 最短进程优先(SPF)/最短作业优先(SJF)
最短进程优先算法是一种非剥夺式/抢占式算法,总是选取预计作业时间最短的作业优先运行。 - 最短剩余时间优先(SRTF)
在每个进程到来时,短期调度程序在可用进程列表和正在运行的进程中以最少的剩余突发时间安排进程。
一旦所有进程都在就绪队列中可用,就不会执行抢占,并且该算法将作为SJF调度工作。 当进程从执行中被移除并且下一个进程被调度时,进程的上下文被保存在进程控制块中。 该PCB在下一次执行该过程时被访问。 - 最高响应比优先
R=(w+s)/s:R为响应比,w为等待处理的时间,s为预计的服务时间。
如果该进程被立即调用,则R值等于周转时间和服务时间的比率。R最小值为1,只有第一个进入系统的进程才能达到该值。当前进程完成或被阻塞时,选择R值最大的就绪进程执行。 - 多级反馈队列算法
当前被公认的一种较好的进程调度算法。过程如下:
首先设置多个就绪队列,各个队列赋予不同的优先级。优先级越高的队列中,每个进程的执行时间片越小。
当一个新进程进入内存后,首先放入优先级第一的队列末尾,按照先来先服务原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入优先级第二队列的末尾,同样等待调度,如此循环,当一个长作业/进程从第一队列依次将到第n队列/最后队列后,便按第n队列时间片轮转运行。
仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。
linux内核的三种调度方法
- SCHED_OTHER 分时调度策略,
- SCHED_FIFO实时调度策略,先到先服务
- SCHED_RR实时调度策略,时间片轮转
相关书籍《深入理解计算机系统》第九章虚拟存储器、《操作系统精髓设计原理》第二部分进程
task_struct结构体
结构体的定义位置:/usr/src/kernels/3.10.0-514.21.1.el7.x86_64/include/linux目录下的sched.h文件中。
task_struct结构体是Linux下的进程控制块PCB,PCB里包含着一个进程的所有信息。
- 标识符:跟这个进程相关的唯一标识符,用来区别其他进程。
- 状态:执行状态等。
- 优先级:相对于其他进程的优先级。
- 程序计数器:程序中即将被执行的下一条指令的地址。
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据:进程执行时处理器的寄存器中的数据。
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备(如磁带驱动器)和被进程使用的文件列表。
- 审计信息:包括处理器时间总和,使用的时钟数总和,时间限制,审计号等。
进程的标识PID(process identifier):
pid_t pid; //进程的唯一标识
pid_t tgid; // 线程组的领头线程的pid成员的值
32位无符号整型数据。表示每一个进程的标识符。也是内核提供给用户程序的借口,用户程序通过pid操作程序。还引入了线程组的概念称为:tgid。一个线程组中的所有线程使用和该线程组中的第一个轻量级线程的pid,被存在tgid成员中。当进程没有线程时,tgid=pid;当有多线程时,tgid表示的是主线程的id,而pid表示每一个线程自己的id。
进程的状态
volatile long state
state的取值:
#define TASK_RUNNING 0:进程要么正在执行,要么准备执行
#define TASK_INTERRUPTIBLE 1:可中断的睡眠
#define TASK_UNINTERRUPTIBLE 2:不可中断睡眠
#define __TASK_STOPPED 4:停止执行
#define __TASK_TRACED 8:被追踪
#define EXIT_ZOMBIE 16:僵尸状态
#define EXIT_DEAD 32:进程的最终状态,进程死亡
#define TASK_DEAD 64:死亡
#define TASK_WAKEKILL 128:唤醒并杀死的进程
#define TASK_WAKING 256:唤醒进程
进程的优先级
long priority
给出进程每次获取CPU后可使用的时间。优先级可通过系统sys_setpriorty改变(在kernel/sys.c中)。
进程调度信息
need_resched:调度标志
Nice:静态优先级
Counter:动态优先级;重新调度进程时会在run_queue中选出Counter值最大的进程
Policy:调度策略开始运行时被赋予的值
rt_priority:实时优先级
进程通信有关信息(IPC:Inter_Process Communication)
unsigned long signal:进程接收到的信号;每位表示一种信号,共32种;置位有效
unsigned long blocked:进程所能接受信号的位掩码;置位表示屏蔽,复位表示不屏蔽
Spinlock_t sigmask_lock:信号掩码的自旋锁
Long blocked:信号掩码
Struct sem_undo *semundo:为避免死锁而在信号量上设置的取消操作
Struct sem_queue *semsleeping:与信号量操作相关的等待队列
struct signal_struct *sig:信号处理函数
时间信息
Start_time:进程创建时间
Per_cpu_utime:进程在执行时在用户态上耗费的时间
Pre_cpu_stime:进程在执行时在系统态上耗费的时间
ITIMER_REAL:实时定时器,不论进程是否运行,都在实时更新
ITIMER_VIRTUAL:虚拟定时器,只有进程运行在用户态时才会更新
ITIMER_PROF:概况定时器,进程在运行处于用户态和系统态时更新
文件信息
Sruct fs_struct *fs:进程的可执行映象所在的文件系统,有两个索引点,称为root和pwd,分别指向对应的根目录和当前目录。
Struct files_struct *files:进程打开的文件
地址空间/虚拟内存信息
每个进程都有自己的一块虚拟内存空间,mm_struct中使用两个指针表示一段虚拟地址空间,然后在最终时通过页表映射到真正的物理内存上。
页面管理信息
Int swappable:进程占用的内存页面是否可换出
Unsigned long min_flat,maj_flt,nswap:进程累计换出、换入页面数
Unsigned long cmin_flat,cmaj_flt,cnswap:本进程作为祖先进程,其所有层次子进程的累计换出、换入页面数
对称对处理机信息
Int has_cpu: 进程是否当前拥有CPU
Int processor: 进程当前正在使用的CPU
Int lock_depth: 上下文切换时内核锁的深度
上下文信息
struct desc_struct *ldt:进程关于CPU段式存储管理的局部描述符表的指针
struct thread_struct tss:任务状态段
信号量数据成员
struct sem_undo *semundo:进程每一次操作一次信号量,都会生成一个undo操作。保存在sem_undo结构体中
进程队列指针
struct task_struct *next_task,*prev_task:所有进程均有各自的PCB。且各个PCB会串在一起,形成一个双向链表。其next_task和prev_task就表示上一个或下一个PCB,即前后指针
struct task_struct *next_run,*prev_run:由进程的run_queue中产生作用的,指向上一个或下一个可运行的进程,链表的头和尾都是0号进程。
struct task_struct *p_opptr:原始父进程(祖先进程)
struct task_struct *p_pptr :父进程
struct task_struct *p_cptr:子进程
struct task_struct *p_ysptr:弟进程
struct task_struct *p_osptr:兄进程
current:当前正在运行进程的指针
struct task_struct init_task:0号进程的PCB
char comm[16]:进程正在执行的可执行文件的文件名
int errno:进程最后一次出错的错误号,0表示无错误
模拟实现僵尸进程、孤儿进程
僵尸进程的实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if (pid == -1)
exit(1);
else if (pid == 0) // child
{
printf("Child pid is: %d, parent pid is : %d\n", getpid(), getppid());
}
else // parent
{
sleep(50);
printf("parent pid is: &d\n", getpid());
}
return 0;
}
第二个即为僵尸进程。
僵尸进程是一个子进程在其父进程还没有调用wait()或waitpid()的情况下退出。这个子进程就是僵尸进程。任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。
孤儿进程的实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if (pid == -1) // 进程创建失败
exit(1);
else if (pid == 0) // 子进程
{
sleep(5);
printf("Child pid is: %d, parent pid is: %d\n", getpid(), getppid());
}
else // 父进程
{
printf("Parent pid is: %d\n", getpid());
return 0;
}
}
孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程,即其父进程先于子进程结束。子进程成为孤儿进程后被1号进程所领养,其父进程id变为1。
环境变量相关函数和命令
setenv函数
用于改变或增加环境变量,但是通过此函数并不能添加或修改shell进程的环境变量,即通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止再次运行该程序,上次的设置是无效的,上次设置的环境变量是无法读到的。
int setenv(const char *name,const char *value,int overwrite);
name为环境变量名称字符串,value为变量内容,overwrite决定是否要改变已存在的环境变量,如果没有此环境变量,则无论overwrite为何值均添加此环境变量。若此环境变量存在,overwrite不为0时,原内容会被改为参数value所指的变量内容,当overwrite为0时,参数会被忽略。
返回值:成功返回0,错误发生,返回-1。
命令
echo: 显⽰示某个环境变量值
export: 设置⼀一个新的环境变量
env: 显示所有环境变量
unset: 清除环境变量
set: 显示本地定义的shell变量和环境变量