文章目录
- 进程(二)
- Linux的进程状态
- R (running)运行态
- S (sleeping)阻塞状态
- D (disk sleep)深度睡眠
- T(stopped)状态
- X(dead)状态
- Z(zombie)状态
- 僵尸进程
- 孤儿进程
- 进程优先级
- 更改优先级
进程(二)
前文我们知道了,传统的进程状态为:运行态、阻塞态、挂起,这是教科书上对于操作系统的进程状态描述,但是我们知道,不同的操作系统对于其自身的状态定义和描述是不同的,我们接下来看看Linux中的进程状态是分为哪几种?以及对于优先级的了解。
Linux的进程状态
我们先通过一段Linux的源码来了解一下,Linux有几种进程状态
/*
* 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)运行态
运行态R,在前文已经很详细描述了,其进程的运行的流程,这里简单描述
将准备好运行的进程(实际上PCB即可)放在runqueue队列中,然后通过调度器来选取要将哪一个进程放在cpu上执行,当是时间片结束之后,就将该进程从cpu上拿下来,然后放在runqueue队列的队尾,再次排队,这就是依据时间片的轮转执行进程。
PCB排队,其对应的代码和数据,只有轮到该进程时候,才会调用。
PCB存在,进程就存在,控制一个进程就是控制其PCB
在Linux中R状态又分为两种表示形式,R和R+
区别:
R: 表示的是该进程在后台运行,此时我们只能通过kill -9 PID命令,或者是kill -19 PID来使得该进程结束或停止
R+:表示的是该进程在前台运行,此时我们在进程结束之前无法输入其他指令
选定执行方式:
./proc 默认前台运行proc进程
./proc & 默认后台运行proc进程
注意:如果程序运行需要I/O时,一般显示S+,表示阻塞状态,虽然先在进程是一直在运行,但是由于CPU处理数据太快,外设输入/输出太慢,导致进程大部分时间都是在等待,所以一般为S+
S (sleeping)阻塞状态
前文也对于阻塞状态做了详细的解释,我们这边也简单描述一下
当进程需要一定的外设输入/输出的时,也就是需要等待资源,这个时候进程并没有准备好被调用,会去找对应的外设,排队,实际上也是PCB排队,当得到资源之后,就会被唤醒,调到runqueue中,由阻塞态转换为运行态。
类型:
S+:表示的是前台,进程处于阻塞状态
S: 表示的是后台,进程处于阻塞状态
注意:此时的阻塞状态,为睡眠,是可以被唤醒的(等资源到位之后就被唤醒,转换成运行态)
D (disk sleep)深度睡眠
深度睡眠,此时进程阻塞,其对应的代码和数据是可以有可能被挂起的,而且处于当前状态是无法被唤醒,直到状态结束
D状态产生的原因:
责任判定:
进程:已执行自己的全部任务,且自己只是听从了操作系统的话,结束进程。
操作系统:为了大局着想,此时内存空间资源不足,得释放一些空间,以供其他进程使用,此进程此时比较空闲,我觉得是可以释放的,所以将其杀死,释放该进程,这是执行自己管理软硬件的能力,合理分配资源的职责。
硬盘:我也想反馈信息给进程,但是此时进程已经被杀死,我不知道这个资源到底是作何使用,我也有很多进程是要进行读写的,也需要空间来存储数据,对于该进程存储的数据,我只能按照规定,删除该数据,这是正常操作。
进程、操作系统、硬盘,都认为自己没有责任,而且陈词有理有据。所以应该不是某一部分的责任,应该是设计的问题。
如何解决该设计的问题呢?
我们要实现的目的是,只需要在进程等待磁盘写入完毕的期间,该进程不被任何人给杀掉即可
于是,我们加入的D(disk sleep)状态,磁盘睡眠,也就是深度睡眠状态,规定,处于该状态的进程不允许任何人来打扰,就算是操作系统也不行,在该状态无法被kill
总结:D状态表示进程正在等待某些资源的释放,通常是由于IO操作(如磁盘读写)过程中发生阻塞引起的。
D状态属于阻塞态,为深度睡眠,不可以被唤醒
S和D的区别:
- S一定是在等待某一种资源,等待唤醒成为运行态
- D可能是在等待某一种资源,也可能是正在被其他资源控制
T(stopped)状态
T状态,就是停止该进程,使得进程暂停运行,我们对于该状态有两种方法能实现
前台:
- 对于前台运行的程序,我们可以使用ctrl+z来使得进程停止运行,从而转换成T状态
- 对于前台运行的程序,我们可以使用kill -19 PID来使得进程停止运行,从而转换成T状态,通过kill -18 PID 来恢复运行
后台:
- 对于后台运行的程序我们只能使用kill -19 和 kill -18的方法
注意:T和t都是表示进程停止运行,没有太大的差别,所以我们就不讲t状态了
X(dead)状态
X状态,表示的是该进程被杀死,这是一个瞬时状态,我们不好观察到
Z(zombie)状态
Z状态就是僵尸状态,在进程被杀死之前,会先进入Z状态,由父进程得到该进程保留的信息之后,才会交给X状态,从而结束进程
使用kill -9 PID 命令杀死进程,就是先Z然后再X
将进程已经死掉,但是此时没有父进程来进行获取该进程的返回信息,此时操作系统要一直保留该状态,称为Z状态
总结:
- 进程退出的时候,如果父进程没有主动回收子进程信息,子进程会一直让自己处于僵尸状态,就需要对于该进程的数据进行维护,进程基本信息存放在PCB中,所以要一直维护PCB进程的相关资源和代码数据信息,尤其的==task_struct结构体不能被释放==
- 僵尸进程使得内存一直被占用,无法释放(如果父进程一直不回收),那么会导致==内存泄漏==
- 僵尸状态的原因是,进程退出之前是需要告诉父进程,你交给我的任务我完成了没有,但是父进程一直没有获取子进程的结束信息,所以子进程要一直处于Z状态
那么就有人会讲,为什么./proc,结束后不会看到Z状态呢?
这是因为,被其父进程给接收信息,然后结束进程了,其父进程为bash,会主动在proc进程结束后回收信息,所以我们观察不到Z状态和X状态,然后proc进程就结束了
那么为什么bash不能处理proc的子进程呢?爷爷管孙子不行吗?
任何父进程只对于其子进程负责
僵尸进程
僵尸进程就是该程序运行之后,其子进程没有被父进程回收信息,使得子进程称为僵尸状态。
僵尸进程的代码为:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("当前进程的PID为:%d , PPID为: %d\n",getpid(),getppid());
pid_t id = fork();
if(id==0)
{
//表示这是一个子进程
int cnt=5;
while(cnt--)
{
printf("子进程的PID为:%d , PPID为:%d\n",getpid(),getppid());
sleep(1);
}
}
else if(id>0)
{
//这是父进程
while(1)
{
printf("父进程的PID为:%d , PPID为:%d\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
僵尸进程的主要危害就是对于内存一直占用,且没有回收,导致内存泄漏。
因为需要维护该Z状态,就要维护其PCB(存储进程的基础信息),PCB不能被释放,PCB占据不少的空间。从而导致内存泄漏
孤儿进程
孤儿进程顾名思义就是父进程提前退出,而子进程还在运行,子进程为孤儿进程
父进程已经退出,被bash回收处理,子进程如果再想退出,怎么办?
父进程退出后,子进程会被1号进程(init/systemd)领养所以init/systemd来回收子进程
init和systemd是不同版本下1号进程的不同叫法
孤儿进程被操作系统所领养,1号进程init表示的是操作系统。
为什么不是bash领养呢?
因为任何进程之对于子进程进行回收,所以父进程由bash回收管理,所以孤儿进程不能被bash回收(父亲只管其自己儿子,不会管孙子),这是代码层面不允许,这个时候,操作系统根据其内核来回收
进程优先级
查看进程优先级的方法,ps -al 显示所有当前进程信息
优先级是指对于资源的访问,谁先访问,谁后访问,因为资源是优先的,进程是多个的,注定了,进程之间是竞争关系,所以操作系统必须保证大家良性竞争,确认优先级。
如果我们进程长时间得不到CPU资源,该进程的代码长时间无法得到推荐,就导致了该进程的饥饿问题。
所以优先级的存在,就是为了调度器能够更好的合理利用CPU资源,来公平调度不同的进程。
基本概念
cpu资源分配的先后顺序,就是指进程的优先级
优先级高的进程有优先执行的权利,配置进程优先级对于多任务环境的Linux很有用,可能改善系统性能
可以将进程运行到指定的CPU上,这样不重要的进程放在其他CPU上,可以大大改善系统性能
主要的信息
- UID:代表执行者的身份 (用户ID)
- PID:当前进程的进程编号
- PPID:当前进程的父进程的编号
- PRI:代表这个进程可被执行的优先级,其值越小越早被执行
- NI:代表当前进程的nice值
优先级PRI,越小优先级级别越高,NI即nice值,是用来修正优先级数值
PRI(new)=PRI(old)+nice
默认PRI(old)= 80
当nice数值变小,PRI会变小,优先级会变高,便会很快被执行,所以调整进程优先级,实际上就是调整nice数值。
nice的数值范围为-20到19,一共40个级别
注意:实际上进程什么时候被运行,都是根据调度器来控制的,调整优先级,到底什么时候调度不确定,所以只能说是可能会改善系统性能
更改优先级
有多种方式可以更改优先级,也就是说有多种方式能更改nice数值。
以下方式来更改进程优先级
- 对于进程使用nice 或者 renice指令进行更改nice数值,进而更改PRI数值。
- root用户下,在top(任务资源管理器)中可以按 “ r ” ,之后输入进程PID,然后输入nice数值
注意:
- root用户更改的nice范围为[-20,19],非root用户的可更改的nice范围为[0,19]
- 非root用户只能将nice越调越高,root用户随意
- 非root用户,只能调自己进程的nice值,root用户随意
更改nice数值,如果输入大于或者小于[-20,19]这个范围,取极值
nice指令
用法:对于未执行的进程,在执行前输入nice -n + 指令(进程)
renice
语法:renice [number] PID
renice是对于已经运行的进程的nice数值进行更改,number表示输入新的nice数值
top更改nice
top可以显示当前的所有进程运行情况
只有root用户才有权利通过top来更改指定PID的nice数值
非root用户,top更改nice会有以下错误
优先级对于操作系统很重要,但是操作时候没有那么重要
操作系统是如何根据优先级,开展的调度呢?
基于Linux内核2.6的O(1)调度算法
主要是,我们通过优先级的加入对于runqueue的结构,再次了解调用进程的过程。
- [100,139] 是基于时间片轮转,公平调度的进程,强调公平
- [0,99] 是实时进程,强调及时响应,在当代操作系统中一般不会使用,但是某些需要实时进程的是需要的。