Linux进程调度学习笔记

时间:2023-01-10 15:46:20

1. Linux进程调度的时机

     Linux进程的调度时机大致分为两种情况:一种是进程自愿调度;另一种是发生强制性调度。
     首先,自愿的调度随时都可以进行。在内核空间中,进程可以通过schedule()启动一次调度;在用户空间中,可以通过系统调用pause()达到同样的目的。如果要为自愿的暂停行为加上时间限制,在内核中使用schedule_timeout(),而在用户空间则使用nanosleep()系统调用。
      Linux中,强制性的调度发生在每次从系统调用返回的前夕,以及每次中断或异常处理返回用户空间的前夕(用户抢占,除此之外,还有内核抢占,此处不做介绍)。应注意的是,从内核态返回到用户态是进程调度发生的必要条件,而不是充分条件, 还要取决于当前进程task_struct结构中的need_ resched是否为1。单CPU条件下,有三种情况可以使当前进程的need_resched置为1 。
     在时钟中断服务程序中,发现当前进程运行的时间片已经结束;当唤醒一个比当前进程优先级权值更高的进程;当一个进程通过系统调用改变调度策略立即引起调度时,比如系统调用sched_ setscheduler()。
     从进程调度的时机可以看出,Linux内核的调度方式为“有条件的剥夺方式”旧。当进程在用户空间运行,不管自愿不自愿,一旦有必要(比如时间片用完),内核就可以暂时剥夺其运行而调度其他进程运行。而进程一旦进入内核空间,即进入核心态时,尽管知道应该要调度了,但实际上却不会发生,一直要到该进程返回到用户空间前夕才能剥夺其运行。

2. Linux进程的优先级权值

     Linux用函数goodness()统一计算进程(包括普通进程和实时进程)的优先级权值,该权值衡量一个处于可运行状态的进程值得运行的程度,权值越大,进程优先级越高。
     每个进程的task_struct结构中,与godness()计算权值相关的域有以下四项:policy、nice(2.2版内核该项为priority)、counter、rt_ priority。
     其中,
     policy 是进程的调度策略,其可用来区分实时进程和普通进程,实时进程优先于普通进程运行。
     nice 从最初的UNIX沿用而来,表示进程的静态负向优先级,其取值范围为19—-20,以-20优先级最高。
     counter 表示进程剩余的时间片计数值,由于counter在计算godness()时起重要作用,因此,counter也可以看作是进程的动态优先级。
     rt_priority 是实时进程特有的,表示实时优先级,其取值范围为1-99。

