英国有哪些做折扣的网站,20元备案域名,区块链开发与应用专业,做销售用的免费发布信息网站文章目录 1. 进程状态1.1 课本名词提炼1.2 运行阻塞挂起1.2.1 运行1.2.2 阻塞1.2.3 挂起 1.3 理解内核链表1.4 Linux中的内核解释1.5 进程状态的查看1.6 Z(zombie)——僵尸进程1.6.1 创建僵尸进程1.6.2 僵尸进程的危害 1.7 孤儿进程 2. 进程优先级2.1 基本概念2.2 查… 文章目录 1. 进程状态1.1 课本名词提炼1.2 运行阻塞挂起1.2.1 运行1.2.2 阻塞1.2.3 挂起 1.3 理解内核链表1.4 Linux中的内核解释1.5 进程状态的查看1.6 Z(zombie)——僵尸进程1.6.1 创建僵尸进程1.6.2 僵尸进程的危害 1.7 孤儿进程 2. 进程优先级2.1 基本概念2.2 查看优先级2.3 PRI和NI2.4 手动更改进程的优先级2.5 竞争、独立、并行与并发 3. 进程切换与调度3.1 基本概念3.2 Linux中真实的调度算法O(1)调度算法重点3.2.1 初步了解3.2.2 详细介绍3.2.3 总结 1. 进程状态
1.1 课本名词提炼 1.2 运行阻塞挂起
1.2.1 运行 我们知道CPU的资源是有限的但每个进程都需要CPU来进行处理数据的操作于是CPU会在其内部维护一个叫做调度队列的数据结构每个进程按照一定的顺序依次排列在一起CPU根据先来先服务的算法依次从头至尾遍历执行这个调度队列中的所有进程这种调度算法也叫做FIFO。于是笼统的讲进程在调度队列中其状态就是运行状态。更细致的讲正在被CPU处理的进程叫做正在运行态在调度队列中等待的进程叫做就绪等待运行态。 1.2.2 阻塞 拿上图来说当1号进程被CPU执行的时候其内部进行了I/O申请于是1号进程就会去等待对应的I/O硬件资源设备就绪此时它会被链接到一个叫做等待队列的数据结构中此时我们把在等待队列中进程的状态叫做阻塞状态。 1.2.3 挂起 所谓挂起就是当前的系统内存资源非常紧张不足的时候此时操作系统会把对应一部分进程的代码和数据放入磁盘的交换分区内swap分区以此来调解缓和内存资源不足的问题。挂起分为1. 阻塞挂起 2. 就绪挂起。阻塞挂起顾名思义就是把在阻塞状态的进程的代码和数据放入swap分区内来缓解内存不足问题就绪挂起是在阻塞挂起后依然出现内存资源不足此时会把就绪状态的一部分的进程的代码和数据也给放入swap交换分区内。 阻塞挂起 就绪挂起
1.3 理解内核链表 进程状态的变化表现之一就是在不同的队列之间进行流动也就是对应数据结构的增删查改。那怎么能做到这种流动的呢也就是说进程是怎么做到既属于这个数据结构又属于另外一个数据结构的呢 这得涉及到具体的操作系统内核中进程的设计以Linux内核为例其在进程PCB(task_struct)的设计中采用了增加 struct list_head* 结构体指针变量的形式其结构如下所示
struct list_head {struct list_head* prev, *next;
};具体的来说在 struct task_struct 结构体内部采用组合的方式增加了struct list_head* 结构体指针作为成员变量其内部是两个指针可以指向前驱与后继进程结点对应着上述就绪队列就可以取名为struct list_head* run_queue同理等待队列就是struct list_head* wait_queue。于是判断一个进程到底属于哪个队列就可以看其task_struct内对应哪个list_head*的指向不为空即可。这也是一个先描述再组织的过程如下图所示
1.4 Linux中的内核解释 为了弄明白正在运行的进程是什么意思我们需要知道进程的不同状态。一个进程可以有几个状态在Linux内核里进程有时候也叫做任务。体现在具体的代码中也就是Linux中的task_structPCB中所谓的进程状态就是task_struct中的一个整型变量。可以对该整型变量进行数值判断来判断该进程此时位于什么状态。 进程状态在kernel源代码定义如下
/*
*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信号给进程来停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。X死亡状态dead这个状态只是一个返回状态你不会在任务列表里看到这个状态。
下一小节是具体的实践操作观察。
1.5 进程状态的查看 具体的进程查看可以使用 ps aux / ps ajx 命令来查看 • a显示一个终端所有的进程包括其他用户的进程。 • x显示没有控制终端的进程例如后台运行的守护进程。 • j显示进程归属的进程组ID、会话ID、父进程ID以及与作业控制相关的信息。 • u以用户为中心的格式显示进程信息提供进程的详细信息如用户、CPU和内存使用情况等。 区别
ps aux ps ajx ps命令查看的时候是静态查看的也就是说它查看的当前进程状态的一个快照。为了方便观察与述说我们可以使用以下脚本命令(grep test是筛选含test的字段进行打印test是可执行程序名)来实施每隔一秒进行自动监测 while :; do ps ajx |head -1; ps ajx | grep test; sleep 1; done 查看运行状态——R
输入以下代码
#includestdio.h
int main()
{while (1) ;return 0;
}查看
查看阻塞状态——S
输入以下代码
#includestdio.h
int main()
{int x 0;scanf(%d, x); // 进行I/O申请所以是阻塞return 0;
}查看 测试printf
#includestdio.h
int main()
{while(1)printf(hello shuaiming\n);return 0;
}查看 分析对上面情况其实很好理解申请I/O时是阻塞状态当申请I/O完了之后就是运行状态。打个比方程序每打印一条语句呈现在窗口上所花费的时间是1秒这其中I/O操作是占了大头可能占到了百分之80的比例其中只有20的比例是在运行程序而我们设置的脚本命令是每隔1秒监测一次也就是说我们的进程是不断的在运行队列和阻塞队列之间来回的进行切换的即存在一定的运气成分能让我们监测时同时监测到R和S状态这与我们的理论并不冲突。
对于其中状态后跟的号解释如R其实就是表示它是在前台运行没有就是在后台运行我们可以手动让程序在后台运行只需运行时最后跟上即可如 ./test 查看
其余状态
T当程序一直在窗口中跑时我们输入ctrlz 就可以停止程序此时对应的状态就是T状态t程序进行调试时对应的状态D不可进行杀死的阻塞状态这个状态是必须会得到某种硬件资源后方可解除D状态继续运行。其中在等待硬件资源时是不可被杀死而S状态在等待硬件资源时是可以被杀死X是一种返回状态不能被观察到Z僵尸状态具体见下小节
1.6 Z(zombie)——僵尸进程 一个人离奇死亡法医会对他进行抽样检测获取死亡信息警察会将法医获取的死亡信息告知家属然后再处理尸体。其中人在死亡直至法医获取死亡信息的这一时间段内我们称这人为僵死(僵尸)状态。同理在计算机的世界内有个神奇的进程叫作僵尸进程它是子进程在已经完成所有的任务后将要退出销毁了此时它需要将自己完成任务的情况信息返回给父进程也就是等待着父进程给他“验尸”在父进程收集完它所有的信息后子进程才会被完全销毁。在完成所有任务后等待“验尸”的这一时间过程中我们称该进程为僵尸进程。 有僵尸进程的概念就是为了获取子进程退出的信息。 1.6.1 创建僵尸进程
如下代码我们创建一个维持30秒的僵尸进程
#include stdio.h
#include stdlib.h
#include sys/types.h
#include unistd.h
int main()
{pid_t id fork();if (id 0) {perror(fork);return 1;}else if (id 0) { //parentprintf(parent[%d] is sleeping...\n, getpid());sleep(30);}else {printf(child[%d] is begin Z...\n, getpid());sleep(5);exit(EXIT_SUCCESS);}return 0;
}查看
1.6.2 僵尸进程的危害
如果父进程一直不管不进行回收那么Z状态一直存在这种情况势必会造成内存资源浪费也就是内存泄漏此外Z状态也是状态也需要操作系统去维护这也会造成时间资源上的浪费
1.7 孤儿进程 当父进程提前退出但此时子进程却没有退出此时子进程就成了 “孤儿”等到子进程退出时会变成僵尸进程此时如果没有父进程来“验尸”的话那么又会造成资源的浪费。为了避免这种情况的发生Linux中引入了孤儿进程的概念即父进程提前退出对应的子进程会被1号进程给领养同时退出时会由1号进程来进行回收。 此时被领养后的孤儿进程会由前台进程变成后台进程。 代码样例
#include stdio.h
#include unistd.h
#include stdlib.h
int main()
{while (1){pid_t id fork();if (id 0) {perror(fork);return 1;}else if (id 0) {//childprintf(I am child, pid : %d\n, getpid());sleep(1);}else {//parentprintf(I am parent, pid: %d\n, getpid());sleep(1);exit(0);}}return 0;
}查看
2. 进程优先级 一个计算机系统中的CPU的资源是有限的而某个时间段要执行很多的进程任务这些进程任务中有的很重要有的又相对不是那么重要如果不对这些进程任务进行合理的安排去让CPU执行的话那么那些急需响应的进程任务就不能及时响应达不到用户所期待的效果用户的使用感就会下降于是在具体的Linux操作系统中引入了优先级的概念。 2.1 基本概念 CPU资源分配的先后顺序就是指进程的优先级priority。优先级高的进程有优先执行权利。配置进程优先级对多任务环境的linux很有用可以改善系统性能。还可以把进程运行到指定的CPU上这样⼀来把不重要的进程安排到某个CPU可以大大改善系统整体的性能。 注意优先级与权限是两种概念优先级是指能得到资源的先后顺序而权限是指是否能得到该资源。
2.2 查看优先级
输入ps -l 命令
注意到一些重要信息
UID执行者的身份PID进程的唯一标识符PPID该进程对应父进程的标识符PRI代表这个进程可被执行的优先级值越小优先级越高NI代表这个进程的nice值也就是对应优先级的修正值
2.3 PRI和NI PRI值越小越快被执行那么加入nice值后将会使得PRI变为PRI(new)PRI(old)nice当nice值为负值的时候那么该程序的优先级值将变小即其优先级会变高则其越快被执行反之相反在Linux下调整进程优先级就是调整进程nice值Linux操作系统是基于时间片的分时操作系统为了考虑公平性优先级可能变化但变化的幅度不能太大nice其取值范围是-20至19一共40个级别。 需要强调的是进程的nice值不是进程的优先级他们不是一个概念但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正数据。
2.4 手动更改进程的优先级
使用top命令更改已存在进程的nice值具体操作如下 top进入top后按“r”‒输入进程PID‒输入nice值 进入top 输入r 输入对应要修改的进程PID按下回车 最后输入nice修正值
查看结果
注意优先级设置的不合理会导致优先级低的进程长时间得不到CPU资源从而造成进程饥饿。
2.5 竞争、独立、并行与并发 竞争性:系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级独立性:多进程运行需要独享各种资源多进程运行期间互不干扰并行:多个进程在多个CPU下分别运行即在同一时刻内是有多个进程在同时执行任务这便称之为并行并发:多个进程在一个CPU下采用进程切换的方式即在一个时间段之内让多个进程都得以推进称之为并发。从微观时间上来看依旧是一个进程执行完再执行下一个进程从宏观时间上来看则像是多个进程在同时运行一样 3. 进程切换与调度
3.1 基本概念
简单来说 Linux操作系统是基于时间片的分时操作系统也就是说一个进程的时间片用完了它就不会再占用CPU了那么它就会被CPU给替换出来让下一个新的进程进入CPU工作这一过程就叫做进程切换。 这个过程也叫做CPU上下文切换更加专业详细一点来说 CPU上下文切换其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器, 并开始下一个任务的运行这一过程就是context switch。 参考一下Linux内核0.11代码 注意 时间片当代计算机都是分时操作系统它保证了进程运行的公平性每个进程都有它合适的时间片(其实就是一个计数器)。时间片用完进程就被操作系统从CPU中剥离下来。 所以一个死循环程序它并不会一直占用CPU它的时间片用完了也自然就会停止运行只不过我们看它似乎一直在运行的原因就是时间片设计的时间间隔很小操作系统进行进程切换的速度又很快导致人为的认为它一直在运行。 3.2 Linux中真实的调度算法O(1)调度算法重点
3.2.1 初步了解
下图是Linux2.6内核中调度队列的数据结构先整体认识下之间的关系后续会有更全面讲解 3.2.2 详细介绍
一个CPU拥有一个runqueue就绪队列runqueue内部含有一个存放两个元素的优先级数组prio_array_t这两个元素是一个结构体类型分别表示活动队列与过期队列这两个队列的结构是一致的。
更具体的来看 active指针永远指向活动队列expired指针永远指向过期队列nr_active表示当前活动队列中的进程数在活动队列中这些进程就是活跃的是时间片还没有用完将要准备执行的。过期队列中就是过期的是当前一个调度内时间片用完了但本身任务还没有完全执行完的。具体的进程是存放在一个叫做queue的指针数组中的其数组大小是140正好一一对应140个优先级这个指针数组实际上就是一个开散列的哈希表采用了链地址法的形式根据每个进程的优先级对应映射在其对应的数组下标。注意不是说进程的数量是只有140个是说优先级位数是140个。也就是说一个数组下标对应一个优先级的进程队列。bitmap[5]其实就是另一个数据结构——位图。其内部存在5个32位的int整型变量5*32160也就是说用了160个比特位去对应记录每个优先级的进程队列中是否有进程比特位数值为1表示当前比特位对应的进程队列有数据为0则表示没有。于是就可以通过进行一系列位运算来判断哪个优先级的进程队列中还有进程。然后运用哈希表的哈希函数进行快速索引找到对应不为空的进程队列去拿进程进行切换与调度。注意不是说有160个比特位就要全部用到只是用其中的140个即可。 对于140个优先级的进程队列实质我们关心的只有后40个也就是对应下标范围[100,139]这个也叫做普通优先级正好对应了上面所说的nice值的范围[-20,19]。其余100个都叫做实时优先级实时优先级的优先顺序又是高于普通优先级的正如它的名字一样它是实时的动态的这个归系统调度管理。
普通优先级100~139我们都是普通的优先级想想nice值的取值范围可与之对应实时优先级0~99不关心
对于为什么要划分实时与普通优先级我的理解是 Linux内核设计为了兼顾全面性在整体基调是分时系统的前提下内部又引入了实时系统实时系统是为了应对那些强制性和高响应硬实施的进程任务而设计的可以在一个调度内动态的插入进程任务于实时优先级的队列中也就是重要的先来直接插队。而后40个普通优先级的进程队列则是静态的基于时间片的公平调度一个进程时间片完成了但还有任务没完全完成就会放入过期队列中同时在一个调度循环内如果有更高优先级的进程来了它不会像实时优先级那样直接插队而是直接按照优先级去入到过期队列中等待下一个调度循环的到来。 具体如下图
当然随着时间的推移进程会根据优先级的顺序挨个执行然后时间片完了的但还没完全执行完的进程任务就会放入过期队列中同时在这段执行的时间段内也会有新的进程加入进来于是便有了下图 时间向前流动那么活动队列中的进程数会越来越少过期队列中的进程数会越来越多直至活动队列没有进程数时也就是nr_active0时此时意味着一个调度循环执行完成需要执行下一个调度循环也就是要执行过期队列中还没执行完的进程此时只需交换runqueue中的active与expired指针的指向即可完成过期队列向活动队列的切换如下图 思考为什么非要加上bitmap[5]来进行对应的映射然后找非0值的比特位进行索引我的优先级运行队列queue[140]的长度也是固定的挨个进行遍历查找也能做到O(1)的时间复杂度
回答 即使你的queue的长度时固定但从局部时间开销来看每次都去遍历140单位的长度可能有些下标对应的优先级有很多进程也可能有些连续很长下标范围内没有进程。从整体来看我们只是需要去访问那些有进程的下标那些没有进程的下标我们不用关心。如果挨个遍历的从局部来看就是个O(N)而且你的前100个长度是动态的实时的可能会有新进程进行插队这样挨个进行遍历的操作就不能很好的处理实时性的要求。注意我们把位运算看成是原子操作也就是说位运算非常的快。 3.2.3 总结 通过active指针永远指向活动队列这一特性我们只需判断活动队列中的nr_active值是否为0即可不为0就去通过对应的bitmap[5]位图进行一系列位运算操作得到不为空的对应下标然后通过哈希函数进行快速索引进而依次得到优先级从高到低排列的进程不断的进行切换与调度直至nr_active的值为0此时一个调度循环就算完成了接着我们再交换active与expired指针的指向进行下一个循环周而复始。从时间上来看每次切换与调度一个进程进出CPU的时间消耗就是O(1)。这就是Linux中的O(1)调度算法。