转载自:http://www.cnblogs.com/zhaoyl/archive/2012/09/04/2671156.htmlhtml
Linux进程优先级linux
进程提供了两种优先级,一种是普通的进程优先级,第二个是实时优先级。前者适用SCHED_NORMAL调度策略,后者可选SCHED_FIFO或SCHED_RR调度策略。任什么时候候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占,同级实时进程之间是按照FIFO(一次机会作完)或者RR(屡次轮转)规则调度的。算法
首先,说下实时进程的调度shell
实时进程,只有静态优先级,由于内核不会再根据休眠等因素对其静态优先级作调整,其范围在0~MAX_RT_PRIO-1间。默认MAX_RT_PRIO配置为100,也即,默认的实时优先级范围是0~99。而nice值,影响的是优先级在MAX_RT_PRIO~MAX_RT_PRIO+40范围内的进程。缓存
不一样与普通进程,系统调度时,实时优先级高的进程老是先于优先级低的进程执行。知道实时优先级高的实时进程没法执行。实时进程老是被认为处于活动状态。若是有数个 优先级相同的实时进程,那么系统就会按照进程出如今队列上的顺序选择进程。假设当前CPU运行的实时进程A的优先级为a,而此时有个优先级为b的实时进程B进入可运行状态,那么只要b<a,系统将中断A的执行,而优先执行B,直到B没法执行(不管A,B为什么种实时进程)。数据结构
不一样调度策略的实时进程只有在相同优先级时才有可比性:多线程
1. 对于FIFO的进程,意味着只有当前进程执行完毕才会轮到其余进程执行。因而可知至关霸道。负载均衡
2. 对于RR的进程。一旦时间片消耗完毕,则会将该进程置于队列的末尾,而后运行其余相同优先级的进程,若是没有其余相同优先级的进程,则该进程会继续执行。异步
总而言之,对于实时进程,高优先级的进程就是大爷。它执行到无法执行了,才轮到低优先级的进程执行。等级制度至关森严啊。socket
重头戏,说下非实时进程调度
引子
将当前目录下的documents目录打包,但不但愿tar占用太多CPU: nice -19 tar zcf pack.tar.gz documents 这个“-19”中的“-”仅表示参数前缀;因此,若是但愿赋予tar进程最高的优先级,则执行: nice --19 tar zcf pack.tar.gz documents 也可修改已经存在的进程的优先级: 将PID为1799的进程优先级设置为最低: renice 19 1799 renice命令与nice命令的优先级参数的形式是相反的,直接以优先级值做为参数便可,无“-”前缀说法。
言归正传
Linux对普通的进程,根据动态优先级进行调度。而动态优先级是由静态优先级(static_prio)调整而来。Linux下,静态优先级是用户不可见的,隐藏在内核中。而内核提供给用户一个能够影响静态优先级的接口,那就是nice值,二者关系以下:
static_prio=MAX_RT_PRIO +nice+ 20
nice值的范围是-20~19,于是静态优先级范围在100~139之间。nice数值越大就使得static_prio越大,最终进程优先级就越低。
ps -el 命令执行结果:NI列显示的每一个进程的nice值,PRI是进程的优先级(若是是实时进程就是静态优先级,若是是非实时进程,就是动态优先级)
而进程的时间片就是彻底依赖 static_prio 定制的,见下图,摘自《深刻理解linux内核》,
咱们前面也说了,系统调度时,还会考虑其余因素,于是会计算出一个叫进程动态优先级的东西,根据此来实施调度。由于,不只要考虑静态优先级,也要考虑进程的属性。例如若是进程属于交互式进程,那么能够适当的调高它的优先级,使得界面反应地更加迅速,从而使用户获得更好的体验。Linux2.6 在这方面有了较大的提升。Linux2.6认为,交互式进程能够从平均睡眠时间这样一个measurement进行判断。进程过去的睡眠时间越多,则越有可能属于交互式进程。则系统调度时,会给该进程更多的奖励(bonus),以便该进程有更多的机会可以执行。奖励(bonus)从0到10不等。
系统会严格按照动态优先级高低的顺序安排进程执行。动态优先级高的进程进入非运行状态,或者时间片消耗完毕才会轮到动态优先级较低的进程执行。动态优先级的计算主要考虑两个因素:静态优先级,进程的平均睡眠时间也即bonus。计算公式以下,
dynamic_prio = max (100, min (static_prio - bonus + 5, 139))
在调度时,Linux2.6 使用了一个小小的trick,就是算法中经典的空间换时间的思想[还没对照源码确认],使得计算最优进程可以在O(1)的时间内完成。
为何根据睡眠和运行时间肯定奖惩分数是合理的
睡眠和CPU耗时反应了进程IO密集和CPU密集两大瞬时特色,不一样时期,一个进程可能便是CPU密集型也是IO密集型进程。对于表现为IO密集的进程,应该常常运行,但每次时间片不要太长。对于表现为CPU密集的进程,CPU不该该让其常常运行,但每次运行时间片要长。交互进程为例,假如以前其其大部分时间在于等待CPU,这时为了调高相应速度,就须要增长奖励分。另外一方面,若是此进程老是耗尽每次分配给它的时间片,为了对其余进程公平,就要增长这个进程的惩罚分数。能够参考CFS的virtutime机制.
现代方法CFS
再也不单纯依靠进程优先级绝对值,而是参考其绝对值,综合考虑全部进程的时间,给出当前调度时间单位内其应有的权重,也就是,每一个进程的权重X单位时间=应获cpu时间,可是这个应得的cpu时间不该过小(假设阈值为1ms),不然会由于切换得不偿失。可是,当进程足够多时候,确定有不少不一样权重的进程得到相同的时间——最低阈值1ms,因此,CFS只是近似彻底公平。
详情参考 《linux内核cfs浅析》
Linux进程状态机
进程是经过fork系列的系统调用(fork、clone、vfork)来建立的,内核(或内核模块)也能够经过kernel_thread函数建立内核进程。这些建立子进程的函数本质上都完成了相同的功能——将调用进程复制一份,获得子进程。(能够经过选项参数来决定各类资源是共享、仍是私有。)
那么既然调用进程处于TASK_RUNNING状态(不然,它若不是正在运行,又怎么进行调用?),则子进程默认也处于TASK_RUNNING状态。
另外,在系统调用clone和内核函数kernel_thread也接受CLONE_STOPPED选项,从而将子进程的初始状态置为 TASK_STOPPED。
进程建立后,状态可能发生一系列的变化,直到进程退出。而尽管进程状态有好几种,可是进程状态的变迁却只有两个方向——从TASK_RUNNING状态变为非TASK_RUNNING状态、或者从非TASK_RUNNING状态变为TASK_RUNNING状态。总之,TASK_RUNNING是必经之路,不可能两个非RUN状态直接转换。
也就是说,若是给一个TASK_INTERRUPTIBLE状态的进程发送SIGKILL信号,这个进程将先被唤醒(进入TASK_RUNNING状态),而后再响应SIGKILL信号而退出(变为TASK_DEAD状态)。并不会从TASK_INTERRUPTIBLE状态直接退出。
进程从非TASK_RUNNING状态变为TASK_RUNNING状态,是由别的进程(也多是中断处理程序)执行唤醒操做来实现的。执行唤醒的进程设置被唤醒进程的状态为TASK_RUNNING,而后将其task_struct结构加入到某个CPU的可执行队列中。因而被唤醒的进程将有机会被调度执行。
而进程从TASK_RUNNING状态变为非TASK_RUNNING状态,则有两种途径:
一、响应信号而进入TASK_STOPED状态、或TASK_DEAD状态;
二、执行系统调用主动进入TASK_INTERRUPTIBLE状态(如nanosleep系统调用)、或TASK_DEAD状态(如exit系统调用);或因为执行系统调用须要的资源得不到满 足,而进入TASK_INTERRUPTIBLE状态或TASK_UNINTERRUPTIBLE状态(如select系统调用)。
显然,这两种状况都只能发生在进程正在CPU上执行的状况下。
经过ps命令咱们可以查看到系统中存在的进程,以及它们的状态:
R(TASK_RUNNING),可执行状态。
只有在该状态的进程才可能在CPU上运行。而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出如今一个CPU的可执行队列中)。进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。
只要可执行队列不为空,其对应的CPU就不能偷懒,就要执行其中某个进程。通常称此时的CPU“忙碌”。对应的,CPU“空闲”就是指其对应的可执行队列为空,以至于CPU无事可作。
有人问,为何死循环程序会致使CPU占用高呢?由于死循环程序基本上老是处于TASK_RUNNING状态(进程处于可执行队列中)。除非一些很是极端状况(好比系统内存严重紧缺,致使进程的某些须要使用的页面被换出,而且在页面须要换入时又没法分配到内存……),不然这个进程不会睡眠。因此CPU的可执行队列老是不为空(至少有这么个进程存在),CPU也就不会“空闲”。
不少操做系统教科书将正在CPU上执行的进程定义为RUNNING状态、而将可执行可是还没有被调度执行的进程定义为READY状态,这两种状态在linux下统一为 TASK_RUNNING状态。
S(TASK_INTERRUPTIBLE),可中断的睡眠状态。
处于这个状态的进程由于等待某某事件的发生(好比等待socket链接、等待信号量),而被挂起。这些进程的task_struct结构被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其余进程触发),对应的等待队列中的一个或多个进程将被唤醒。
经过ps命令咱们会看到,通常状况下,进程列表中的绝大多数进程都处于TASK_INTERRUPTIBLE状态(除非机器的负载很高)。毕竟CPU就这么一两个,进程动辄几十上百个,若是不是绝大多数进程都在睡眠,CPU又怎么响应得过来。
D(TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
与TASK_INTERRUPTIBLE状态相似,进程处于睡眠状态,可是此刻进程是不可中断的。不可中断,指的并非CPU不响应外部硬件的中断,而是指进程不响应异步信号。
绝大多数状况下,进程处在睡眠状态时,老是应该可以响应异步信号的。不然你将惊奇的发现,kill -9居然杀不死一个正在睡眠的进程了!因而咱们也很好理解,为何ps命令看到的进程几乎不会出现TASK_UNINTERRUPTIBLE状态,而老是TASK_INTERRUPTIBLE状态。
而TASK_UNINTERRUPTIBLE状态存在的意义就在于,内核的某些处理流程是不能被打断的。若是响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),因而原有的流程就被中断了(参见《linux异步信号handle浅析》)。
在进程对某些硬件进行操做时(好比进程调用read系统调用对某个设备文件进行读操做,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能须要使用TASK_UNINTERRUPTIBLE状态对进程进行保护,以免进程与设备交互的过程被打断,形成设备陷入不可控的状态。(好比read系统调用触发了一次磁盘到用户空间的内存的DMA,若是DMA进行过程当中,进程因为响应信号而退出了,那么DMA正在访问的内存可能就要被释放了。)这种状况下的TASK_UNINTERRUPTIBLE状态老是很是短暂的,经过ps命令基本上不可能捕捉到。
linux系统中也存在容易捕捉的TASK_UNINTERRUPTIBLE状态。执行vfork系统调用后,父进程将进入TASK_UNINTERRUPTIBLE状态,直到子进程调用exit或exec。
经过下面的代码就能获得处于TASK_UNINTERRUPTIBLE状态的进程:
#include <unistd.h>
void main() {
if (!vfork()) sleep(100);
}
编译运行,而后ps一下:
kouu@kouu-one:~/test$ ps -ax | grep a\.out
4371 pts/0 D+ 0:00 ./a.out
4372 pts/0 S+ 0:00 ./a.out
4374 pts/1 S+ 0:00 grep a.out
而后咱们能够试验一下TASK_UNINTERRUPTIBLE状态的威力。无论kill仍是kill -9,这个TASK_UNINTERRUPTIBLE状态的父进程依然屹立不倒。
T(TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程自己处于TASK_UNINTERRUPTIBLE状态而不响应信号)。(SIGSTOP与SIGKILL信号同样,是很是强制的。不容许用户进程经过signal系列的系统调用从新设置对应的信号处理函数。)
向进程发送一个SIGCONT信号,可让其从TASK_STOPPED状态恢复到TASK_RUNNING状态。
当进程正在被跟踪时,它处于TASK_TRACED这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操做。好比在gdb中对被跟踪的进程下一个断点,进程在断点处停下来的时候就处于TASK_TRACED状态。而在其余时候,被跟踪的进程仍是处于前面提到的那些状态。
对于进程自己来讲,TASK_STOPPED和TASK_TRACED状态很相似,都是表示进程暂停下来。
而TASK_TRACED状态至关于在TASK_STOPPED之上多了一层保护,处于TASK_TRACED状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程经过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操做(经过ptrace系统调用的参数指定操做),或调试进程退出,被调试的进程才能恢复TASK_RUNNING状态。
Z(TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
进程在退出的过程当中,处于TASK_DEAD状态。
在这个退出过程当中,进程占有的全部资源将被回收,除了task_struct结构(以及少数资源)之外。因而进程就只剩下task_struct这么个空壳,故称为僵尸。
之因此保留task_struct,是由于task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程极可能会关心这些信息。好比在shell中,$?变量就保存了最后一个退出的前台进程的退出码,而这个退出码每每被做为if语句的判断条件。
固然,内核也能够将这些信息保存在别的地方,而将task_struct结构释放掉,以节省一些空间。可是使用task_struct结构更为方便,由于在内核中已经创建了从pid到task_struct查找关系,还有进程间的父子关系。释放掉task_struct,则须要创建一些新的数据结构,以便让父进程找到它的子进程的退出信息。
父进程能够经过wait系列的系统调用(如wait四、waitid)来等待某个或某些子进程的退出,并获取它的退出信息。而后wait系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉。
子进程在退出的过程当中,内核会给其父进程发送一个信号,通知父进程来“收尸”。这个信号默认是SIGCHLD,可是在经过clone系统调用建立子进程时,能够设置这个信号。
经过下面的代码可以制造一个EXIT_ZOMBIE状态的进程:
#include <unistd.h>
void main() {
if (fork())
while(1) sleep(100);
}
编译运行,而后ps一下:
kouu@kouu-one:~/test$ ps -ax | grep a\.out
10410 pts/0 S+ 0:00 ./a.out
10411 pts/0 Z+ 0:00 [a.out] <defunct>
10413 pts/1 S+ 0:00 grep a.out
只要父进程不退出,这个僵尸状态的子进程就一直存在。那么若是父进程退出了呢,谁又来给子进程“收尸”?
当进程退出的时候,会将它的全部子进程都托管给别的进程(使之成为别的进程的子进程)。托管给谁呢?多是退出进程所在进程组的下一个进程(若是存在的话),或者是1号进程。因此每一个进程、每时每刻都有父进程存在。除非它是1号进程。
1号进程,pid为1的进程,又称init进程。
linux系统启动后,第一个被建立的用户态进程就是init进程。它有两项使命:
一、执行系统初始化脚本,建立一系列的进程(它们都是init进程的子孙);
二、在一个死循环中等待其子进程的退出事件,并调用waitid系统调用来完成“收尸”工做;
init进程不会被暂停、也不会被杀死(这是由内核来保证的)。它在等待子进程退出的过程当中处于TASK_INTERRUPTIBLE状态,“收尸”过程当中则处于TASK_RUNNING状态。
X(TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。
而进程在退出过程当中也可能不会保留它的task_struct。好比这个进程是多线程程序中被detach过的进程(进程?线程?参见《linux线程浅析》)。或者父进程经过设置SIGCHLD信号的handler为SIG_IGN,显式的忽略了SIGCHLD信号。(这是posix的规定,尽管子进程的退出信号能够被设置为SIGCHLD之外的其余信号。)
此时,进程将被置于EXIT_DEAD退出状态,这意味着接下来的代码当即就会将该进程完全释放。因此EXIT_DEAD状态是很是短暂的,几乎不可能经过ps命令捕捉到。
一些重要的杂项
调度程序的效率
“优先级”明确了哪一个进程应该被调度执行,而调度程序还必需要关心效率问题。调度程序跟内核中的不少过程同样会频繁被执行,若是效率不济就会浪费不少CPU时间,致使系统性能降低。
在linux 2.4时,可执行状态的进程被挂在一个链表中。每次调度,调度程序须要扫描整个链表,以找出最优的那个进程来运行。复杂度为O(n);
在linux 2.6早期,可执行状态的进程被挂在N(N=140)个链表中,每个链表表明一个优先级,系统中支持多少个优先级就有多少个链表。每次调度,调度程序只须要从第一个不为空的链表中取出位于链表头的进程便可。这样就大大提升了调度程序的效率,复杂度为O(1);
在linux 2.6近期的版本中,可执行状态的进程按照优先级顺序被挂在一个红黑树(能够想象成平衡二叉树)中。每次调度,调度程序须要从树中找出优先级最高的进程。复杂度为O(logN)。
那么,为何从linux 2.6早期到近期linux 2.6版本,调度程序选择进程时的复杂度反而增长了呢?
这是由于,与此同时,调度程序对公平性的实现从上面提到的第一种思路改变为第二种思路(经过动态调整优先级实现)。而O(1)的算法是基于一组数目不大的链表来实现的,按个人理解,这使得优先级的取值范围很小(区分度很低),不能知足公平性的需求。而使用红黑树则对优先级的取值没有限制(能够用32位、64位、或更多位来表示优先级的值),而且O(logN)的复杂度也仍是很高效的。
调度触发的时机
调度的触发主要有以下几种状况:
一、当前进程(正在CPU上运行的进程)状态变为非可执行状态。
进程执行系统调用主动变为非可执行状态。好比执行nanosleep进入睡眠、执行exit退出、等等;
进程请求的资源得不到知足而被迫进入睡眠状态。好比执行read系统调用时,磁盘高速缓存里没有所须要的数据,从而睡眠等待磁盘IO;
进程响应信号而变为非可执行状态。好比响应SIGSTOP进入暂停状态、响应SIGKILL退出、等等;
二、抢占。进程运行时,非预期地被剥夺CPU的使用权。这又分两种状况:进程用完了时间片、或出现了优先级更高的进程。
优先级更高的进程受正在CPU上运行的进程的影响而被唤醒。如发送信号主动唤醒,或由于释放互斥对象(如释放锁)而被唤醒;
内核在响应时钟中断的过程当中,发现当前进程的时间片用完;
内核在响应中断的过程当中,发现优先级更高的进程所等待的外部资源的变为可用,从而将其唤醒。好比CPU收到网卡中断,内核处理该中断,发现某个socket可读,因而唤醒正在等待读这个socket的进程;再好比内核在处理时钟中断的过程当中,触发了定时器,从而唤醒对应的正在nanosleep系统调用中睡眠的进程;
内核抢占
理想状况下,只要知足“出现了优先级更高的进程”这个条件,当前进程就应该被马上抢占。可是,就像多线程程序须要用锁来保护临界区资源同样,内核中也存在不少这样的临界区,不大可能随时随地都能接收抢占。
linux 2.4时的设计就很是简单,内核不支持抢占。进程运行在内核态时(好比正在执行系统调用、正处于异常处理函数中),是不容许抢占的。必须等到返回用户态时才会触发调度(确切的说,是在返回用户态以前,内核会专门检查一下是否须要调度);
linux 2.6则实现了内核抢占,可是在不少地方仍是为了保护临界区资源而须要临时性的禁用内核抢占。
也有一些地方是出于效率考虑而禁用抢占,比较典型的是spin_lock。spin_lock是这样一种锁,若是请求加锁得不到知足(锁已被别的进程占有),则当前进程在一个死循环中不断检测锁的状态,直到锁被释放。
为何要这样忙等待呢?由于临界区很小,好比只保护“i+=j++;”这么一句。若是由于加锁失败而造成“睡眠-唤醒”这么个过程,就有些得不偿失了。
那么既然当前进程忙等待(不睡眠),谁又来释放锁呢?其实已获得锁的进程是运行在另外一个CPU上的,而且是禁用了内核抢占的。这个进程不会被其余进程抢占,因此等待锁的进程只有可能运行在别的CPU上。(若是只有一个CPU呢?那么就不可能存在等待锁的进程了。)
而若是不由用内核抢占呢?那么获得锁的进程将可能被抢占,因而可能好久都不会释放锁。因而,等待锁的进程可能就不知何年何月得偿所望了。
对于一些实时性要求更高的系统,则不能容忍spin_lock这样的东西。宁肯改用更费劲的“睡眠-唤醒”过程,也不能由于禁用抢占而让更高优先级的进程等待。好比,嵌入式实时linux montavista就是这么干的。
因而可知,实时并不表明高效。不少时候为了实现“实时”,仍是须要对性能作必定让步的。
多处理器下的负载均衡
前面咱们并无专门讨论多处理器对调度程序的影响,其实也没有什么特别的,就是在同一时刻能有多个进程并行地运行而已。那么,为何会有“多处理器负载均衡”这个事情呢?
若是系统中只有一个可执行队列,哪一个CPU空闲了就去队列中找一个最合适的进程来执行。这样不是很好很均衡吗?
的确如此,可是多处理器共用一个可执行队列会有一些问题。显然,每一个CPU在执行调度程序时都须要把队列锁起来,这会使得调度程序难以并行,可能致使系统性能降低。而若是每一个CPU对应一个可执行队列则不存在这样的问题。
另外,多个可执行队列还有一个好处。这使得一个进程在一段时间内老是在同一个CPU上执行,那么极可能这个CPU的各级cache中都缓存着这个进程的数据,颇有利于系统性能的提高。
因此,在linux下,每一个CPU都有着对应的可执行队列,而一个可执行状态的进程在同一时刻只能处于一个可执行队列中。
因而,“多处理器负载均衡”这个麻烦事情就来了。内核须要关注各个CPU可执行队列中的进程数目,在数目不均衡时作出适当调整。何时须要调整,以多大力度进程调整,这些都是内核须要关心的。固然,尽可能不要调整最好,毕竟调整起来又要耗CPU、又要锁可执行队列,代价仍是不小的。
另外,内核还得关心各个CPU的关系。两个CPU之间,多是相互独立的、多是共享cache的、甚至多是由同一个物理CPU经过超线程技术虚拟出来的……CPU之间的关系也是实现负载均衡的重要依据。关系越紧密,进程在它们之间迁移的代价就越小。参见《linux内核SMP负载均衡浅析》。
优先级继承
因为互斥,一个进程(设为A)可能由于等待进入临界区而睡眠。直到正在占有相应资源的进程(设为B)退出临界区,进程A才被唤醒。
可能存在这样的状况:A的优先级很是高,B的优先级很是低。B进入了临界区,可是却被其余优先级较高的进程(设为C)抢占了,而得不到运行,也就没法退出临界区。因而A也就没法被唤醒。
A有着很高的优先级,可是如今却沦落到跟B一块儿,被优先级并不过高的C抢占,致使执行被推迟。这种现象就叫作优先级反转。
出现这种现象是很不合理的。较好的应对措施是:当A开始等待B退出临界区时,B临时获得A的优先级(仍是假设A的优先级高于B),以便顺利完成处理过程,退出临界区。以后B的优先级恢复。这就是优先级继承的方法。
中断处理线程化在linux下,中断处理程序运行于一个不可调度的上下文中。从CPU响应硬件中断自动跳转到内核设定的中断处理程序去执行,到中断处理程序退出,整个过程是不能被抢占的。一个进程若是被抢占了,能够经过保存在它的进程控制块(task_struct)中的信息,在以后的某个时间恢复它的运行。而中断上下文则没有task_struct,被抢占了就无法恢复了。中断处理程序不能被抢占,也就意味着中断处理程序的“优先级”比任何进程都高(必须等中断处理程序完成了,进程才能被执行)。可是在实际的应用场景中,可能某些实时进程应该获得比中断处理程序更高的优先级。因而,一些实时性要求更高的系统就给中断处理程序赋予了task_struct以及优先级,使得它们在必要的时候可以被高优先级的进程抢占。可是显然,作这些工做是会给系统形成必定开销的,这也是为了实现“实时”而对性能作出的一种让步。