3. Linux进程调度策略

     首先,Linux根据调度策略policy从整体上区分实时进程和普通进程。
     Linux支持SCHED—RR、SCHED—FIFO和SCHED—OTHER的调度策略(VxWorks只支持前两种策略,不支持SCHED_OTHER策略)。另外,Linux实时进程和普通进程可以互相转换,通过SCHED_OTHER 参数调用sched_ setscheduler()函数,可以将Linux实时进程转化为普通进程。通过以SCHEDl-RR或SCHED FIFO 为参数调用sched_ setscheduler()也可以将普通进程转为实时进程。(VxWorks下改变调度策略的唯一手段是通过kernelTimeSlice()函数设置轮转调度的时间片长度来实现的,该时间片长度设为0时。即取消了SCHED—RR策略。)
     Linux中的调度策略是基于进程的,某个进程调度策略的设置不影响其他进程。VxWorks的任务调度策略不是基于某个任务的,而是针对整个系统的所有任务。
  
     对于policy为SCHED_OTHER的普通进程,Linux采用动态优先级调度,其优先级权值取决于(20-nice)和进程当前的剩余时间片计数counter之和。
     进程创建时,子进程继承父进程的nice值,而父进程的counter值则被分为二半,子进程和父进程各得一半。
     时间片计数器counter每次清零后由(20一nice)经过换算重新赋值。字面上看,nice是“优先级”、counter是“计数器”的意思,然而实际上,它们表达的是同一个意思:nice决定了分配给该进程的时间片计数,nice优先级越高的进程分到的时间片越长,用户通过系统调用nice()或setpriority()改变进程静态优先级nice值的同时,也改变了该进程的时间片长度;counter表示该进程剩余的时间片计数值,而nice和counter综合起来又决定进程可运行的优先级权值。在进程运行过程中,counter不断减少,而nice保持相对不变;当一个普通进程的时间片用完以后,并不马上根据nice对counter进行重新赋值,只有所有处于可运行状态的普通进程的时间片都用完了以后(counter等于0),才根据nice对counter重新赋值,这个普通进程才有了再次被调度的机会。这说明,普通进程运行过程中,counter的减小给了其它进程得以运行的机会,直至counter减为0时才完全放弃对CPU的使用,这就相当于优先级在动态变化,所以称之为动态优先调度。  
  
     对于实时进程,Linux采用了两种调度策略,即SCHED_FlFO(先来先服务调度)和SCHED_RR(时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,Linux采用了一个比较固定的标准,即参考rL-priority的值。
     Linux用函数godness()计算进程的优先级权值时,对实时进程是在1000的基础上加上rt_priority的值,而非实时进程的动态优先级综合起来的调度权值始终在1Oo0以下,所以goodness()的优先级权值计算方法确保实时进程的调度权值始终比所有的非实时进程都要大,这就保证了实时进程的优先运行。
     实时进程的counter与nice都与其优先级权值无关,这和普通进程是有区别的,实时进程task_struct中的nice和counter只与SCHED—RR调度策略进程的时间片计数相关;而对于SCHED—FIFO调度策略的实时进程没有调度的参考意义。

【总结】

Linux进程调度学习笔记Linux进程调度学习笔记

4. 其他问题

4.1 系统调用&内核线程
 【描述一】

    mm_struct 数据结构是描述内存存储信息的数据结构,进程控制块task_struct中用mm指针指想mm_struct数据结构。也就是在进程的属性中通过mm指针来管理起对应的内存区。
    mm_struct *active_mm 内核线程用来指向调用它的普通进程的内存地址空间。当普通进程在运行时如果发生系统调用,程序就会从用户态转为内核态,内核态中执行的是内核线程,内核线程没有内存空间地址结构mm_struct。当它需要内存空间地址的时候,就会调用用户态对应进程的用户空间地址结构mm_struct。内核线程就是就是通过active_mm指针来指向用户态进程的mm_struct结构。
 【描述二】
    内核线程可以理解成在内核中运行的特殊进程,它有自己的“进程上下文”(借用调用它的用户进程的上下文),所以同样被进程调度程序调度,也可以睡眠——它和用户进程属性何其相似,不同之处就在于内核线程运行于内核空间,可访问内核数据,运行期间不能被抢占。(个人理解:Linux 2.6后内核可抢占,中断返回等情况有可能发生调度)
 【分析】

    普通进程发生系统调用确实是从用户态转入内核态,但它还是普通进程,并不是什么内核线程,内核线程也确实只能在内核态中执行。普通进程在内核态运行时不管因为何种原因都有可能交出CPU使用权(主动睡眠或被抢占),接替它的进程就可以是内核线程(内核中对线程或进程实际上是等同视之的,调度也一样),这个时候就出现了这么一个active_mm的指针用来指向交出CPU使用权的普通线程的mm_struct结构,这个指针有一个非常重要的作用——延迟刷TLB,幸运的话,甚至可以不刷。这是一个性能上的优化,因为本内核线程执行完之后,原来的普通线程可以有机会不用再填充TLB。

    系统调用实际上是应用程序在用户空间激起了一次软中断,在软中断之前要按照规范,将各个需要传递的参数填入到相应的寄存器中。软中断会激起内核的异常处理,此时就会强制陷入内核态(此时cpu运行权限提升),软中断的异常处理函数会根据应用软件的请求来决定api调用是否合法,如果合法选择需要执行的函数,执行完毕后软中断会填入返回值,安全地降低cpu权限,将控制权交还给用户空间。所以内核提供的api调用,你完全可以认为就是一个软件包,只不过这些软件包你不能控制,只能请求内核帮你执行。
    因为内核态和用户态属于同一个进程,所以不存在同一进程内内核态阻塞用户态这种说法,只能是进程是否在内核态执行了阻塞操作而被阻塞。

    Linux进程调度学习笔记