• 嵌入式Linux系统编程-》线程调度与信号牌处理+面试题+源码【第8天】


    活动地址:毕业季·进击的技术er

    1. 基本概念

    在Linux中,线程是系统的调度实体,Linux是抢占式内核,意味着线程在占用CPU运行时并不是高枕无忧的,而是可以被所谓高优先级的其他任务抢占,这就引出了优先级的基本概念。

    在这里插入图片描述

    2. 静态优先级

    在Linux中,所有的任务(进程、线程在内核统称任务,task)被分成两类静态优先级

    `普通任务(静态优先级是 0)
    系统任务(静态优先级是 1-99)`
    
    • 1
    • 2

    静态优先级越大,优先权限也越大,普通任务是0,意味着会被任意系统任务抢占。静态优先级之所以被称为静态,是因为这种优先级一旦设定就不能再改变,是任务本身的属性。

    2.1 普通任务及其调度策略

    普通任务的静态优先级必须被设定为0,普通任务无法跟系统任务参与系统资源的竞争,普通任务彼此之间的用动态优先级去竞争系统资源。

    SCHED_OTHER调度

    对于普通任务而言,调度策略没得选择,只有一种:SCHED_OTHER,处于该调度策略的任务的静态优先级也必须设定为0。

    核心API调用流程

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    
    // 声明要显式设定优先级策略(否则下面代码无法执行)
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    
    // 在属性变量中,指定静态优先级的策略为SCHED_OTHER:
    // 1,SCHED_FIFO
    // 2,SCHED_RR
    // 3,SCHED_OTHER(即0级普通任务)
    pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
    
    // 将静态优先级的级别设置为0(SCHED_OTHER必须是0):
    struct sched_param param;
    param.sched_priority = 0;
    pthread_attr_setschedparam(&attr, &param);
    
    // 创建一条普通调度策略的线程
    pthread_t tid;
    pthread_create(&tid, &attr, routine, NULL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上代码实际上跟直接创建不带任何属性的线程是完全一样的,示例只是给大家演示如何设定线程优先级的流程。

    2.2 系统任务及其调度策略

    系统任务的静态优先级必须被设定为1-99之间,系统任务还可以设定以下两种不同的调度策略:

    SCHED_FIFO调度

    一个被设定为FIFO调度策略的系统任务,会一直占用CPU资源,直到自动放弃或被更高优先级的任务抢占为止。

    SCHED_RR调度

    一个被设定为RR调度策略的系统任务,会一直占用CPU资源,直到自动放弃或被更高优先级的任务抢占,或所分配的时间片耗尽为止。

    核心API调用流程

    pthread_attr_t attr1;
    pthread_attr_t attr2;
    pthread_attr_init(&attr1);
    pthread_attr_init(&attr2);
    
    // 声明要显式设定优先级策略(否则下面代码无法执行)
    pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
    
    // 在属性变量中,指定静态优先级的策略:
    // 1,SCHED_FIFO
    // 2,SCHED_RR
    // 3,SCHED_OTHER
    pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
    pthread_attr_setschedpolicy(&attr2, SCHED_RR);
    
    // 在属性变量中,添加静态优先级的级别:
    struct sched_param param1;
    struct sched_param param2;
    param1.sched_priority = 12; // 静态优先级级别: 1 ~ 99
    param2.sched_priority = 13;
    pthread_attr_setschedparam(&attr1, &param1);
    pthread_attr_setschedparam(&attr2, &param2);
    
    // 创建两条系统线程,静态优先级分别为12和13
    pthread_t tid1, tid2;
    pthread_create(&tid1, &attr1, routine1, NULL);
    pthread_create(&tid2, &attr2, routine2, NULL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3. 动态优先级

    当多个普通任务并发运行时,系统会根据其实际运行的表现来动态地调整他们的nice值nice值越高,优先级越低,nice值越低,优先级越高。


    任务表现指标主要体现在:

    睡眠时间越多,放着系统资源不用,系统就倾向于判定其为IO消耗性任务,会逐步提高其优先级
    睡眠时间越少,拼命抢占系统资源,系统就倾向于判定其为CPU消耗性任务,会逐步降低其优先级
    除了系统这种自动化的管理,我们关心的是如何通过代码的方式自主地干预线程的动态优先级,修改动态优先级,实际上就是修改nice值。

    3.1 核心API

    动态优先级相关API非常简单:

    int nice(int incr);
    
    • 1

    参数

    incr:nice值,范围:-20 ~ 19
    返回值
    成功返回0
    失败返回-1
    
    • 1
    • 2
    • 3
    • 4

    4. 线程的信号处理 基本问题

    由于多线程程序中的线程的执行状态是并发的,因此当一个进程(注意不是线程)收到一个信号时,那么究竟由进程中的哪条线程响应这个信号就是不确定的,取决于哪条线程刚好在信号达到的瞬间被调度,这种不确定性在程序逻辑中一般是不能接收的。

    5. 解决思路

    以上问题的一般解决思路是:

    在多线程进程中选定某条线程 TiTi 去响应信号 其余线程对该信号屏蔽

    6. 核心API

    发送信号给指定线程

    int pthread_kill(pthread_t thread, int sig);
    
    • 1

    功能:给一条线程发送信号
    参数

    thread - 接收信号的线程TID
    sig - 发送的信号编号
    返回值
    成功返回0
    失败返回-1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    发送带参数的信号给指定线程

    int pthread_sigqueue(pthread_t thread, int sig,
                         const union sigval value);
    
    • 1
    • 2

    功能:给一条线程发送带额外参数的信号
    参数

    thread - 接收信号的线程TID
    sig - 发送的信号编号
    value - 额外携带的参数
    返回值
    成功返回0
    失败返回-1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    屏蔽指定信号

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    
    • 1

    功能:屏蔽(阻塞)指定信号
    参数

    how:
    SIG_BLOCK - 屏蔽信号集中的信号
    SIG_UNBLOCK - 解除信号集中的信号的屏蔽
    SIG_SETMASK - 先解除目前正在屏蔽的所有信号,然后屏蔽信号集中的信号
    set - 信号集,集合中的所有信号都会按照参数how的规定被设定
    oldset - 信号集,保留该函数调用前的信号集,可设置为NULL
    返回值
    成功返回0
    失败返回-1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7. 示例代码

    下面代码展示了使用信号屏蔽技术来使得进程中某条确定的线程去响应信号的流程:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <pthread.h>
    
    sigset_t sigs;
    
    void sighook(int sig)
    {
        printf("线程%u正在处理信号\n", pthread_self());
    }
    
    void *routine1(void *arg)
    {
        printf("线程1:%u\n", pthread_self());
        sigprocmask(SIG_BLOCK, &sigs, NULL);
        while(1)
            pause();
    }
    
    void *routine2(void *arg)
    {
        printf("线程2:%u\n", pthread_self());
        sigprocmask(SIG_BLOCK, &sigs, NULL);
        while(1)
            pause();
    }
    
    
    void *routine3(void *arg)
    {
        printf("线程3:%u\n", pthread_self());
        while(1)
            pause();
    }
    
    int main(void)
    {
        // 注册信号响应函数
        signal(SIGINT, sighook);
    
        sigemptyset(&sigs);
        sigaddset(&sigs, SIGINT);
    
        pthread_t t1, t2, t3;
        pthread_create(&t1, NULL, routine1, NULL);
        pthread_create(&t2, NULL, routine2, NULL);
        pthread_create(&t3, NULL, routine3, NULL);
    
        pthread_exit(NULL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    8.问题

    1. 问:老师,怎么把虚拟机调成单核模式?
    在这里插入图片描述

    答: 点击虚拟机菜单栏中的设置,找到处理器,将处理器的数量和内核数量都设置为1就可以了,如下图所示:

    (https://img-blog.csdnimg.cn/307d9cf963dd4e3dbab13d500fd5ae71.png)

    9.面试题

    编写一个多线程程序,使其产生两条不同静态优先级的线程,一个死循环输出数字,一个死循环输出字母,观察其运行效果。


    解析

    要想观察到不同优先级下的各个任务的竞争系统资源的现象,需要在一个单核运算环境下,如果是多核系统,则将难以观察到对应的现象。

    参考代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <unistd.h>
    #include <string.h>
    #include <strings.h>
    #include <errno.h>
    
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <pthread.h>
    
    void *routine(void *arg)
    {
        while(1)
        {
            fprintf(stderr, "%c", *(char *)arg);
        }
    }
    
    int main(int argc, char **argv)
    {
        // 定义两个线程属性变量
        pthread_attr_t attr1;
        pthread_attr_t attr2;
    
        // 初始化(清空)属性变量
        pthread_attr_init(&attr1);
        pthread_attr_init(&attr2);
    
        // 在属性变量中,添加显式优先级策略(不继承)
        // 在不继承原有线程的优先级策略的情况下,才能设定动、静优先级
        pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
        pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
    
        // 在属性变量中,指定静态优先级的策略:
        // 1,SCHED_FIFO
        // 2,SCHED_RR
        // 2,SCHED_OTHER(对应0级普通任务,此时param参数必须设置为0)
        pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
        pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);
    
        // 在属性变量中,添加静态优先级的级别:
        struct sched_param param1;
        struct sched_param param2;
        param1.sched_priority = 80; // 静态优先级级别: 0 ~ 99
        param2.sched_priority = 80;
        pthread_attr_setschedparam(&attr1, &param1);
        pthread_attr_setschedparam(&attr2, &param2);
    
        // 使用属性变量,去创建线程
        pthread_t tid1, tid2;
        pthread_create(&tid1, &attr1, routine, "A");
        pthread_create(&tid2, &attr2, routine, "B");
    
        pause();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    编写一个多线程程序,使其产生两条相同静态优先级但不同动态优先级的线程,一个死循环输出数字,一个死循环输出字母,观察其运行效果。


    解析

    操作流程与上题一致,只需将调度策略统一设置为 SCHED_OTHER即可,然后在线程函数中调用 nice()动态改变自身的nice值即可。有几点需要

    注意:

    与静态优先级实验类似,须在单核系统方可便于观察多任务竞争的现象
    如果需要降低线程默认的动态优先级(即nice值设置为负数),则需要管理员权限方可

    参考代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <unistd.h>
    #include <string.h>
    #include <strings.h>
    #include <errno.h>
    
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <pthread.h>
    
    void *routine(void *arg)
    {
        if(*(char *)arg == 'A')
            nice(1);
        else if(*(char *)arg == 'B')
            nice(5);
    
        while(1)
        {
            fprintf(stderr, "%c", *(char *)arg);
        }
    }
    
    int main(int argc, char **argv)
    {
        // 定义两个线程属性变量
        pthread_attr_t attr1;
        pthread_attr_t attr2;
    
        // 初始化(清空)属性变量
        pthread_attr_init(&attr1);
        pthread_attr_init(&attr2);
    
        // 在属性变量中,添加显式优先级策略(不继承)
        // 在不继承原有线程的优先级策略的情况下,才能设定动、静优先级
        pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
        pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
    
        // 在属性变量中,指定静态优先级的策略:
        // 1,SCHED_FIFO
        // 2,SCHED_RR
        // 2,SCHED_OTHER(对应0级普通任务,此时param参数必须设置为0)
        pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
        pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);
    
        // 在属性变量中,添加静态优先级的级别:
        struct sched_param param1;
        struct sched_param param2;
        param1.sched_priority = 80; // 静态优先级级别: 0 ~ 99
        param2.sched_priority = 80;
        pthread_attr_setschedparam(&attr1, &param1);
        pthread_attr_setschedparam(&attr2, &param2);
    
        // 使用属性变量,去创建线程
        pthread_t tid1, tid2;
        pthread_create(&tid1, &attr1, routine, "A");
        pthread_create(&tid2, &attr2, routine, "B");
    
        pause();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    活动地址:毕业季·进击的技术er

  • 相关阅读:
    软件测试V模型
    2024上海国际智能驾驶技术展览会(自动驾驶展)
    ruoyi 代码生成 react的项目
    我的Vue之旅、04 CSS媒体查询完全指南(Media Quires)
    oracle数据库控制语言—DCL
    高效访问数据的关键:解析MySQL主键自增长的运作机制!
    Linux学习-24-Linux用户和用户组管理介绍
    [附源码]计算机毕业设计在线票务系统Springboot程序
    Stratasys F170 3D打印教程
    java集合类史上最细讲解 - Map篇
  • 原文地址:https://blog.csdn.net/m0_45463480/article/details/125473041