线程调度
在 Linux 中,线程是由进程来实现的,可以认为线程就是一个轻量级的进程,因此,线程调度是按照进程调度的方式来进行的。这样设计,线程调度流程可以直接复用进程调度流程,没必要再设计一个进程内的线程调度器了。
调度策略与调度优先级
在 Linux 中,进程调度器是基于进程的调度策略与调度优先级来决定调度哪个进程运行。
调度策略主要包括:
- SCHED_OTHER
- SCHED_IDLE
- SCHED_BACH
- SCHED_FIFO
- SCHED_RR
调度优先级的范围是 0~99,数值越大,表示优先级越高。
其中,SCHED_OTHER、SCHED_IDLE、SCHED_BACH 为非实时调度策略,其调度优先级为 0。而 SCHED_FIFO、SCHED_RR 是实时调度策略,其调度优先级范围为 1~99。
实时调度策略的进程总是比非实时调度策略的进程优先级高。
在 Linux 内部实现中,调度器会为每个可能的调度优先级维护一个可运行的进程列表,以最高优先级列表头部的进程作为下一次调度的进程,所有的调度都是抢占式的,如果一个具有更高调度优先级的进程转换为可运行状态,那么当前运行的进程将被强制进入其等待的队列中。
常见的调度策略
SCHED_OTHER
该调度策略是默认的 Linux 分时调度策略,该调度策略为非实时的,其调度优先级总是为 0。
对于该调度策略类型的进程,调度器是基于动态优先级来调度的。动态优先级跟属性 nice 有关,nice 的值会随着进程的运行时间而动态改变,以确保所有具有 SCHED_OTHER 策略的进程公平地得到调度。
在 Linux 中,nice 的值范围为-20 ~ +19,默认值为 0。nice 值越大,则优先级越低,因此相对较低 nice 值的进程可以获得更多的处理器时间。
通过命令 ps -el 查看系统中的进程列表,其中 NI 列就是进程对应的 nice 值。
[root@vm_rp0_cpu1_docker ~]# ps -el
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 1 0 0 80 0 - 13653 ep_pol ? 00:00:40 systemd
1 S 0 2 0 0 80 0 - 0 kthrea ? 00:00:00 kthreadd
1 S 0 3 2 0 80 0 - 0 smpboo ? 00:00:05 ksoftirqd/0
1 S 0 7 2 0 -40 - - 0 smpboo ? 00:00:00 migration/0
1 S 0 8 2 0 80 0 - 0 rcu_gp ? 00:00:00 rcu_bh
1 S 0 9 2 0 80 0 - 0 rcu_gp ? 00:00:21 rcu_sched
5 S 0 10 2 0 60 -20 - 0 rescue ? 00:00:00 lru-add-drain
5 S 0 11 2 0 -40 - - 0 smpboo ? 00:00:01 watchdog/0
5 S 0 13 2 0 80 0 - 0 devtmp ? 00:00:00 kdevtmpfs
5 S 0 14 2 0 60 -20 - 0 rescue ? 00:00:00 netns
使用 top 命令,看到的 NI 列也是进程的 nice 值。
[root@vm_rp0_cpu1_docker ~]# top
top - 14:23:38 up 8 days, 5:44, 5 users, load average: 3.73, 2.67, 2.00
Tasks: 136 total, 3 running, 132 sleeping, 1 stopped, 0 zombie
%Cpu(s): 29.4 us, 29.4 sy, 0.0 ni, 41.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st, 38.0 id_exact, 1.2 hi_exact, 1.7 irq_exac
KiB Mem : 991516 total, 434932 free, 408604 used, 147980 buff/cache
KiB Swap: 524284 total, 158632 free, 365652 used. 461432 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
22067 root 20 0 7312 28 0 R 25.0 0.0 70:38.00 stress
22068 root 20 0 621716 215216 28 R 25.0 21.7 70:34.70 stress
1 root 20 0 54612 3452 2108 S 0.0 0.3 0:40.82 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.17 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:05.92 ksoftirqd/0
7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
9 root 20 0 0 0 0 S 0.0 0.0 0:21.27 rcu_sched
10 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 lru-add-drain
11 root rt 0 0 0 0 S 0.0 0.0 0:01.39 watchdog/0
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
14 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 netns
15 root 20 0 0 0 0 S 0.0 0.0 0:00.14 khungtaskd
16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 writeback
17 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kintegrityd
18 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
19 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kblockd
调整 nice 值,可以通过 shell 命令 nice,该命令可以按照指定的 nice 值运行 cmd,命令的帮助信息为:
[root@vm_rp0_cpu1_docker ~]# nice --help
Usage: nice [OPTION] [COMMAND [ARG]...]
Run COMMAND with an adjusted niceness, which affects process scheduling.
With no COMMAND, print the current niceness. Niceness values range from
-20 (most favorable to the process) to 19 (least favorable to the process).
Mandatory arguments to long options are mandatory for short options too.
-n, --adjustment=N add integer N to the niceness (default 10)
--help display this help and exit
--version output version information and exit
NOTE: your shell may have its own version of nice, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
For complete documentation, run: info coreutils 'nice invocation'
重新调整已运行进程的 nice 值,可通过 renice 命令实现,命令的帮助信息为:
[root@vm_rp0_cpu1_docker ~]# renice --help
Usage:
renice [-n] <priority> [-p|--pid] <pid>...
renice [-n] <priority> -g|--pgrp <pgid>...
renice [-n] <priority> -u|--user <user>...
Options:
-g, --pgrp <id> interpret argument as process group ID
-n, --priority <num> specify the nice increment value
-p, --pid <id> interpret argument as process ID (default)
-u, --user <name|id> interpret argument as username or user ID
-h, --help display help text and exit
-V, --version display version information and exit
For more information see renice(1).
另外,可以执行 top 命令,输入 r,根据提示输入进程的 pid,再输入 nice 数值,也可以调整进程的 nice 值。
SCHED_FIFO
该调度策略为先入先出调度策略,简单概括,就是一旦进程占用了 CPU,则一直运行,直到有更高优先级的任务抢占,或者进程自己放弃占用 CPU。
SCHED_RR
该调度策略为时间片轮转调度策略,该调度策略是基于 SCHED_FIFO 策略的演进,其在每个进程上增加一个时间片限制,当时间片使用完成后,调度器将该进程置于队列的尾端,放在尾端保证了所有具有相同调度优先级的进程的调度公平。
使用 top 命令,如果 PR 列的值为 RT,则说明该进程采用的是实时调度策略,其调度策略为 SCHED_FIFO 或者 SCHED_RR,而对于非实时调度策略的进程,该列的值为 NI + 20。
可以通过命令 ps -eo state,uid,pid,ppid,rtprio,time,comm 来查看进程对应的实时优先级,实时优先级位于 RTPRIO 列下,如果进程对应的列显示为 -,说明该进程不是实时进程。
[root@vm_rp0_cpu1_docker ~]# ps -eo state,uid,pid,ppid,rtprio,time,comm
S UID PID PPID RTPRIO TIME COMMAND
S 0 1 0 - 00:00:40 systemd
S 0 2 0 - 00:00:00 kthreadd
S 0 3 2 - 00:00:05 ksoftirqd/0
S 0 7 2 99 00:00:00 migration/0
S 0 8 2 - 00:00:00 rcu_bh
S 0 9 2 - 00:00:21 rcu_sched
S 0 10 2 - 00:00:00 lru-add-drain
S 0 11 2 99 00:00:01 watchdog/0
S 0 13 2 - 00:00:00 kdevtmpfs
S 0 14 2 - 00:00:00 netns
S 0 15 2 - 00:00:00 khungtaskd
S 0 16 2 - 00:00:00 writeback
chrt 命令
chrt 命令可以用来很简单地更改进程的调度策略与调度优先级。在 Linux 下查看 chrt 命令的帮助信息:
[root@vm_rp0_cpu1_docker ~]# chrt --help
Show or change the real-time scheduling attributes of a process.
Set policy:
chrt [options] <priority> <command> [<arg>...]
chrt [options] --pid <priority> <pid>
Get policy:
chrt [options] -p <pid>
Policy options:
-b, --batch set policy to SCHED_BATCH
-d, --deadline set policy to SCHED_DEADLINE
-f, --fifo set policy to SCHED_FIFO
-i, --idle set policy to SCHED_IDLE
-o, --other set policy to SCHED_OTHER
-r, --rr set policy to SCHED_RR (default)
Scheduling options:
-R, --reset-on-fork set SCHED_RESET_ON_FORK for FIFO or RR
-T, --sched-runtime <ns> runtime parameter for DEADLINE
-P, --sched-period <ns> period parameter for DEADLINE
-D, --sched-deadline <ns> deadline parameter for DEADLINE
Other options:
-a, --all-tasks operate on all the tasks (threads) for a given pid
-m, --max show min and max valid priorities
-p, --pid operate on existing given pid
-v, --verbose display status information
-h, --help display this help and exit
-V, --version output version information and exit
For more details see chrt(1).
比如,获取某个进程的调度策略,使用如下命令:
[root@vm_rp0_cpu1_docker ~]# chrt -p 27916
pid 27916's current scheduling policy: SCHED_FIFO
pid 27916's current scheduling priority: 80
在比如,设置某个进程的调度策略为 SCHED_FIFO,调度优先级为 70,使用如下命令:
[root@vm_rp0_cpu1_docker ~]# chrt -p -f 70 27917
[root@vm_rp0_cpu1_docker ~]#
测试代码
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <assert.h>
static int get_thread_policy(pthread_attr_t *attr)
{
int policy;
int rs = pthread_attr_getschedpolicy(attr,&policy);
assert(rs==0);
switch(policy)
{
case SCHED_FIFO:
printf("policy=SCHED_FIFO\n");
break;
case SCHED_RR:
printf("policy=SCHED_RR\n");
break;
case SCHED_OTHER:
printf("policy=SCHED_OTHER\n");
break;
default:
printf("policy=UNKNOWN\n");
break;
}
return policy;
}
static void show_thread_priority(pthread_attr_t *attr,int policy)
{
int priority = sched_get_priority_max(policy);
assert(priority != -1);
printf("max_priority=%d\n",priority);
priority= sched_get_priority_min(policy);
assert(priority != -1);
printf("min_priority=%d\n",priority);
}
static int get_thread_priority(pthread_attr_t *attr)
{
struct sched_param param;
int rs = pthread_attr_getschedparam(attr,¶m);
assert(rs == 0);
printf("priority=%d\n",param.__sched_priority);
return param.__sched_priority;
}
static void set_thread_policy(pthread_attr_t *attr,int policy)
{
int rs = pthread_attr_setschedpolicy(attr,policy);
assert(rs==0);
}
int main(void)
{
pthread_attr_t attr;
int rs;
rs = pthread_attr_init(&attr);
assert(rs==0);
int policy = get_thread_policy(&attr);
printf("Show current configuration of priority\n");
get_thread_policy(&attr);
show_thread_priority(&attr,policy);
printf("show SCHED_FIFO of priority\n");
show_thread_priority(&attr,SCHED_FIFO);
printf("show SCHED_RR of priority\n");
show_thread_priority(&attr,SCHED_RR);
printf("show priority of current thread\n");
get_thread_priority(&attr);
printf("Set thread policy\n");
printf("set SCHED_FIFO policy\n");
set_thread_policy(&attr,SCHED_FIFO);
get_thread_policy(&attr);
get_thread_priority(&attr);
printf("set SCHED_RR policy\n");
set_thread_policy(&attr,SCHED_RR);
get_thread_policy(&attr);
printf("Restore current policy\n");
set_thread_policy(&attr,policy);
get_thread_priority(&attr);
rs = pthread_attr_destroy(&attr);
assert(rs==0);
return 0;
}