Linux的任务调度

发表于 2016-03-17 | 分类于 Linux内核 |

文章系原创,如需转载请注明:转载自”[Blog of UnicornX” (http://unicornx.github.io/)

最新更新于:2016-03-17

任务调度是操作系统内核的核心,我目前对它的认识也只能说是寥寥,但是聊胜于无,知道一点对理解内核的多任务处理机制,以及编写内核驱动还是有好处的。

将我目前理解的东西总结在这里,以后有新的认识再逐渐更新吧。

主要参考文章:

其他参考文章

一些别人写的课件

进程调度概述

  • 目标:最大限度地利用处理器时间
  • 基本任务:选择下一个要运行的进程
  • 核心问题:在可运行态进程之间分配有限的处理器时间,保证各进程公平,有效地使用处理器时间资源。
  • 调度的基本要求和难点:
    • 不同类型的进程有不同的调度需求。进程主要分为两种类型:”IO操作频繁型”和”计算密集型”
    • 响应时间 vs 系统开销:一方面对交互式应用要有良好的响应速度;一方面要减少调度器自身的开销,以增加花在执行程序上的时间。

任务,进程和线程的概念

任务的概念

在逻辑上表达一个顺序执行流,在操作系统中用于表达内核中可以独立调度的一个实体。

进程和线程

历史上先有进程的概念,然后才有线程的概念。它们都是历史上任务概念的具体实现。

现在Linux内核中一个任务对应的是一个线程,所以目前最新的内核中一个线程才对应着内核中可以独立调度的一个实体。进程的概念演变为线程组。

Linux内核中任务的组织方式

多个共享资源的线程构成线程组,线程组即进程。

进程之间资源不共享,地址空间独立。

进程之间有父子关系,线程之间无父子关系。父子进程之间组织为树状结构,多个子进程以链表的形式保存在父进程的task_struct结构体中。每个进程维护一个指向父进程的指针。

内核线程是完全运行在内核地址空间的任务,和用户空间没有关系。可以运行ps -eLf命令列出来的线程,如果CMD那一列的值被[]括起来了,就是内核线程。

进程状态

SCHED_NORMAL在POSIX中称为SCHED_OTHER,所以查man手册时都叫SCHED_OTHER,但查看linux的头文件中都叫SCHED_NORMAL,两个实际是一回事。

围绕任务的状态迁移,有另外一个组织方式。
在CFD调度算法中内核把所有TASK_RUNNING的任务组织为一棵红黑树rbtree。
内核中还存在多个队列,用来组织处于TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE的任务

任务类型和调度策略

内核把它所要处理的所有任务分为两种类型,普通任务和实时任务

三类调度

static const struct sched_class fair_sched_class
static const struct sched_class idle_sched_class
static const struct sched_class rt_sched_class

struct sched_class

策略

Linux支持三种进程调度策略,分别是SCHED_FIFO 、 SCHED_RR和SCHED_NORMAL。Linux支持两种类型的进程,实时进程和普通进程。实时进程可以采用SCHED_FIFO 和SCHED_RR调度策略;普通进程采用SCHED_NORMAL调度策略。

`man sched_setscheduler`

TASK_RUNNING任务的组织和管理

definesfilepath
runqueueskernel/sched.c
struct rqkernel/sched.c

对于调度器来说,只有任务进入TASK_RUNNING态才需要执行调度。TASK_RUNNING态的任务又分为两种,一种是尚未拥有CPU的就绪态,另外一种是拥有CPU的实际运行态。对一个CPU来说,实际正在其上运行的任务只有一个,记为CURRENT。

img

在多核系统中,每个CPU(此处指一个核心)对应一个全局变量per_cpu_runqueues,其数据结构为struct rq,该变量为调度的最顶层的数据结构。每个CPU对应一个rq结构体,所有rq结构体存放在runqueues数组中,有几个CPU数组长度就为几。

在这个数据结构中将系统中所有处于TASK_RUNNING状态的任务组织在一起。

static DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

内核将所有就绪的进程分为两大类,普通进程和实时进程。这个概念在代码上体现为:在struct rq结构中包含了两个重要的成员:cfs和rt,其数据结构为分别为struct cfs_rqstruct rt_rq。分别表示两个就绪队列:cfs就绪队列用于组织就绪的普通进程(这个队列上的进程用完全公平调度器进行调度);rt就绪队列用于用于组织就绪的实时进程(该队列上的进程用实时调度器调度)。

struct rq {
    ...
    struct cfs_rq cfs;
    struct rt_rq rt;
    ...
    struct task_struct *curr, *idle;
    ...
};

curr指向此CPU上的CURRENT进程,通常用cpu_curr(cpu)宏来访问。

其他状态的任务的组织管理

调度器原理概述

触发内核施行任务调度的触发点:

  • 显式调用:
    想到这种时机最为正常。一个进程显式调用调度函数schedule(),意味着它已经完成自己所需要做的事情,主动放弃对处理器的使用。这个“时间点”自然就是一个调度时机。

  • 系统调用
    有一些系统调用会产生调度时间发生,这些与调度相关的系统调度其实也很好去理解,凡是对task_struct中与调度相关的参数进行操作(例如改变优先级)的时候,这就意味着有调度的期望,所以这些调度函数返回(执行完操作)的时候就是一个必要的调度时机。下图列出了一些与调度相关的函数API,像nice()sched_getscheduler()sched_set_param()这样的函数在返回的时候就会进行重新调度(注:这些函数会引发调用不意味着绝对会被调度)。

  • 中断和时钟中断:
    中断——产生一个中断就很可能意味着发生了一件很紧急的事情,它很可能意味着出现了一个更高优先级的进程迫切需要执行,所以在中断返回的时候就需要考虑是否有进程需要进行调度。

    对于时钟中断,之所以把它单独提出讲,是因为它太重要了,在时钟中断的中断处理程序中存在一个周期性调度时机。

任务调度函数

任务调度与两个调度函数有关:scheduler_tick()schedule(),这两者分别被称作周期性调度器(或周期性调度函数)和主调度器(或主调度函数)。两者合在一起被称作通用调度器(或者核心调度器)

主调度器

在我们通常的概念中:调度器就负责将CPU使用权限从一个进程切换到另一个进程。完成这个工作的这其实就是Linux内核中所谓的主调度器。

在前面说到的触发内核施行任务调度的触发点中”显式调用”,”系统调用”和”中断返回”时就会调用主调度器函数schedule()

img

上图中,三种不同颜色的长条分别表示CPU分配给进程A、B、C的一小段执行时间,执行顺序是:A,B,C。竖直的虚线表示当前时间,也就是说;A已经在CPU上执行完CPU分配给它的时间,马上轮到B执行了。这时主调度器shedule就负责完成相关处理工作然后将CPU的使用权交给进程B。总之,主调度器的工作就是完成进程间的切换。

周期性调度器

在前面说到的触发内核施行任务调度的触发点中”时钟中断”就会调用周期性调度器scheduler_tick()

img

再来看看周期性调度器都干些什么吧。同样是刚才的那幅图,不过现在我们关注的不是从进程A切换到进程B这个过程,而是把A在CPU上执行的过程放大后观察细节。
在A享用它得到的CPU时间的过程中,系统会定时调用周期性调度器(即定时执行周期性调度函数scheduler_tick())。

在此版本的内核中,这个周期为10ms(这个10ms是这样得来的:内中定义了一个宏变量:HZ=100,它表示每秒钟周期性调度器执行的次数,那么时间间隔t=1/HZ=1/100s=10ms。10ms是个什么概念呢,我们粗略地计算一下:如果周期性调度程序每次执行100条指令,每秒执行100次,那么一秒钟周期性调度器在CPU上执行的指令就是1万条。如果主频为1GHz的处理器每秒钟执行10亿条指令,就相当于,周期性调度器消耗的CPU只占CPU总处理能力的 1万/10亿=10万分之一,微乎其微)。为了方便理解,上图将A获得的时间段分成长度为10ms的小片(注意:只是为了方便讲解,假想成这样的,内核并没有做这样的划分)。
周期性调度器每10ms执行一次,那它都干了些什么呢?

  • 首先它负责减少当前运行进程(CURRENT)的时间片计数。例如:进程A的结构体的成员sum_exec_runtime记录了A在CPU上运行的总时间,周期性调度器会更新该时间为:sum_exec_runtime+=10ms
  • 其次,它会在需要时设置need_resched标志。这会影响后续主调度器函数schedule()对该进程的处理,譬如抢占。
  • 在SMP机器中,该函数还要负责平衡多个处理器上的运行队列。
    总之,这个函数虽然称为周期性调度器,但它不负责实际的进程切换,它是为主调度器函数schedule()服务的。
    有关时钟中断处理程序的细节,可以参考LKD的11.5章节。

小结:
置need_resched标志。这会影响后续主调度器函数schedule()对该进程的处理,譬如抢占。

  • 在SMP机器中,该函数还要负责平衡多个处理器上的运行队列。
    总之,这个函数虽然称为周期性调度器,但它不负责实际的进程切换,它是为主调度器函数schedule()服务的。
    有关时钟中断处理程序的细节,可以参考LKD的11.5章节。

小结:
主调度器负责将CPU的使用权从一个进程切换到另一个进程。周期性调度器只是定时更新调度相关的统计信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值