Linux进程(七):调度



  所有的操作系统,在设计调度器的时候,都是朝着两个目标去的:1、缩小响应时间:最小化某个任务的响应时间,哪怕牺牲其他的任务为代价。2、提高吞吐率:使整个系统的 workload被最大化处理。但是这两个目标往往不可兼得,提高响应可能将会降低吞吐率,而提高吞吐率响应时间又将变长。因为吞吐率提高表示着资源都花费在了做有用功上,而缩小响应时间所产生的CPU抢占势必将减少吞吐率(此处不仅仅是上下文切换的时间,还包括CPU的 cache miss之后所发生=的一系列处理)。

调度模型

​   在Linux内核的编译选项中,我们可以选择操作系统的进程调度模型(Preemption Model),在Linux中一般分为以下三种:

  • No Forced Preemption (Server)

  • Voluntary Kernel Preemption (Desktop)

  • Preemption Kernel (Low-Latency Desktop)

​   这三种选择将影响到Linux中的调度算法。第一种表示不强制抢占、第二种表示不允许内核抢占、第三种表示允许内核抢占。这三种调度模型中抢占强度由低到高,这意味着从相应速度来看这三种模型也是从低到高,同时吞吐率则为由高到低。因此在一些需要高吞吐率的环境中我们一般使用第一种抢占模型(例如服务器),而在一些需要高相应速度的系统中我们一般使用第三种抢占模型(例如手机或桌面电脑)。

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

​   在操作系统中的进程可分为I/O消耗型和CPU消耗型:

  • I/O消耗型的进程CPU利用率低,进程的运行效率主要受限于I/O速度。

  • CPU消耗型的进程多数时间花在CPU上做运算。

  I/O消耗型的任务主要关注与系统是否能够及时把该任务调度到,能够及时拿到CPU。所以在一个典型的计算机操作系统中,一般I/O消耗型任务都会比CPU消耗型任务优先调度。并且I/O消耗型任务通常都伴随着用户体验(比如说鼠标输入就是I/O消耗型任务,一直等待鼠标的输入,鼠标一有I/O输入必须立即调度并执行响应,不然用户将会感觉到卡顿)。

​   所以一般I/O消耗型任务对于拿到的CPU性能好坏并不是特别敏感,而对于是否能及时获取到CPU极为敏感。在此基础上,ARM研发了一套big.LITTLE CPU架构:几个负责运算的高功耗CPU专门负责CPU消耗型任务的处理,而几个低功耗的CPU则只负责I/O消耗型任务的处理。同时在系统的进程调度中,将I/O消耗型的任务统一调度到低功耗CPU上,这样可以通过这种CPU架构利用小功耗达到高性能效果。

调度器实现

早期2.6:优先级数组和bitmaps

  早期的Linux2.6的调度器在Linux内核空间中把整个Linux的优先级划分为0-139,并且在内核空间中数值越小,优先级越高(但是在用户空间设置进程的优先级的时候时反着设置的,例如我们在用户空间设置进程的优先级为50,在Linux内核中会把99-40最终得到的59设置为该进程在内核空间中的优先级,所以在用户空间中设置的优先级数值越大优先级越高)。注意,100-139这个优先级的进程不会抢占CPU,只有走到0-99优先级的进程无进程可调度之后才会调度100-139优先级(也就是用户空间-20~19优先级)的进程。在每一次调度的时候内核从第0位开始查看哪一位上有进程可被调度,若检测到可被调度的进程则将其置为运行态。

在这里插入图片描述

RT调度策略:
  • SCHED_FIFO:不同优先级按照优先级高的先跑直到睡眠,优先级低的在跑;同等优先级先进先出。
  • SCHED_RR:不同优先级按照优先级高的先跑直到睡眠,优先级低的再跑,同等优先级轮转(RR - RoundRobin)。

​   普通进程(100 - 139优先级的进程)将在不同优先级直接进行轮转,但是不会等到sleepwait之后再释放CPU,而是CPU会一直在这些进程中轮转。此时优先级高的优势在于:1、可以得到更多的时间片。2、当从睡眠中被唤醒时可以抢占优先级低的进程,然后再进行轮转。

