问题:内核对进程调度时发生了什么?
进程调度的本质
任务 / 进程切换
- 即:上下文切换,内核对处理器上的进程进行切换
- “上下文” 指 : 寄存器上的值
“上下文” 指 :
- 将寄存器的值保存到内存中(进程被剥夺处理器,停止执行)
- 将另一组寄存器的值从内存中加载到寄存器(调度下一个进程执行)
进程调度的本质
当时间片耗完,不管进程正在执行什么代码,都一定会发生上下文切换!
- 上下文切换必然导致进程状态的切换
- 上下文切换由终端触发(时钟中断,IO 中断, 等)
有趣的问题
上下文切换时,突然收到一个中断会发生什么
答:
对于简单设计,在上下文切换时会关闭中断
详解 Linux 进程状态 (ps au)
可执行状态 <==> 就绪态
Z, 当前进程已经执行结束,但是资源还未被回收(暂时存在的状态)
main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
static void *thread_entry(void *arg)
{
while (1);
return arg;
}
static void make_thread(void)
{
pthread_t tid = {0};
pthread_create(&tid, NULL, thread_entry, NULL);
}
int main(void)
{
printf("pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello word\n");
while (1) sleep(1);
return 0;
}
输出:
wu_tiansong@ubuntu-server:~/project/my_linux$ ./a.out
pid = 914014, ppid = 829629, pgid = 914014
hello word
启用新的控制台
# CPU 状态 S+,CPU 利用率 0
wu_tian+ 914014 0.0 0.0 2644 624 pts/20 S+ 20:17 0:00 ./a.out
mian.c
int main(void)
{
printf("pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello word\n");
while (1); // 删除 sleep
return 0;
}
启用新的控制台
# CPU 状态 R+,CPU 利用率 109
wu_tian+ 1038500 109 0.0 2644 620 pts/20 R+ 20:23 0:03 ./a.out
main.c
int main(void)
{
printf("pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello word\n");
make_thread(); // 启用多线程
while (1);
return 0;
}
启用新的控制台
# CPU 状态 Rl+,CPU 利用率 180
wu_tian+ 1044852 180 0.0 10840 560 pts/20 Rl+ 20:27 0:12 ./a.out
对于 CPU 利用率超过 100% 的说明: 有多个 CPU 核心被占用 (CPU 调度的基本单位是线程)
wu_tiansong@ubuntu-server:~/project/my_linux$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 46 bits physical, 48 bits virtual
CPU(s): 12 # ======>>>>> CPU 总计 12 核心
...
指定进程在特定核心运行
# 控制台运行
wu_tiansong@ubuntu-server:~/project/my_linux$ taskset -c 0 ./a.out
pid = 1054371, ppid = 829629, pgid = 1054371
hello word
# 启用新的控制台, CPU 利用率在 100% 内
wu_tian+ 1054371 98.8 0.0 10840 620 pts/20 Rl+ 20:32 0:04 ./a.out
总结
线程是 CPU 调度的基本单位,同时 linux 会将线程调度到不同的 CPU 上
每个核心一时刻只能执行一个线程,对于当前处理器,最多可以并行执行 12 个线程
细说空闲状态
- 处理器上电后,开始一直不停的向下执行指令
- 当线程中没有进程时,会执行一个“不执行任何操作”的空闲进程
- 空闲进程的职责:执行特殊指令使处理器进入休眠状态(低功耗状态)
- 空闲状态是一种暂态,但凡出现就绪进程,空闲状态立即结束
Linux 性能工具介绍
- ps, 查看进程运行时的数据 (pa au)
- top, Linux 整体性能检测工具(类似任务管理器)
- sar, Linux 活动情况报告(系统性能分析工具)
系统的平均负载
- 即:Linux 系统平均负载值 (Linux System Load Averages)
该值表示的时一段时间内任务对系统资源需求的平均值 (1、5和15分钟)
- 如果平均值接近 0 , 意味着系统处于空闲状态
如果平均值大于 1, 意味着系统繁忙,任务需要等待,无法及时执行 (对于仅有1个处理器时为1,4个处理器时为4)
- 如果 1min 平均值高于 5min 或 15min 平均值,则负载正在增加
- 如果 1min 平均值低于 5min 或 15min 平均值,则负载正在减少
详解 sar -q
- runq-sz : 执行队列的长度 (正在被执行的线程个数)
- plist-sz : 运行中的任务(进程 & 线程)总数
- ldavg-1 : 最近 1 分钟系统平均负载
- ldavg-5 : 最近 5 分钟系统平均负载
- ldavg-15 : 最近 15 分钟系统平均负载
如果系统平均负载值大于处理器的数量,那么系统可能遇到了性能问题 (即,1个处理器时超过1,4个处理器时超过4)
系统调度实验观察
通过 Linux 性能工具观察进程调度
- 单处理器运行过程
- 多处理器运行过程
int main(void)
{
printf("pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello word\n");
while (1) sleep(1);
return 0;
}
输出:
tiansong@tiansong-VMware-Virtual-Platform:~/桌面$ taskset -c 0,1 ./a.out &
[1] 3642
tiansong@tiansong-VMware-Virtual-Platform:~/桌面$ pid = 3642, ppid = 3272, pgid = 3642
hello word
taskset -c 2,3 ./a.out &
[2] 3706
pid = 3706, ppid = 3272, pgid = 3706
hello word
###############################################################################
# CPU 占用率 200 + 200, 处于繁忙状态
tiansong 3642 200 0.0 10876 1408 pts/1 Rl 21:19 0:51 ./a.out
tiansong 3706 200 0.0 10876 1408 pts/1 Rl 21:19 0:14 ./a.out
################################################################################
load average: 3.96, 2.40, 1.09. 其中1分钟平均负载值小于 12, 说明还有 CPU 资源可用
# top -d 1 (1s 刷新一次)
top - 21:23:20 up 7 min, 3 users, load average: 3.96, 2.40, 1.09
任务: 402 total, 3 running, 399 sleeping, 0 stopped, 0 zombie
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu4 : 4.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu5 : 4.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu6 : 58.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu7 : 0.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu8 : 0.0 us, 1.0 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu9 : 2.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu10 : 2.0 us, 1.0 sy, 0.0 ni, 97.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu11 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 7716.2 total, 5661.7 free, 1273.8 used, 1059.5 buff/cache
MiB Swap: 4096.0 total, 4096.0 free, 0.0 used. 6442.4 avail Mem
进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME+ COMMAND
3642 tiansong 20 0 10876 1408 1408 R 199.0 0.0 8:09.79 a.out
3706 tiansong 20 0 10876 1408 1408 R 199.0 0.0 7:32.90 a.out
2972 tiansong 20 0 1318012 89760 45312 S 2.0 1.1 0:08.92 node
3795 tiansong 20 0 14832 6016 3712 R 2.0 0.1 0:01.46 top
1585 tiansong 20 0 4268160 267528 121952 S 1.0 3.4 0:08.09 gnome-shell
3125 tiansong 20 0 15684 7332 4992 S 1.0 0.1 0:00.86 sshd
1 root 20 0 23196 13668 9316 S 0.0 0.2 0:03.65 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 pool_workqueue_release
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-rcu_g
5 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-rcu_p
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-slub_
7 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-netns
8 root 20 0 0 0 0 I 0.0 0.0 0:00.03 kworker/0:0-cgroup_destroy
9 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H-events_highpri
11 root 20 0 0 0 0 I 0.0 0.0 0:00.06 kworker/u256:0-ext4-rsv-conversion
12 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-mm_pe
13 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_tasks_kthread
sq 的使用
tiansong@tiansong-VMware-Virtual-Platform:~/桌面$ taskset -c 0,1 ./a.out &
[1] 6082
pid = 6082, ppid = 5980, pgid = 6082
hello word
tiansong@tiansong-VMware-Virtual-Platform:~/桌面$ sar -q 1 # 指定 1 S 中打印一次,同时观察数据可得,系统负载值在增加
Linux 6.8.0-39-generic (tiansong-VMware-Virtual-Platform) 2024年09月24日 _x86_64_ (12 CPU)
21时35分36秒 runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15 blocked
21时35分37秒 3 811 0.41 2.43 2.52 0
21时35分38秒 2 812 0.62 2.44 2.52 0
21时35分39秒 3 809 0.62 2.44 2.52 0
21时35分40秒 2 809 0.62 2.44 2.52 0
21时35分41秒 2 811 0.62 2.44 2.52 0
21时35分42秒 2 808 0.62 2.44 2.52 0
21时35分43秒 2 808 0.73 2.43 2.52 0
21时35分44秒 2 808 0.73 2.43 2.52 0
21时35分45秒 2 808 0.73 2.43 2.52 0
21时35分46秒 3 811 0.73 2.43 2.52 0
21时35分47秒 2 808 0.73 2.43 2.52 0
21时35分48秒 3 808 0.83 2.42 2.52 0
tiansong@tiansong-VMware-Virtual-Platform:~/桌面$ sar -P 0,1,2,3 1 # 观察四个处理器的负载情况,并且 1 秒钟输出一次
Linux 6.8.0-39-generic (tiansong-VMware-Virtual-Platform) 2024年09月24日 _x86_64_ (12 CPU)
21时38分55秒 CPU %user %nice %system %iowait %steal %idle
21时38分56秒 0 100.00 0.00 0.00 0.00 0.00 0.00
21时38分56秒 1 100.00 0.00 0.00 0.00 0.00 0.00
21时38分56秒 2 0.00 0.00 0.00 0.00 0.00 100.00
21时38分56秒 3 1.00 0.00 0.00 0.00 0.00 99.00
21时38分56秒 CPU %user %nice %system %iowait %steal %idle
21时38分57秒 0 100.00 0.00 0.00 0.00 0.00 0.00
21时38分57秒 1 100.00 0.00 0.00 0.00 0.00 0.00
21时38分57秒 2 0.00 0.00 1.98 0.00 0.00 98.02
21时38分57秒 3 0.00 0.00 0.00 0.00 0.00 100.00
系统调度核心性能指标
吞吐量:单位时间内的工作总量 (越大越好)
- 处理器资源消耗越多(空闲状态占比越低),吞吐量越大
- 对于进程调度而言,吞吐量指单位时间处理(处理完)的进程数量
延迟:从开始处理任务到结束处理任务所耗费的时间(越短越好)
- 对于进程而言,延迟即是生命周期,指进程从运行到结束所经历的事故华北
- 注意:运行 和 执行 不同,运行时间可能很长,但执行时间可能很短
吞吐量计算一
吞吐量 = 1 进程 / 100ms = 1 进程 / 0.1s = 10 进程 / 秒
延迟 = 100ms
吞吐量计算二
吞吐量 = 2 进程 / 120ms = 1 进程 / 0.06s = 16.7 进程 / 秒
延迟 = 120ms
吞吐量计算三
吞吐量 = 3 进程 / (40 + 60*2 + 20) = 1进程 / 0.06s = 16.7 进程 / 秒
延迟 = 180ms (图中没有花完整)
示例
- 假设:每个进程固定执行 60ms
- 则: 进程运行结束时
结论
- 处理器的能力由硬件决定,吞吐量存在一个上限
- 当吞吐量未达上限,进程的延迟取决于进程自身
- 当吞吐量达到上线,随着进程数量增加,总延迟增加,但平均延迟不变(即吞吐量不变,上图当有多个线程时的平均延迟均为60ms)
思考
问:如何提高系统吞吐量?
答:提高处理器能力 ; 提高处理器数量
多核吞吐量计算
吞吐量 = 4 进程 / 120ms = 2 进程 / 0.06s = 33.3 进程 / 秒
延迟 = 120ms
对于多处理器计算机来说,只有多个进程并行执行才能提高吞吐量;
并且吞吐量也存在一个上限值,当进程数量多于处理器数量时,吞吐量不再提高。
现实中的系统
- 理想状态:进程正在执行,并且没有就绪状态的进程
- 空闲状态:处理器占用率低,吞吐量低
繁忙状态:
- 多个进程同时运行,但存在多个就绪状态进程
- 此时,吞吐量很高(可能达到峰值),但总体延迟会边长
课后思考
如何验证处理器,进程数量,吞吐量之间的关系?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。