进程调度器
对于CPU进程调度,目前主流的方式是两种,第一种是像window那样抢占式调度,每一个CPU可能会出现调度时间分配不等的情况,这是由于历史硬件单核性能强多核性能弱考虑。
而另一种是时间分片的方式,时间分片是Linux 常见的进程调度器,特点是每一个进程有近似相等的CPU使用权,在使用完成之后立马交给下一个进程完成工作,使用分片的方式虽然可能导致一些重要任务延迟,但这样的处理和调度方式使得系统最为稳定。
一些结论
针对Linux进程调度可以有下面的思考:
- 每一个CPU同一时间只能调度一个进程
- 每一个进程有*近乎相等的执行时间
- 对于逻辑CPU而言进程调度使用轮询的方式执行,当轮询完成则回到第一个进程反复
- 进程数量消耗时间和进程量成正比
对于进程调度来说不能保证一个程序是连续完成的,由于CPU调度和进程切换,上下文也会出现切换情况。
进程状态
对于大部分进程来说,当我们不使用的时候多数处于睡眠状态。
除了睡眠状态之外,进程还有下面几种状态
- 运行状态:获得CPU调度,执行进程任务
- 僵死状态:进程等待结束,等待父进程回收
- 就绪状态:具备运行条件,等待CPU分配
- 睡眠状态:进程不准备运行除非某种条件触发才会获得CPU调度分配。
对于处在睡眠态的进程触发运行态的条件如下
- 外部存储器访问,
- 用户键入或者鼠标操作触发事件
- 等待指定时间
- 等待指定时间
通过ps ax
命令可以查看当前的进程状态,下面的案例以个人的Mac电脑为例:
MacBook-Pro ~ % ps ax PID TT STAT TIME COMMAND 32615 ?? Ss 0:00.11 /usr/libexec/nearbyd 32632 ?? Ss 0:00.51 /System/Library/CoreServices/Screen Time.app/Content 32634 ?? Ss 0:00.02 /System/Library/PrivateFrameworks/Categories.framewo 32635 ?? S 0:00.12 /System/Library/CoreServices/iconservicesagent 32636 ?? Ss 0:00.05 /System/Library/CoreServices/iconservicesd 32671 ?? S 0:02.44 /Applications/Microsoft Edge.app/Contents/Frameworks 32673 ?? S 0:02.86 /Applications/Microsoft Edge.app/Contents/Frameworks 32678 ?? Ss 0:00.17 /System/Library/PrivateFrameworks/UIFoundation.frame 32726 ?? S 0:00.07 /System/Library/Frameworks/CoreServices.framework/Fr 32736 ?? S 0:00.08 /System/Library/Frameworks/CoreServices.framework/Fr 32738 ?? S 0:00.75 /System/Applications/Utilities/Terminal.app/Contents 32739 ?? Ss 0:00.02 /System/Library/PrivateFrameworks/Categories.framewo 32746 ?? Ss 0:00.03 /System/Library/Frameworks/Metal.framework/Versions/ 32740 s000 Ss 0:00.02 login -pf xxxxxx 32741 s000 S 0:00.03 -zsh 32750 s000 R+ 0:00.01 ps ax
s表示sleep,d表示此时可能在等待磁盘IO,但是如果长时间处于D状态则可能是磁盘IO等待超时或者内核可能发生故障。
那么如果只执行一个进程,同时在进程中间休眠过一次,那么休眠的时候进程在干什么?
此时进程会进入一个空进程的模式轮询,但是空进程不是没有事做,而是需要调用一些维持系统运行的线程,为了保证系统正常稳定运行。
如果只有CPU和空闲进程,那么同样会不断的切换睡眠态和运动态,运动态获取用户输入操作完成动作,睡眠态则执行一些轻量操作。
针对睡眠态的进程会有如下特点:
- 遵循同一时间CPU只能完成一个进程操作
- 睡眠态不占用CPU时间,也就是完全不管。
吞吐量和延迟
- 吞吐量:处理完成的进程数量 / 耗费时间
- 延迟:结束处理时间 - 开始处理时间
通过这两点可以总结几个规则:吞吐量的上限是进程的数量多过逻辑CPU的数量,则再增加进程无法增加吞吐量,另外进程中的延迟总是平均的,也就是说多个进程执行会获得近似平均的延迟,最后进程越多延迟越高。
但是现实系统没有那么多理想情况,多数情况是下面几种:
- 空闲进程,此时吞吐量很低,因为很多逻辑CPU都在睡眠状态
- 进程运行态,但是没有就绪,比较理想,CPU可以安排到下一次处理,此时虽然不会延长,但是会出现CPU空闲的情况
- 进程运行态,同时都就绪,此时就好像赛跑,但是只有一个跑道,跑得快的可以抢着多处理,但是总归都要跑完赛道,所以此时延迟变长
最后其实由于程序编写都是单线程的情况,一核运行,多核围观或许在过去更位普遍。
最终主要的优化方式是使用 sar
命令找到运行时间和开销最大进程,同时把一些死进程kill掉。
多CPU调度情况
因为是分片时间每一个进程用一个CPU工作,那么分配和调度CPU安排工作又是如何的? 主要有两种方式,第一种是通过轮询的负载均衡,另一种是全局分配,把任务分配给空闲进程的逻辑CPU。
负载均衡是CPU遇到进程任务依次安排工作,当最后一个CPU安排完成之后,则再回到第一个CPU进行分配,同时都是对于进程执行一定的时间,也就是说出现CPUa处理一部分,另一部分可能是CPUb完成。 全局分配的方式比较简单,就是把任务分配给处于空闲进程的逻辑CPU完成工作。
查看系统逻辑CPU的命令如下:
grep -c processor /proc/cpuinfo
多核cpu通常只有在同时运行多个进程的时候才会发挥作用,但是并不是说有多少核心就有多少倍性能,因为大部分时候进程很少很多CPU都在睡眠态度
如果进程超过逻辑CPU数量,无论怎么增加进程都不会提高处理速度
最后处于睡眠状态的进程其实可以指定睡眠时间,通过sleep函数调用完成进程休眠的操作。