引用
对每个进程,Linux内核都把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中:一个是内核态的进程堆栈,另一个是紧挨着进程描述符的小数据结构thread_info,叫做线程描述符。
5个互斥状态:state域能够取5个互为排斥的值(通俗一点就是这五个值任意两个不能一起使用,只能单独使用)。系统中的每个进程都必然处于以上所列进程状态中的一种。 | |
状态 | 描述 |
TASK_RUNNING | 表示进程要么正在执行,要么正要准备执行(已经就绪),正在等待cpu时间片的调度 |
TASK_INTERRUPTIBLE | 进程因为等待一些条件而被挂起(阻塞)而所处的状态。这些条件主要包括:硬中断、资源、一些信号……,一旦等待的条件成立,进程就会从该状态(阻塞)迅速转化成为就绪状态TASK_RUNNING |
TASK_UNINTERRUPTIBLE | 意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外,对于处于TASK_UNINTERRUPIBLE状态的进程,哪怕我们传递一个信号或者有一个外部中断都不能唤醒他们。只有它所等待的资源可用的时候,他才会被唤醒。这个标志很少用,但是并不代表没有任何用处,其实他的作用非常大,特别是对于驱动刺探相关的硬件过程很重要,这个刺探过程不能被一些其他的东西给中断,否则就会让进城进入不可预测的状态 |
TASK_STOPPED | 进程被停止执行,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态 |
TASK_TRACED | 表示进程被debugger等进程监视,进程执行被调试程序所停止,当一个进程被另外的进程所监视,每一个信号都会让进城进入该状态 |
2个终止状态:两个附加的进程状态既可以被添加到state域中,又可以被添加到exit_state域中。只有当进程终止的时候,才会达到这两种状态. | |
状态 | 描述 |
EXIT_ZOMBIE | 进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时进程成为僵尸进程 |
EXIT_DEAD | 进程的最终状态 |
TASK_KILLABLE:Linux 中的新进程状态(TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE) | |
状态 | 描述 |
TASK_KILLABLE | 当进程处于这种可以终止的新睡眠状态中,它的运行原理类似于 TASK_UNINTERRUPTIBLE,只不过可以响应致命信号 |
可用pmap查看
用ps查看线程时,名字为[..]这样的线程,都是内核线程。
例如:中断线程化使用的irq内核线程;软中断使用的内核线程ksoftirqd;以及work使用的kworker内核线程。
内核线程没有地址空间,所以task_struct->mm指针为NULL。
内核线程没有用户上下文。
内核线程只工作在内核空间,不会切换至用户空间。但内核线程同样是可调度且可抢占的。普通线程即可工作在内核空间,也可工作在用户空间。
内核线程只能访问3GB以上地址,而普通线程可访问所有4GB地址空间。
常见内核线程 | prio | policy |
irq | 49 | SCHED_FIFO |
softirq | 120 | SCHED_NORMAL |
worker | 120 | SCHED_NORMAL |
init | 120 | SCHED_NORMAL |
kthreadd | 120 | SCHED_NORMAL |
cfinteractive | 0 | SCHED_FIFO |
中断内核线程优先级很高,为49,并且使用了实时调度策略。softirq和worker都是普通内核线程。
init_workqueues中创建了绑定CPU0的两个kworker,分别是nice=0和nice=-20。apply_workqueue_attrs创建unbund worker,即kworker/uX:0。
其它特殊内核线程init优先级为120,kthreadd优先级为120.
cfinteractive优先级最高,主要处理CPU Frequency负载更新。
1. kernel_thread(): kernel_thread接口,使用该接口创建的线程,必须在该线程中调用daemonize()函数,这是因为只有当线程的父进程指向”Kthreadd”时,该线程才算是内核线程,而恰好daemonize()函数主要工作便是将该线程的父进程改成“kthreadd”内核线程;默认情况下,调用deamonize()后,会阻塞所有信号,如果想操作某个信号可以调用allow_signal()函数。
2. kthread_create(): kthread_create接口,则是标准的内核线程创建接口,只须调用该接口便可创建内核线程;默认创建的线程是存于不可运行的状态,所以需要在父进程中通过调用wake_up_process()函数来启动该线程。
3. kthread_run(): 创建并启动线程的函数; 线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。
4. kthread_should_stop(), kthread_stop(): 停止线程
5. kthread_should_park(), kthread_parkme(), kthread_park(),kthread_unpark(): 当在其他某个地方,调用 kthread_park(practice_task_p)后,线程将在kthread_parkme()处挂起睡眠,直到其他某个地方执行了kthread_unpark(practice_task_p)后,线程才被唤起,继续执行。
4. 内核线程的退出: 当线程执行到函数末尾时会自动调用内核中do_exit()函数来退出或其他线程调用kthread_stop()来指定线程退出。
使用抢占式内核可以保证系统响应时间. 最高优先级的任务一旦就绪, 总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
缺点:不能直接使用不可重入型函数。(即需要考虑高低优先级线程之间相关数据的竞态情况,需要加锁保护)
抢占点:
1. 用户抢占:发生在用户空间的抢占现象
1. 时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志; |
2. 信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的任务更改为就绪状态并设置need_resched标志。 |
3. 设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态; |
4. 改变任务的优先级时,可能会使高优先级的任务进入就绪状态; |
5. 新建一个任务时,可能会使高优先级的任务进入就绪状态; |
6. 对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行 |
2. 内核抢占:一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取代。linux内核通过在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter)来作为内核抢占的标记。
1. 响应:最小化某个任务的响应时间,哪怕牺牲其他任务为代价。
2. 吞吐:全局视野,整个系统的workload被最大化处理。
1. IO bound: CPU利用率低,进程的运行效率主要受限于I/O速度。
2. CPU bound:多数时间花在CPU上面(做运算)
int prio, static_prio, normal_prio; unsigned int rt_priority; |
|
字段 | 描述 |
static_prio | 用于保存静态优先级,可以通过nice系统调用来进行修改;(100 ~ 139) |
rt_priority | 用于保存实时优先级;0 - MAX_RT_PRIO-1 (0 - 99) |
normal_prio | 值取决于静态优先级和调度策略; |
prio | 用于保存动态优先级,调度器最终使用的。0 ~ 139(包括 0 和 139) |
1. prio动态优先级:prio 的值是调度器最终使用的优先级数值,即调度器选择一个进程时实际选择的值。prio 值越小,表明进程的优先级越高。prio 值的取值范围是 0 ~ MAX_PRIO,即 0 ~ 139(包括 0 和 139),根据调度策略的不同,又可以分为两个区间,其中区间 0 ~ 99 的属于实时进程,区间 100 ~139 的为非实时进程;当进程为实时进程时, prio 的值由实时优先级值(rt_priority)计算得来;当进程为非实时进程时,prio 的值由静态优先级值(static_prio)得来。
prio = MAX_RT_PRIO - 1 - rt_priority // 进程为实时进程
prio = static_prio // 进程为非实时进程
2. static_prio 静态优先级:静态优先级不会随时间改变,内核不会主动修改它,只能通过系统调用 nice 去修改 static_prio。
通过调用 NICE_TO_PRIO(nice) 来修改 static_prio 的值, static_prio 值的计算方法如下:
static_prio = MAX_RT_PRIO + nice +20
MAX_RT_PRIO 的值为100,nice 的范围是 -20 ~ +19,故 static_prio 值的范围是 100 ~ 139。 static_prio 的值越小,表明进程的静态优先级越高。
3. normal_prio归一化优先级:normal_prio 的值取决于静态优先级和调度策略,可以通过 _setscheduler 函数来设置 normal_prio 的值 。对于非实时进程,normal_prio 的值就等于静态优先级值 static_prio;对于实时进程,normal_prio = MAX_RT_PRIO-1 - p->rt_priority。
4. rt_priority实时优先级:rt_priority 值的范围是 0 ~ 99,只对实时进程有效。由式子:
prio = MAX_RT_PRIO-1 - p->rt_priority;
知道,rt_priority 值越大,则 prio 值越小,故 实时优先级(rt_priority)的值越大,意味着进程优先级越高。
rt_priority 的值也是取决于调度策略的,可以在 _setscheduler 函数中对 rt_priority 值进行设置。
字段 POLICY | 描述 | 所在调度器类 |
SCHED_NORMAL | (也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用 | CFS |
SCHED_BATCH | SCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配 CPU 运算资源。注意:这类进程比上述两类实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化 | CFS |
SCHED_IDLE | 优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者) | CFS |
SCHED_FIFO | 先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务 | RT |
SCHED_RR | 轮流调度算法(实时调度策略),后者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler()API 设置策略 | RT |
SCHED_DEADLINE | 新支持的实时调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法 | |
调度器类 SCHED_CLASS | 描述 | |
idle_sched_class | 每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump | |
stop_sched_class | 优先级最高的线程,会中断所有其他线程,且不会被其他任务打断。作用:1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration;2.HOTPLUG_CPU的情况下关闭任务。 | |
rt_sched_class | RT,作用:实时线程 | |
fair_sched_class | CFS(公平),作用:一般常规线程 |
目前系統中,Scheduling Class的优先级顺序为StopTask > RealTime > Fair > IdleTask
Linux的RT调度策略和普通进程在调度算法上面有差异,RT的SCHED_FIFO和SCHED_RR采用的是一个bitmap:
每次从第0bit开始往后面搜索第一个有进程ready的bit,然后调度这个优先级上面的进程执行,所以,在内核里面,prio数值越小,优先级越高。但是,从用户态的API里面,则是数值越大,优先级越高。
下面的代码,一个线程通过调用API把自己设置为SCHED_FIFO,优先级50。
这个上面的50,对应内核的49 (从内核的视角上面来看,又会用99减去用户在chrt里面设置的优先级)。
如果我们把优先级设置为51:
这个51,对应内核bitmap上面的48。
所以,你会发现,从用户的视角来看,数值变大,优先级变高。
对于RT的进程而言,TOP的视角里面的 PR= -1 -用户视角。
注:只有最高优先级的RT进程,才在top里面显示为rt。(用户视角的99---内核bitmap视角的0)
普通的讲nice的人相对来说比较简单,我们更关注它的nice值,-20~19之间,nice越低,优先级越高,权重越大,在CFS的红黑树左边的机会大。
你发现.nice为5的进程,在top命令显示PR是25。
下面我们看nice是-5的:
它显示的是PR=15。
由此大家可以发现规律,对于普通的采用CFS策略的NORMAL进程,top里面的 PR=20+NICE
由此发现,在top里面,RT策略的PR都显示为负数;最高优先级的RT,显示为rt。top命令里面也是,数字越小,优先级越高。
一个是PRI,一个是NI,这到底是什么东西?相对而言,PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值。如前面所说,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。
到目前为止,更需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
注:这里的PRI = 139 - 内核中prio。即该值越大,优先级越高。
1. RTPRIO
"-": 表示普通进程
"数字": 表示优先级为xx的实时进程