"才一年,看着世界变迁,有种沧海桑田"
一、进程调度 与 进程优先级
(1)何为优先级
双击.exe(可执行程序)文件 会发生什么?
但是,当我们使用电脑的时候,不仅仅只会 启动一个程序! 系统中一定会有多个 进程同时存在!
然而,需求是无限的! 资源却是有限的! 既是存在再多CPU也有 无限的进程 里的代码需要被执行!
在我们实际使用中的计算机,恐怕不会遇见 只能开一个或者几个进程,而其他进程起 不来的情况吧?
显然聪明的大佬们不会让这种事情发生!当然,我们也过于低估了CPU执行、跑代码的速度!
总有进程能够有机会 或许在(到来的时间上,或是"nice"值上.....)
能够更优先一步地 " 抢占 "CPU资源!
这也就是 所谓的优先级的概念!
权限 -----> 能否
优先级 ---->(能的基础上) 先后!
①nice值
//进程nice值设置
top + r + 进程id + 设置的值[-20,19]
为什么这样的nice值 会有限制?[-19,20)
限制的根本在于: 促进 各个进程 都能被cpu调度,cpu能更公平地被 使用!
可以想想,如果 一个特别高优先级的进程存在! 那么 一定会首先抢占cpu资源, 而它的执行时间又长又臭 并且大多数都在等待某种资源就绪!这样所谓的 "占着茅坑,不拉屎" 的行为 反而让其他进程 因为优先级低 无法 抢占cpu而无法被调度得到cpu资源。
(2)何为调度?
任何时刻,进程都有可能会因调度 从cpu上拿下来,换上另外一个 优先级更高 或者 因超时而被换下..... 那么,如果这个被 “切换下” 的进程如果再次被调度呢? 它原先执行到的历史数据是否该被丢弃了?
答案是肯定不行!
但又如何 解决 正在被执行的进程突然被切走呢? 保存 寄存器里的 临时数据! 也就是保存 寄存器里的上下文数据。(当然,这种数据最后被保存到哪里呢? 此时我们并不用太过关心)
由此,如果被切走的进程 再一次被cpu调度,也就会先读取 加载历史数据,而不会再重新 去执行该进程的代码。
"保护 数据是为了 恢复数据"
二、进程状态
在前半部分,我们有了一个概念。 cpu是有限的! 但是 进程 却是无限的! 如果此时 一个进程正在被cpu调度,那么 "正在运行"的程序呢? 他们所处的状态是什么?
(1)Linux下的进程状态;
为什么需要这么多进程状态? 实质上是因为 不同的场景有不同的需求! 任何操作系统都有不同的进程状态以满足 进程、用户、OS......的需求。
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */ 运行状态
"S (sleeping)", /* 1 */ 睡眠
"D (disk sleep)", /* 2 */ 深度睡眠
"T (stopped)", /* 4 */ 停止
"t (tracing stop)", /* 8 */ 追溯停止
"X (dead)", /* 16 */ 死亡
"Z (zombie)", /* 32 */ 僵尸
};
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态.
先来给大家搭一个宏观的 os系统调度概念;
①睡眠(S) 和 运行(R)
难道上面的图是 口说无凭吗?肯定不是!
从上面的描述来看可以有以下几个结论:
1.一个cpu 那么就一定会被OS维护一个运行队列! 方便调度。2.进程启动 让进程进入运行队列的本质是 把PCB连入到 运行队列中。
3.不是某个进程正在执行的状态为R , 而是连入运行队列中。
4.进程不仅仅只会等待 cpu的调度, 同样也会等待外设资源的就绪。
那么OS怎么知道 一个进程 该处于什么状态呢? 该怎样去读取 一个启动进程的状态呢?
答案当然是也在进程的 PCB当中 有标识。 更改进程状态的本质 就是更改 PCB里的标识符。
所谓的不同状态,就是进程 在不同的队列中! 等待某种资源就绪!
②停止(T) 和追溯停止(t)
上面说了 进程的 运行 (R) 与 阻塞(S)状态。那么如何模拟停止 与 追溯停止的状态呢?
这两种停止有何区别??
此时给进发送 信号SIGCONT 进程状态切换到T
如果我们此时让进程 又运行起来 只需要给进程发送SIGCONT
此时进程又开始跑了。此时心细的会发现 查看进状态的 字母S 没有了 “+” 这说明什么?
"+" 说明这是一个前台进程 。
因此当 对被停止的进程 唤醒后, 会自动让它从前台进程 切换到后台!
我们是否有一个疑问:当我们对程序进行调试的时候,该程序是什么状态?
我们打开gdb调试 并随便打一个断点;
t-tarcing stop 此时就出现了小t状态。
③挂起
什么是挂起?
任何进程状态的存在 都是有自己需求的场景!
1.用户层面:
用户在进程运行期间,发现有可疑问题时,希望进程暂时停止下来。但并不终止进程的。若进程处于运行状态,则暂停运行;若进程处于就绪状态,则不接受调度。
2.父进程需求:
父进程要查看、修改子进程或者协调各个子进程之间的活动,需要暂时挂起自己的子进程。
3.操作系统需求:
操作系统有时候需要挂起某些程序,然后检查系统中的资源使用情况,从而改善系统运行的性能。
4.对换需求:
为了缓和主存与系统其他资源的紧张情况,并且提高系统的性能。将处于等待状态的进程从主存换到外存。当等待事件的完成,换到外存的进程不具备运行的条件,不会进入调度队列。
我们模拟一下下面的场景;
现如今一个进程需要与外设磁盘交互
这种状态就被 称谓"挂起"状态。 当然内核代码肯定不会 让用户知道有这门子个操作。因为没必要。
其实本质上 , 挂起 是一种特殊的 阻塞状态。 这也就不难理解 有些说法是挂起阻塞状态。
④深度睡眠(D)
已经 有一个浅度睡眠s 为什么又来一个深度睡眠D状态呢?
与浅度睡眠想比较;
深度睡眠TASK_UNINTERRUPTIBLE:不可被信号唤醒;
不可被kill -9杀死 除非自己唤醒
浅度睡眠TASK_INTERRUPTIBLE:唤醒方式,等到需要的资源,响应信号;
可被kill -9杀死 和 信号唤醒
从另外一个层面来说,一旦进入深度睡眠 就算是操作系统 也不能对 处于深度睡眠的进程 做任何操作!
例如:
执行程序的过程中,假如在读取磁盘的过程中 ,又有代码需要从磁盘里读取,就会造成"嵌套休眠"(S)。那么此时不得不让这两个动作互不干扰!读磁盘的动作不可被打断。即只等待IO资源可用,不响应任何信号。
对于设置了深度睡眠的进程别无他法,因为系统层面上已经无法控制它了。
1.等待该进程执行完。
2.拔电源(或重启)
⑤僵尸进程(Z)
什么是僵尸进程?
僵尸进程 是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
我们来看看代码模拟;
//监视脚本
while :; do ps ajx | head -1 && ps ajx | grep test | grep -v 'grep'; echo '#################' ; sleep 1; done
那么有什么东西能够避免这些情况呢? 当然有的。但不是本篇内容所设计的。
⑥孤儿进程
什么是孤儿进程?
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
我们先来模拟一下场景;
而pid==1 的这个进程 就是操作系统
孤儿进程 vs 僵尸进程
理解了僵尸进程 也就理所应当地知道 为什么产生孤儿进程后 会自动被操作系统"领养"
并且 本来是前台的子进程 也会自动被 切换成后台运行。
总结:
①优先级确定先后,权限决定能否。
②调度并不是 只管清空数据 还应保存 寄存器里的数据 也就是上下文数据 以便恢复。
③什么是运行? 什么是挂起? 什么是休眠?
④孤儿进程 vs 僵尸进程