活动地址:毕业季·进击的技术er
嵌入式Linux系统编程-》线程调度与信号牌处理+面试题+源码
1. 基本概念
在Linux中,线程是系统的调度实体,
Linux是抢占式内核
,意味着线程在占用CPU运行时并不是高枕无忧的,而是可以被所谓高优先级的其他任务抢占,这就引出了优先级的基本概念。
2. 静态优先级
在Linux中,所有的任务(进程、线程在内核统称任务,task)被分成两类静态优先级:
`普通任务(静态优先级是 0)
系统任务(静态优先级是 1-99)`
静态优先级越大,优先权限也越大,普通任务是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, ¶m);
// 创建一条普通调度策略的线程
pthread_t tid;
pthread_create(&tid, &attr, routine, NULL);
以上代码实际上跟直接创建不带任何属性的线程是完全一样的,示例只是给大家演示如何设定线程优先级的流程。
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, ¶m1);
pthread_attr_setschedparam(&attr2, ¶m2);
// 创建两条系统线程,静态优先级分别为12和13
pthread_t tid1, tid2;
pthread_create(&tid1, &attr1, routine1, NULL);
pthread_create(&tid2, &attr2, routine2, NULL);
3. 动态优先级
当多个普通任务并发运行时,系统会根据其实际运行的表现来动态地调整他们的nice值,
nice值越高,优先级越低,nice值越低,优先级越高。
任务表现指标主要体现在:
睡眠时间越多,放着系统资源不用,系统就倾向于判定其为IO消耗性任务,会逐步提高其优先级
睡眠时间越少,拼命抢占系统资源,系统就倾向于判定其为CPU消耗性任务,会逐步降低其优先级
除了系统这种自动化的管理,我们关心的是如何通过代码的方式自主地干预线程的动态优先级,修改动态优先级,实际上就是修改nice值。
3.1 核心API
动态优先级相关API非常简单:
int nice(int incr);
参数
incr:nice值,范围:-20 ~ 19
返回值
成功返回0
失败返回-1
4. 线程的信号处理 基本问题
由于多线程程序中的线程的执行状态是并发的,因此当一个
进程(注意不是线程)收到一个信号时
,那么究竟由进程中的哪条线程响应这个信号就是不确定的,取决于哪条线程刚好在信号达到的瞬间被调度,这种不确定性在程序逻辑中一般是不能接收的。
5. 解决思路
以上问题的一般解决思路是:
在多线程进程中选定某条线程 TiTi 去响应信号 其余线程对该信号屏蔽
6. 核心API
发送信号给指定线程
int pthread_kill(pthread_t thread, int sig);
功能:给一条线程发送信号
参数
thread - 接收信号的线程TID
sig - 发送的信号编号
返回值
成功返回0
失败返回-1
发送带参数的信号给指定线程
int pthread_sigqueue(pthread_t thread, int sig,
const union sigval value);
功能:给一条线程发送带额外参数的信号
参数
thread - 接收信号的线程TID
sig - 发送的信号编号
value - 额外携带的参数
返回值
成功返回0
失败返回-1
屏蔽指定信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:屏蔽(阻塞)指定信号
参数
how:
SIG_BLOCK - 屏蔽信号集中的信号
SIG_UNBLOCK - 解除信号集中的信号的屏蔽
SIG_SETMASK - 先解除目前正在屏蔽的所有信号,然后屏蔽信号集中的信号
set - 信号集,集合中的所有信号都会按照参数how的规定被设定
oldset - 信号集,保留该函数调用前的信号集,可设置为NULL
返回值
成功返回0
失败返回-1
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);
}
8.问题
1. 问:老师,怎么把虚拟机调成单核模式?
答: 点击虚拟机菜单栏中的设置,找到处理器,将处理器的数量和内核数量都设置为1就可以了,如下图所示:
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, ¶m1);
pthread_attr_setschedparam(&attr2, ¶m2);
// 使用属性变量,去创建线程
pthread_t tid1, tid2;
pthread_create(&tid1, &attr1, routine, "A");
pthread_create(&tid2, &attr2, routine, "B");
pause();
return 0;
}
编写一个多线程程序,使其产生两条相同静态优先级但不同动态优先级的线程,一个死循环输出数字,一个死循环输出字母,观察其运行效果。
解析
操作流程与上题一致,只需将调度策略统一设置为 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, ¶m1);
pthread_attr_setschedparam(&attr2, ¶m2);
// 使用属性变量,去创建线程
pthread_t tid1, tid2;
pthread_create(&tid1, &attr1, routine, "A");
pthread_create(&tid2, &attr2, routine, "B");
pause();
return 0;
}
活动地址:毕业季·进击的技术er