linux进程管理和调度大全

引用

  • Linux进程管理专题
  • Linux进程管理与调度-之-目录导航
  • Linux下0号进程的前世(init_task进程)今生(idle进程)----Linux进程的管理与调度(五)
  • 蜗窝科技-进程管理
  • 郭健: Linux进程调度技术的前世今生之“前世”
  • 郭健: Linux进程调度技术的前世今生之“今生”
  • 宋宝华:Linux的任督二脉——进程调度和内存管理
  • 宋宝华: Linux僵尸进程可以被“杀死”吗?
  • 宋宝华: 聊一聊进程深度睡眠的TASK_KILLABLE这个状态
  • 宋宝华: 关于Linux进程优先级数字混乱的彻底澄清
  • Linux进程优先级和nice值
  • Linux的进程线程及调度
  • Linux内核学习笔记(6)-- 进程优先级详解(prio、static_prio、normal_prio、rt_priority)

一. 进程基础知识

  • 进程:资源封装,是处于执行期的程序以及它所管理的资源(如 打开的文件,挂起的信号,进程状态,地址空间等)的总称。用PCB(Processing Control Block)来描述,在linux中,用 task_struct 结构体来描述。
  • 线程:调度单位。用thread_info来描述。
  • 进程内核栈:void *stack

    对每个进程,Linux内核都把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中:一个是内核态的进程堆栈,另一个是紧挨着进程描述符的小数据结构thread_info,叫做线程描述符。

  • Linux把thread_info(线程描述符)和内核态的线程堆栈存放在一起,这块区域通常是8192(占两个页框),其实地址必须是8192的整数倍。
  • 进程最常用的是进程描述符结构task_struct,而不是thread_info结构的地址。为了获取当前CPU上运行进程的task_struct结构,内核提供了current宏,由于task_struct *task在thread_info的起始位置,该宏本质上等价于current_thread_info()->task。
  • pid的数量是有限的,为 32768 (cat /proc/sys/kernel/pid_max)

linux进程管理和调度大全_第1张图片

linux进程管理和调度大全_第2张图片

  • 1.2 进程的各种状态,生命周期

linux进程管理和调度大全_第3张图片

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,只不过可以响应致命信号
  • 1.3 进程地址空间

    可用pmap查看

linux进程管理和调度大全_第4张图片

  • 1.4 内核线程

      用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.5 几个特殊的内核进程

linux进程管理和调度大全_第5张图片

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()来指定线程退出。

  • 1.6 内核抢占

      使用抢占式内核可以保证系统响应时间. 最高优先级的任务一旦就绪, 总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
缺点:不能直接使用不可重入型函数。(即需要考虑高低优先级线程之间相关数据的竞态情况,需要加锁保护)

抢占点:

1. 用户抢占:发生在用户空间的抢占现象

  • 当内核即将返回用户空间时, 内核会检查need_resched是否设置TIF_NEED_RESCHED, 如果设置, 则调用schedule(),此时,发生用户抢占.
  • 从中断(或异常)处理程序返回用户空间时;
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)来作为内核抢占的标记。

  • 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。(中断之后返回内核态时通过preempt_schedule_irq触发内核抢占)
  • 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数。(内核重新开启内核抢占时使用preempt_schedule检查内核抢占)
  • 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权。
  • 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权。

 

二. 进程调度

  • 吞吐 vs 响应:吞吐和响应之间的矛盾

     1. 响应:最小化某个任务的响应时间,哪怕牺牲其他任务为代价。

      2. 吞吐:全局视野,整个系统的workload被最大化处理。

  • I/O 消耗型 vs CPU消耗型

     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_CLASS

字段 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

  • RT调度策略和普通进程在调度算法上的差异

    Linux的RT调度策略和普通进程在调度算法上面有差异,RT的SCHED_FIFO和SCHED_RR采用的是一个bitmap:

linux进程管理和调度大全_第6张图片

每次从第0bit开始往后面搜索第一个有进程ready的bit,然后调度这个优先级上面的进程执行,所以,在内核里面,prio数值越小,优先级越高。但是,从用户态的API里面,则是数值越大,优先级越高。

下面的代码,一个线程通过调用API把自己设置为SCHED_FIFO,优先级50。

linux进程管理和调度大全_第7张图片

这个上面的50,对应内核的49 (从内核的视角上面来看,又会用99减去用户在chrt里面设置的优先级)。

如果我们把优先级设置为51:

linux进程管理和调度大全_第8张图片

这个51,对应内核bitmap上面的48。

所以,你会发现,从用户的视角来看,数值变大,优先级变高。

对于RT的进程而言,TOP的视角里面的 PR= -1 -用户视角。

linux进程管理和调度大全_第9张图片

注:只有最高优先级的RT进程,才在top里面显示为rt。(用户视角的99---内核bitmap视角的0

  • 普通进程的优先级 nice

     普通的讲nice的人相对来说比较简单,我们更关注它的nice值,-20~19之间,nice越低,优先级越高,权重越大,在CFS的红黑树左边的机会大。

linux进程管理和调度大全_第10张图片

你发现.nice为5的进程,在top命令显示PR是25。

下面我们看nice是-5的:

linux进程管理和调度大全_第11张图片

它显示的是PR=15。

由此大家可以发现规律,对于普通的采用CFS策略的NORMAL进程,top里面的 PR=20+NICE

linux进程管理和调度大全_第12张图片

由此发现,在top里面,RT策略的PR都显示为负数;最高优先级的RT,显示为rt。top命令里面也是,数字越小,优先级越高。

  • ps 中的RTPRIO, PRI,NI

linux进程管理和调度大全_第13张图片

一个是PRI,一个是NI,这到底是什么东西?相对而言,PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值。如前面所说,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。

到目前为止,更需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。

注:这里的PRI = 139 - 内核中prio。即该值越大,优先级越高。

  • rt的门限

linux进程管理和调度大全_第14张图片

  • CFS调度

linux进程管理和调度大全_第15张图片

linux进程管理和调度大全_第16张图片

 

  • 怎样修改进程优先级?

linux进程管理和调度大全_第17张图片

linux进程管理和调度大全_第18张图片

  • 实时进程调度

linux进程管理和调度大全_第19张图片

  • 非实时进程的调度和动态优先级

linux进程管理和调度大全_第20张图片

  • 怎样查看linux系统中的实时进程和普通进程?

    ps -eo state,uid,pid,ppid,rtprio,time,comm

    linux进程管理和调度大全_第21张图片

1. RTPRIO 

"-": 表示普通进程

"数字": 表示优先级为xx的实时进程

你可能感兴趣的:(linux系统开发)