调度机制的原理
进程和线程的调度都是由内核来完成的。操作系统内核包含一个调度器(scheduler),负责管理和调度所有进程和线程。调度器按照特定的调度策略和优先级规则来决定那个进程或线程应当获得CPU资源,进而实现多任务和并发执行。
调度过程分为以下几个步骤:
预选:调度器会从进程和线程列表中选择一个候选者,作为接出来要运行的执行单元。
上下文切换:调度器会将当前运行的进程或线程的状态(包括寄存器、程序计数器等)保存到内核数据结构中,之后将选取的候选者的状态加载到CPU中。
执行:选取的进程或线程开始运行。它会在用户态执行指令,直至发生某个风波(如时间片用完、系统调用、中断等),须要重新进行调度。
再度调度:当一个进程或线程难以继续运行时(比如,由于它须要等待I/O操作完成),调度器会再度执行预选和上下文切换手机linux操作系统,将另一个进程或线程调度到CPU上。
在整个过程中,线程会在用户态和内核态之间切换。当线程执行完一个时间片或碰到须要内核介入的风波时(如系统调用、中断处理等),它会切换到内核态,让调度器重新分配资源。调度器在内核态运行,可以确保对系统资源的控制和管理。
在多核处理器系统中,调度器可以同时调度多个进程或线程在不同的核心上运行,以实现真正的并行执行。反正,进程和线程的调度都是通过内核来完成的,内核调度器负责管理和分配CPU资源,以实现高效的多任务和并发执行。
查询当前进程或线程的资源使用情况
在Linux中,可以使用getrusage()系统调用查询当前进程或线程的资源使用情况,包括本次被调度的时间,另外,通过剖析/proc//sched文件中的调度统计信息linux内存管理,可以间接了解进程的调度情况。
getrusage()会返回一个rusage结构,其中包含了各类资源使用信息。以下是rusage结构的定义:
struct rusage {
struct timeval ru_utime; /* 用户态使用的CPU时间 */
struct timeval ru_stime; /* 内核态使用的CPU时间 */
// ...其他资源使用信息
};
getrusage()函数的原型如下:
int getrusage(int who, struct rusage *usage);
who参数可以是以下值之一:
要查询本次被调度的时间,可以将ru_utime和ru_stime的值相减,得到总的CPU时间。
调度优先级基本知识Linux进程调度的三种策略
SCHED_OTHER,分时调度策略
该策略是是默认的Linux分时调度(time-sharingscheduling)策略,它是Linux线程默认的调度策略。SCHED_OTHER策略的静态优先级总是为0,对于该策略列表上的线程,调度器是基于动态优先级(dynamicpriority)来调度的,动态优先级是跟nice中相关(nice值可以由插口nice,setpriority,sched_setattr来设置),该值会随着线程的运行时间而动态改变,以确保所有具有SCHED_OTHER策略的线程公正运行
SCHED_FIFO,实时调度策略,先到先服务。
按照进程的优先级进行调度,一旦占领到CPU则仍然运行,直至自己主动舍弃或被被更高优先级的进程占据;
SCHED_RR,实时调度策略,时间片轮转
在SCHED_FIFO的基础上,加上了时间片的概念。当一个进程占领到CPU以后,运行到一定的时间后,调度器会把这个进程置于CPU中,当前优先级进程队列的末尾,之后选择另一个相同优先级的进程来执行;
Linux线程优先级
进程
调度策略
优先级
说明
普通进程
SCHED_OTHER或SCHED_NORMAL
100-139
这个区间的优先级又称为静态优先级,不会随着时间而改变,内核不会更改它,只能通过系统调用nice去更改,静态优先级数值越大,进程的优先级越小,分配的基时间量就越少。普通进程几乎是难以分到时间片的(只能分到5%的CPU时间)。
staticpriority=nice+20+MAX_RT_PRIO,nice值[-20,19],MAX_RT_PRIO默认为100,这样做的用处是,任何内核态进程优先级都小于用户态的进程.;
实时进程
SCHED_FIFO或SCHED_RR
0-99
只有在下列风波之一发生时,实时进程就会被另外一个进程代替
1.进程被另外一个具有更高实时优先级的实时进程占据
2.进程执行了阻塞操作并步入睡眠
3.进程停止(处于TASK_STOPPED或TASK_TRACED状态)或被杀害
4.进程通过调用系统调用sched_yield(),自愿舍弃CPU5.进程基于时间片轮转的实时进程(SCHED_RR),并且用完了它的时间片.
这张图表示的是内核中的优先级,分为两段。
后面的数值0-99是实时任务,旁边的数值100-139是普通任务。
数值越低,代表这个任务的优先级越高。
以上是从内核角度来看的优先级。
我们在应用层创建线程的时侯,设置了一个优先级数值,这是从应用层角度来看的优先级数值。
然而内核并不会直接使用应用层设置的这个数值,而是经过了一定的运算,才得到内核中所使用的优先级数值(0~139)。
对于实时任务
我们在创建线程的时侯,可以通过下边这样的形式设置优先级数值(0~99):
struct sched_param param;
param.__sched_priority = xxx;
当创建线程函数步入内核层面的时侯,内核通过下边这个公式来估算真正的优先级数值:
kernelpriority=100-1-param.__sched_priority
假如应用层传入数值0,这么在内核中优先级数值就是99(100-1-0=99),在所有实时任务中,它的优先级是最低的。
假如应用层传输数值99,这么在内核中优先级数值就是0(100-1-99=0),在所有实时任务中,它的优先级是最高的。
因而linux判断线程是否在运行,从应用层的角度看,传输人优先级数值越大,线程的优先级就越高;数值越小,优先级就越低。
与内核角度是完全相反的!
对于普通任务
调整普通任务的优先级,是通过nice值来实现的,内核中也有一个公式来把应用层传入的nice值,转成内核角度的优先级数值:
kernelprifoity=100+20+nice
nice的合法数值是:-20~19。
假如应用层设置线程nice数值为-20,这么在内核中优先级数值就是100(100+20+(-20)=100)linux判断线程是否在运行,在所有的普通任务中,它的优先级是最高的。
假如应用层设置线程nice数值为19,这么在内核中优先级数值就是139(100+20+19=139),在所有的普通任务中,它的优先级是最低的。
因而,从应用层的角度看,传输人优先级数值越小,线程的优先级就越高;数值越大,优先级就越低。
与内核角度是完全相同的!
top中的PR和NI
top命令中pri的估算方式
说明
普通进程
top_pr=static_priority-100
static_priority取值是[100,139],所以top_pri取值是[0,39]
实时进程
top_pri=-1-real_time_priority
chrt命令就是更改实时进程的优先级,例如给进程12345分配优先级为93,chrt-p9312345,则top命令pri值显示的是-94。有的实时进程的pri值显示的是rt,没有具体显示数值
多核调度
对于多核处理器,分布不同核时,调度策略、优先级,都不起作用!(确切的说:调度策略和优先级,在线程所在的那种CPU中是起作用的)