手机门户网站,北京网站制作与建设公司,房屋设计软件免费下载,怎么制作美图素材图片引用Linux进程管理专题Linux进程管理与调度-之-目录导航Linux下0号进程的前世(init_task进程)今生(idle进程)----Linux进程的管理与调度#xff08;五#xff09;蜗窝科技-进程管理郭健#xff1a; Linux进程调度技术的前世今生之“前世”郭健#xff1a; Linux进程调度技术…引用Linux进程管理专题Linux进程管理与调度-之-目录导航Linux下0号进程的前世(init_task进程)今生(idle进程)----Linux进程的管理与调度五蜗窝科技-进程管理郭健 Linux进程调度技术的前世今生之“前世”郭健 Linux进程调度技术的前世今生之“今生”宋宝华Linux的任督二脉——进程调度和内存管理深度讲解Linux内存管理和Linux进程调度-打通任督二脉宋宝华: Linux僵尸进程可以被“杀死”吗宋宝华 聊一聊进程深度睡眠的TASK_KILLABLE这个状态宋宝华 关于Linux进程优先级数字混乱的彻底澄清Linux进程优先级和nice值Linux的进程线程及调度Linux内核学习笔记6-- 进程优先级详解prio、static_prio、normal_prio、rt_priority调度器简介以及Linux的调度策略理解Linux内核抢占模型最透彻一篇灵魂拷问之调度与切换十六问进程调度内核基础设施——per cpu变量 - Notes about linux and my work (laoqinren.net)#Linux进程管理 (qq.com)一. 进程基础知识1.1进程是资源的封装是处于执行期的程序以及它所管理的资源(如 打开的文件挂起的信号进程状态地址空间等)的总称。用 PCB(Processing Control Block) 来描述在linux中用 struct task_struct 结构体来描述。pid的数量是有限的为 32768 (cat /proc/sys/kernel/pid_max)1.2 线程是调度单位。用struct thread_infoe.g. arm64来描述 线程描述符该结构和进程的内核栈stack存放在同一个单独为进程分配的内存区域。由于这个内存区域同时保存了thread_info和stack所以使用了union 来定义。为什么需要thread_info?内核需要存储每个进程的PCB信息, linux内核是支持不同体系的的, 但是不同的体系结构可能进程需要存储的信息不尽相同, 这就需要我们实现一种通用的方式, 我们将体系结构相关的部分和无关的部门进行分离。用一种通用的方式来描述进程, 这就是struct task_struct, 而thread_info就保存了特定体系结构的汇编代码段需要访问的那部分进程的数据。进程最常用的是进程描述符结构task_struct而不是thread_info结构的地址。为了获取当前CPU上运行进程的task_struct 结构内核提供了current 宏。union thread_union {//x86: CONFIG_ARCH_TASK_STRUCT_ON_STACK is not set.
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACKstruct task_struct task;
#endif//x86,arm64: CONFIG_THREAD_INFO_IN_TASKy
#ifndef CONFIG_THREAD_INFO_IN_TASKstruct thread_info thread_info;
#endif//x86: 8k, x86_64: 16k//arm: 8k, arm64: 16k//必须是8192的整数倍。unsigned long stack[THREAD_SIZE/sizeof(long)];
};1.3 进程内核栈为什么需要内核栈因为进程在内核态运行时需要保持自己的栈信息。该栈不同于用户态的进程所用的栈。用户态进程所用的栈是在进程线性地址空间中。进程内核栈除了需要保存内核空间过程调用外还需要保存用户空间栈的数据和返回地址以便 在返回用户空间继续执行。进程通过syscall陷入内核时进行栈切换。1.4 内核栈stack struct thread_info, struct task_struct的关系经典关系 CONFIG_THREAD_INFO_IN_TASKn将thread_info存放在内核栈stack中。在thread_info中保存task_struct指针。新版关系CONFIG_THREAD_INFO_IN_TASKy将thread_info放到task_struct中。如何通过current宏找到当前的task_struct?传统做法通过寄存器(x86的esp寄存器, arm的sp栈栈顶寄存器)找到当前进程的内核栈顶然后找到thread_info然后thread_info中保存了task_struct的指针即就可以拿到当前进程的PCB了。新做法更高效x86使用了current_task这个每CPU变量来存储当前正在使用的cpu的进程描述符struct task_struct。x86上通用寄存器有限无法像ARM中那样单独拿出寄存器来存储进程描述符task_sturct结构的地址。由于采用了每cpu变量current_task来保存当前运行进程的task_struct所以在进程切换时就需要更新该变量。在arch/x86/kernel/process_64.c文件中的__switch_to函数中有如下代码来更新此全局变量arm64: 会通过保存到寄存器(sp_el0)中直接使用。1.2 进程的各种状态和生命周期上图中左侧为操作系统中通俗的进程三状态模型右侧为Linux对应的进程状态切换。每一个标志描述了进程的当前状态这些状态都是互斥的Linux中的 就绪态 和 运行态 对应的都是TASK_RUNNING 标志位就绪态 表示进程正处在队列中尚未被调度运行态 则表示进程正在CPU上运行5个互斥状态state域能够取5个互为排斥的值通俗一点就是这五个值任意两个不能一起使用只能单独使用。系统中的每个进程都必然处于以上所列进程状态中的一种。状态描述TASK_RUNNING表示进程要么正在执行要么正要准备执行已经就绪正在等待cpu时间片的调度TASK_INTERRUPTIBLE进程因为等待一些条件而被挂起阻塞而所处的状态。这些条件主要包括硬中断、资源、一些信号……一旦等待的条件成立进程就会从该状态阻塞迅速转化成为就绪状态TASK_RUNNINGTASK_UNINTERRUPTIBLE意义与TASK_INTERRUPTIBLE类似除了不能通过接受一个信号来唤醒以外对于处于TASK_UNINTERRUPIBLE状态的进程哪怕我们传递一个信号或者有一个外部中断都不能唤醒他们。只有它所等待的资源可用的时候他才会被唤醒。这个标志很少用但是并不代表没有任何用处其实他的作用非常大特别是对于驱动刺探相关的硬件过程很重要这个刺探过程不能被一些其他的东西给中断否则就会让进城进入不可预测的状态TASK_STOPPED进程被停止执行当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态TASK_TRACED表示进程被debugger等进程监视进程执行被调试程序所停止当一个进程被另外的进程所监视每一个信号都会让进城进入该状态2个终止状态两个附加的进程状态既可以被添加到state域中又可以被添加到exit_state域中。只有当进程终止的时候才会达到这两种状态.状态描述EXIT_ZOMBIE进程的执行被终止但是其父进程还没有使用wait()等系统调用来获知它的终止信息此时进程成为僵尸进程EXIT_DEAD进程的最终状态TASK_KILLABLELinux 中的新进程状态(TASK_UNINTERRUPTIBLE TASK_WAKEKILL TASK_KILLABLE)状态描述TASK_KILLABLE当进程处于这种可以终止的新睡眠状态中它的运行原理类似于 TASK_UNINTERRUPTIBLE只不过可以响应致命信号1.3 进程地址空间 可用pmap查看1.4 内核线程用ps查看线程时名字为 [..] 这样的线程都是内核线程。例如中断线程化使用的irq内核线程软中断使用的内核线程ksoftirqd以及work使用的kworker内核线程。内核线程没有地址空间所以task_struct-mm指针为NULL。内核线程没有用户上下文。内核线程只工作在内核空间不会切换至用户空间。但内核线程同样是可调度且可抢占的。普通线程即可工作在内核空间也可工作在用户空间。内核线程只能访问3GB以上内核地址空间而普通线程可访问所有4GB地址空间。常见内核线程priopolicyirq49SCHED_FIFOsoftirq120SCHED_NORMALworker120SCHED_NORMALinit120SCHED_NORMALkthreadd120SCHED_NORMALcfinteractive0SCHED_FIFO中断内核线程优先级很高为49并且使用了实时调度策略。softirq和worker都是普通内核线程。init_workqueues中创建了绑定CPU0的两个kworker_pool分别是nice0和nice-20。apply_workqueue_attrs创建unbund worker_pool即kworker/uX:0也有两个attr分别是nice0和nice-20其它特殊内核线程init优先级为120kthreadd优先级为120.cfinteractive优先级最高主要处理CPU Frequency负载更新。1.4.1几个特殊的内核进程 1.4.2内核线程相关APIkernel_thread()kernel_thread接口使用该接口创建的线程必须在该线程中调用daemonize()函数这是因为只有当线程的父进程指向”Kthreadd”时该线程才算是内核线程而恰好daemonize()函数主要工作便是将该线程的父进程改成“kthreadd”内核线程默认情况下调用deamonize()后会阻塞所有信号如果想操作某个信号可以调用allow_signal()函数。kthread_create()kthread_create接口则是标准的内核线程创建接口只须调用该接口便可创建内核线程默认创建的线程是存于不可运行的状态所以需要在父进程中通过调用wake_up_process()函数来启动该线程。kthread_run()创建并启动线程的函数; 线程一旦启动起来后会一直运行除非该线程主动调用do_exit函数或者其他的进程调用kthread_stop函数结束线程的运行。kthread_should_stop() kthread_stop()停止线程kthread_should_park(), kthread_parkme(), kthread_park()kthread_unpark()当在其他某个地方调用 kthread_park(practice_task_p)后线程将在kthread_parkme()处挂起睡眠直到其他某个地方执行了kthread_unpark(practice_task_p)后线程才被唤起继续执行。do_exit()当线程执行到函数末尾时会自动调用内核中do_exit()函数来退出或其他线程调用kthread_stop()来指定线程退出。1.6 抢占 使用抢占式内核可以保证系统响应时间. 最高优先级的任务一旦就绪, 总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就会被剥夺或者说被挂起了那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态中断完成时中断了的任务被挂起优先级高的那个任务开始运行。缺点不能直接使用不可重入型函数。(即需要考虑高低优先级线程之间相关数据的竞态情况需要加锁保护)1.6.1 抢占触发点不管是用户抢占还是内核抢占抢占触发点是一样的。内核提供了set_tsk_need_resched() 函数来将 thread_info中flag字段设置成TIF_NEED_RESCHED设置了TIF_NEED_RESCHED 标志表明需要发生抢占调度1信号量、等到队列、completion等机制唤醒时都是基于waitqueue的而waitqueue的唤醒函数为default_wake_function()其调用try_to_wake_up() 将被唤醒的任务更改为就绪状态并设置 need_resched 标志。2时钟中断处理例程检查当前任务的时间片当任务的时间片消耗完时scheduler_tick() 函数就会设置 need_resched 标志3新建一个任务时可能会使高优先级的任务进入就绪状态4对CPU(SMP)进行负载均衡时当前任务可能需要放到另外一个CPU上运行5设置用户进程的nice值时可能会使高优先级的任务进入就绪状态6改变任务的优先级时可能会使高优先级的任务进入就绪状态1.6.2 抢占执行点用户抢占抢占执行发生在进程处于用户态。抢占的执行最明显的标志就是调用了schedule() 函数来完成任务的切换。具体来说在用户态执行抢占在以下几种情况异常处理后返回到用户态中断处理后返回到用户态系统调用后返回到用户态内核抢占抢占执行发生在进程处于内核态。Linux内核有三种内核抢占模型CONFIG_PREEMPT_NONE不支持内核抢占中断退出后需要等到低优先级任务主动让出CPU才发生抢占切换一般服务器选择这种策略CONFIG_PREEMPT_VOLUNTARY支持内核自愿抢占即在一些耗时的routine中增加抢占点提高实时性在中断退出后遇到抢占点时进行抢占切换一般桌面系统选择这种策略CONFIG_PREEMPT支持内核抢占当中断退出后如果遇到了更高优先级的任务立即进行任务抢占有实时响应的内核选择这种策略抢占执行点中断执行完毕后进行抢占调度主动调用preemp_enable() 或schedule() 等接口的地方进行抢占调度二. 进程调度2.1吞吐 vs 响应吞吐和响应之间的矛盾响应最小化某个任务的响应时间哪怕牺牲其他任务为代价。吞吐全局视野整个系统的workload被最大化处理。2.2 I/O 消耗型 vs CPU消耗型IO bound: CPU利用率低进程的运行效率主要受限于I/O速度。CPU bound多数时间花在CPU上面(做运算)2.3 优先级linux系统中有多种优先级下面是其关系优先级字段描述static_prio用于保存静态优先级可以通过nice系统调用来进行修改100 ~ 139rt_priority用于保存实时优先级0 - MAX_RT_PRIO-1 (0 - 99)normal_prio值取决于静态优先级和调度策略prio用于保存动态优先级调度器最终使用的。0 ~ 139包括 0 和 1392.3.1 prio动态优先级prio 的值是调度器最终使用的优先级数值即调度器选择一个进程时实际选择的值。prio 值越小表明进程的优先级越高。prio 值的取值范围是 0 ~ MAX_PRIO即 0 ~ 139包括 0 和 139根据调度策略的不同又可以分为两个区间其中区间 0 ~ 99 的属于实时进程区间 100 ~139 的为非实时进程。当进程为实时进程时 prio 的值由实时优先级值rt_priority计算得来prio MAX_RT_PRIO - 1 - rt_priority // 进程为实时进程当进程为非实时进程时prio 的值由静态优先级值static_prio得来。prio static_prio // 进程为非实时进程2.3.2 static_prio 静态优先级静态优先级不会随时间改变内核不会主动修改它只能通过系统调用 nice 去修改 static_prio。通过调用 NICE_TO_PRIO(nice) 来修改 static_prio 的值 static_prio 值的计算方法如下static_prio MAX_RT_PRIO nice 20MAX_RT_PRIO 的值为100nice 的范围是 -20 ~ 19故 static_prio 值的范围是 100 ~ 139。 static_prio 的值越小表明进程的静态优先级越高。2.3.3 normal_prio归一化优先级normal_prio 的值取决于静态优先级和调度策略可以通过 _setscheduler() 函数来设置 normal_prio 的值 。对于非实时进程normal_prio static_prio对于实时进程normal_prio MAX_RT_PRIO-1 - p-rt_priority。 2.3.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 值进行设置。2.4 调度器类SCHED_CLASS和调度策略 policy所谓调度就是按照某种调度的算法从进程的就绪队列中选取进程分配CPU主要是协调对CPU等的资源使用。进程调度的目标是最大限度利用CPU时间。2.4.1调度器类内核默认提供了5个调度器Linux内核使用struct sched_class来对调度器进行抽象。目前系統中,Scheduling Class的优先级顺序为stop_sched_class dl_sched_class rt_sched_class cfs_sched_class idle_sched_class调度器类 SCHED_CLASS描述Stop调度器stop_sched_class优先级最高的调度类可以抢占其他所有进程不能被其他进程抢占作用1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration2.HOTPLUG_CPU的情况下关闭任务。Deadline调度器dl_sched_class使用红黑树把进程按照绝对截止期限进行排序选择最小进程进行调度运行RT调度器rt_sched_class实时调度器为每个优先级维护一个队列CFS调度器cfs_sched_class完全公平调度器采用完全公平调度算法引入虚拟运行时间概念IDLE-Task调度器idle_sched_class空闲调度器每个CPU都会有一个idle线程当没有其他进程可以调度时调度运行idle线程2.4.2 调度策略Linux内核提供了一些调度策略供用户程序来选择调度器其中Stop调度器和IDLE-Task调度器仅由内核使用用户无法进行选择。字段 POLICY描述所在调度器类SCHED_DEADLINE限期进程调度策略使task选择Deadline调度器来调度运行DeadlineSCHED_RR实时进程调度策略时间片轮转进程用完时间片后加入优先级对应运行队列的尾部把CPU让给同优先级的其他进程RTSCHED_FIFO实时进程调度策略先进先出调度没有时间片没有更高优先级的情况下只能等待主动让出CPURTSCHEE_NORMAL普通进程调度策略使task选择CFS调度器来调度运行CFSSCHED_BATCH普通进程调度策略批量处理使task选择CFS调度器来调度运行CFSSCHED_IDLE普通进程调度策略使task以最低优先级选择CFS调度器来调度运行CFS2.5 RT调度策略和普通进程在调度算法上的差异 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视角的02.6 普通进程的优先级 nice 普通的讲nice的人相对来说比较简单我们更关注它的nice值-20~19之间nice越低优先级越高权重越大在CFS的红黑树左边的机会大。你发现.nice为5的进程在top命令显示PR是25。下面我们看nice是-5的:它显示的是PR15。由此大家可以发现规律,对于普通的采用CFS策略的NORMAL进程top里面的 PR20NICE由此发现在top里面RT策略的PR都显示为负数最高优先级的RT显示为rt。top命令里面也是数字越小优先级越高。2.7 ps 中的RTPRIO, PRINI一个是PRI一个是NI这到底是什么东西相对而言PRI也还是比较好理解的即进程的优先级或者通俗点说就是程序被CPU执行的先后顺序此值越小进程的优先级别越高。那NI呢就是我们所要说的nice值了其表示进程可被执行的优先级的修正数值。如前面所说PRI值越小越快被执行那么加入nice值后将会使得PRI变为PRI(new)PRI(old)nice。这样当nice值为负值的时候那么该程序将会优先级值将变小即其优先级会变高则其越快被执行。到目前为止更需要强调一点的是进程的nice值不是进程的优先级他们不是一个概念但是进程nice值会影响到进程的优先级变化。注这里的PRI 139 - 内核中prio。即该值越大优先级越高。2.8 rt的门限2.9 CFS调度2.10 怎样修改进程优先级2.10.1 实时进程调度2.10.2 非实时进程的调度和动态优先级 2.11 怎样查看linux系统中的实时进程和普通进程ps -eo state,uid,pid,ppid,rtprio,time,commRTPRIO -: 表示普通进程数字: 表示优先级为xx的实时进程