Linux :多处理器遇到实时进程和普通进程的程序设计

道哥分享

    失败是成功之母,这篇文章就是一次真实的失败调试记录。
    通过这篇文章,您能深刻体验到 Linux 系统中下面几个概念:
    实时进程和普通进程的调度策略;
    Linux 中混乱的进程优先级是如何计算的;
    CPU亲和性的测试;
    多处理器(SMP)遇到实时进程和普通进程的程序设计;
    道哥的脑袋被门夹了一下的短路经历;
    背景知识:Linux 调度策略
    关于进程的调度策略,不同的操作系统有不同的整体目标,因此调度算法也就各不相同。
    这需要根据进程的类型(计算密集型?IO密集型?)、优先级等因素来进行选择。
    对于 Linux  x86 平台来说,一般采用的是 CFS:完全公平调度算法。
    之所以叫做完全公平,是因为操作系统以每个线程占用 CPU 的比率来进行动态的计算,操作系统希望每一个进程都能够平均的使用 CPU 这个资源,雨露均沾。
    我们在创建一个线程的时候,默认就是这个调度算法 SCHED_OTHER,默认的优先级为 0。
    PS: 在 Linux 操作系统中,线程的内核对象与进程的内核对象(其实就是一些结构体变量)是很类似的,所以线程可以说是轻量级的进程。
    在本文中,可以把线程约等于进程,有的地方也可能称为任务,在不同的语境下有一些不同的惯用说法。
    可以这么理解:如果系统中一共有 N 个进程,那么每个进程会得到 1/N 的执行机会。每个进程执行一段时间之后,就被调出,换下一个进程执行。
    如果这个 N 的数量太大了,导致每个进程刚开始执行时,分给它的时间就到了。如果这个时候就进行任务调度,那么系统的资源就耗费在进程上下文切换上去了。
    因此,操作系统引入了最小粒度,也就是每个进程都有一个最小的执行时间保证,称作时间片。
    除了 SCHED_OTHER 调度算法,Linux 系统还支持两种实时调度策略:
    1. SCHED_FIFO:根据进程的优先级进行调度,一旦抢占到 CPU 则一直运行,直达自己主动放弃或被被更高优先级的进程抢占;
    2. SCHED_RR:在 SCHED_FIFO 的基础上,加上了时间片的概念。当一个进程抢占到 CPU 之后,运行到一定的时间后,调度器会把这个进程放在 CPU 中,当前优先级进程队列的末尾,然后选择另一个相同优先级的进程来执行;
    本文想测试的就是 SCHED_FIFO 与普通的 SCHED_OTHER 这两种调度策略混合的情况。
    背景知识:Linux 线程优先级
    在 Linux 系统中,优先级的管理显得比较混乱,先看下面这张图:
    
    这张图表示的是内核中的优先级,分为两段。
    前面的数值 0-99 是实时任务,后面的数值 100-139 是普通任务。
    数值越低,代表这个任务的优先级越高。
    数值越低,代表这个任务的优先级越高。
    数值越低,代表这个任务的优先级越高。
    再强调一下,以上是从内核角度来看的优先级。
    好了,重点来了:
    我们在应用层创建线程的时候,设置了一个优先级数值,这是从应用层角度来看的优先级数值。
    但是内核并不会直接使用应用层设置的这个数值,而是经过了一定的运算,才得到内核中所使用的优先级数值(0 ~ 139)。
    1. 对于实时任务
    我们在创建线程的时候,可以通过下面这样的方式设置优先级数值(0 ~ 99):
    struct sched_param param;
    param.__sched_priority = xxx;
    当创建线程函数进入内核层面的时候,内核通过下面这个公式来计算真正的优先级数值:
    kernel priority = 100 - 1 - param.__sched_priority
    如果应用层传入数值 0,那么在内核中优先级数值就是 99(100 - 1 - 0 = 99),在所有实时任务中,它的优先级是最低的。
    如果应用层传输数值 99,那么在内核中优先级数值就是 0(100 - 1 - 99 = 0),在所有实时任务中,它的优先级是最高的。
    因此,从应用层的角度看,传输人优先级数值越大,线程的优先级就越高;数值越小,优先级就越低。
    与内核角度是完全相反的!
    2. 对于普通任务
    调整普通任务的优先级,是通过 nice 值来实现的,内核中也有一个公式来把应用层传入的 nice 值,转成内核角度的优先级数值:
    kernel prifoity = 100 + 20 + nice
    nice 的合法数值是:-20 ~ 19。
    如果应用层设置线程 nice 数值为 -20,那么在内核中优先级数值就是 100(100 + 20 + (-20) = 100),在所有的普通任务中,它的优先级是最高的。
    如果应用层设置线程 nice 数值为 19,那么在内核中优先级数值就是 139(100 +20 +19 = 139),在所有的普通任务中,它的优先级是最低的。
    因此,从应用层的角度看,传输人优先级数值越小,线程的优先级就越高;数值越大,优先级就越低。
    与内核角度是完全相同的!
    背景知识交代清楚了,终于可以进行代码测试了!
    测试代码说明
    注意点:
    #define _GNU_SOURCE 必须在 #include <sched.h> 之前定义;
    #include <sched.h> 必须在 #include <pthread.h> 之前包含进来;
    这个顺序能够保证在后面设置继承的 CPU 亲和性时,CPU_SET, CEPU_ZERO这两个函数能被顺利定位到。
    // filename: test.c
    #define _GNU_SOURCE
    #include <unistd.h>  
    #include <stdio.h>
    #include <stdlib.h>
    #include <sched.h>
    #include <pthread.h>
    // 用来打印当前的线程信息:调度策略是什么?优先级是多少?
    void get_thread_info(const int thread_index)
    {
        int policy;
        struct sched_param param;
        printf("====> thread_index = %d ", thread_index);
        pthread_getschedparam(pthread_self(), &policy, &param);
        if (SCHED_OTHER == policy)
            printf("thread_index %d: SCHED_OTHER ", thread_index);
        else if (SCHED_FIFO == policy)
            printf("thread_index %d: SCHED_FIFO ", thread_index);
        else if (SCHED_RR == policy)
            printf("thread_index %d: SCHED_RR ", thread_index);
        printf("thread_index %d: priority = %d ", thread_index, param.sched_priority);
    }
    // 线程函数,
    void *thread_routine(void *args)
    {
        // 参数是:线程索引号。四个线程,索引号从 1 到 4,打印信息中使用。
        int thread_index = *(int *)args;
        // 为了确保所有的线程都创建完毕,让线程睡眠1秒。
        sleep(1);
        // 打印一下线程相关信息:调度策略、优先级。
    
    
    1  2  3  下一页>