​   此处普通进程的nice值(-20~19)是会被操作系统不停更改的:一个进程睡眠次数越多,nice值将越低,优先级越高。而睡眠次数少的进程nice值将变高,优先级降低。这是为了时I/O消耗型任务能够优先获取到CPU而提高响应速度。

  为了防止RT任务(0-99优先级)一直占用CPU而普通任务得不到执行的机会,Linux后期加入了/proc/sys/kernel/sched_rt_period_us/proc/sys/kernel/sched_rt_runtime_us,限制了在一个时间周期内RT进程所占用的时间比例。

schedule normal调度算法

CFS:完全公平调度

在这里插入图片描述

​   完全公平调度算法利用了一颗红黑树,节点中存放的值为进程运行到目前为止的vruntime。每一次Linux调度都调度当前vruntime最小的task_struct。其中vruntime = runtime * (NICE_0_LOAD / nice_n_weight)。而nice_n_weight的值由nice值决定,如下所示:

在这里插入图片描述

​   在这个表中我们可以找到规律:高一个等级的weight是第一个等级的1.25倍。

​   当进程被调度到时,进程将得到CPU的运行时间,则进程的物理运行时间的值将逐渐增大,当增大到某个程度时,该进程的vruntime在红黑树中的值将不是最小,此时操作系统更新红黑树之后再去调度红黑树中的最左边节点的进程,实现了完全公平调度算法。

​   CFS调度算法引入的vruntime不仅记录了每个进程的权重信息,还包含了每个进程的CPU占用时间。这种做法完美解决了I/O消耗型任务和CPU消耗型任务调度的优先级问题,因为I/O消耗型任务的CPU占用时间注定是比CPU消耗型任务的运行时间少,因此vruntime的分子也将变小。

  CFS调度算法致力于每一个进程的virtual runtime时间相等,那就意味着若两个进程的weight若相差了三倍,则真实的CPU利用率也将相差三倍。

以下通过实验证明以上观点,以下一个程序将创建八个线程并执行死循环:

#define N 8

void *thread_fun(void *param)
{
    printf("thread pid:%d, tid:%lu\n", getpid(), pthread_self());
    while (1) ;
    return NULL;
}

int main(void)
{
    pthread_t tid[N];
    int ret;

    printf("main pid:%d, tid:%lu\n", getpid(), pthread_self());

    for (int i = 0; i < N; i++){
        ret = pthread_create(&tid[i], NULL, thread_fun, NULL);
        if (ret == -1){
            perror("cannot create new thread");
            return 1;
        }
    }

    for (int i = 0; i < N; i++){
        if (pthread_join(tid[i], NULL) != 0){
            perror("call pthread_join function fail");
            return 1;
        }
    }

    return 0;
}

​   在默认情况下,该进程中所有线程的nice值都相同,我们可以启动两次上述程序,若不做任何干涉,两个进程的CPU占用率应该相同:

第一个进程:

在这里插入图片描述

第二个进程:

在这里插入图片描述

  此时我们使用top命令观察27618和27627这两个进程的CPU使用情况,都是接近200%(两个线程每个线程占用一个核):

在这里插入图片描述

  若此时我们使用命令renice -n -5 -g 27618将27618的nice值设置为-5(-5的权重相比于0的权重来说,-5的权重比0的权重大了将近3倍),由于CFS需要保持vruntime的一致性,所以27618的runtime必须为27627的三倍,也就是说27618的CPU利用率将会是27627的三倍:

在这里插入图片描述

  在改变完27618进程的nice值之后,我们可以看到top命令输出的参数已经发生变化:

在这里插入图片描述

注意:该效果需要在CPU满负荷情况下才能实现,若CPU存在空闲,则进程始终将会被调度执行。

调度相关的系统调用

在C/C++当中,可以去调用以下api去做进程调度的相关选项:

System CallDescription
nice()Sets a process’s nice value
sched_setscheduler()Sets a process’s scheduling policy
sched_getscheduler()Gets a process’s scheduling policy
sched_setparam()Sets a process’s real-time priority
sched_getparam()Gets a process’s real-time priority
sched_get_priority_max()Gets the maximum real-time priority
sched_get_priority_min()Gets the minimum real-time priority
sched_rr_get__interval()Gets a process’s timeslice value
sched_setaffinity()Sets a process’s processor affinity
sched_getaffinity()Gets a process’s processor affinity
sched_yield()Temporarily yields the processor
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值