文章目录
- 1. 进程状态
- 2. Linux的进程状态
- 3. 僵尸进程
- 4. 孤儿进程
- 5. 进程优先级
1. 进程状态
为了更深入地了解进程,我们需要知道进程的不同状态。
不同的操作系统,对于进程状态有着不同的说法,如:运行、阻塞、挂起、新建、就绪、等待、死亡等。进程的多种状态,本质上是为了满足不同的应用场景。这里我们了解几种普遍的状态:
- 运行状态
每个CPU都只有一个运行队列,进程的执行需要入队,而让进程入队的本质是将该进程的task_struct结构体对象放入运行队列中。在CPU的运行队列中的进程,其状态称为运行状态。
- 阻塞状态
进程在运行过程中会占用CPU和其他硬件(如外设)资源,CPU与其他硬件相比,二者处理进程请求的速度相差甚远,如果等待其他硬件处理完毕,CPU再执行下一个进程,那么系统的整体速度会大幅降低;
所以:当CPU发现运行队列中进程需要占用其他硬件资源时,会将该进程移至被占用硬件的等待队列(wait_queue)中进行排队,这种进程的状态称为阻塞状态。
- 挂起状态
我们知道进程 = 进程的数据结构 + 磁盘上的代码和数据,而进程执行时会将磁盘上对应的代码和数据加载到内存;硬件响应速度慢,当有大量进程访问硬件时会产生较多的阻塞进程,这些进程的代码和数据在短期内不会被使用,如果一直在内存会占用很多内存资源。此时操作系统会将其代码和数据移至磁盘,仅保留PCB,以节省空间,这种进程的状态称为挂起状态。
挂起状态也可以叫做阻塞挂起状态,所以阻塞状态不一定是挂起状态,挂起状态一定是阻塞状态。
总结:
- 状态是进程的内部属性,具体地说,状态是PCB中的一个整数值。
- 进程的状态,本质是进程在不同的队列中,等待某种资源
- 进程的PCB在运行队列(run_queue)中,其状态就是运行状态(R),不是进程正在运行才是运行状态
- 进程既占用CPU资源又占用其他硬件(如外设)资源
2. Linux的进程状态
在Linux内核里,进程有时候也叫做任务(task)。
下面是状态在kernel源代码里的定义:
- R运行状态(running):
进程的状态为R,并不意味着进程一定在运行中,它表明进程要么是在运行中要么是在运行队列里。
test.c
#include <stdio.h>
int main()
{
while (1);
return 0;
}
+表示前台进程,没有+表示后台进程。前台进程运行时,无法输入指令,Ctrl + C可以终止进程;后台进程运行时,可以输入指令但Ctrl + C不能终止进程,可以使用kill -9 PID
杀死进程。
- S睡眠状态(sleeping):
意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
mytest.c
#include <stdio.h>
int main()
{
int cnt = 0;
while (1)
{
printf("%d\n",cnt++);
}
return 0;
}
S状态是浅度睡眠状态,是阻塞状态的一种,是可以被终止的进程状态。虽然数字一直在打印,但大部分时间在等显示器IO就绪,只有小部分时间在执行打印代码。所以查询该进程的状态大概率是S状态,上图碰巧查询到了R状态。
- D磁盘休眠状态(Disk sleep):
有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
D状态一般在高IO的情况下才会发生,此状态下的进程处于深度睡眠,无法被杀死,只有通过断电或等待进程自己”醒来“的方式才能中断D状态。 - T停止状态(stopped):
T状态是阻塞状态的一种。可以通过发送 SIGSTOP 信号kill -19 PID
给进程来停止进程。这个被暂停的进程可 以通过发送 SIGCONT 信号kill -18 PID
让进程继续运行。
- X死亡状态(dead):
这个状态只是一个返回状态,无法在任务列表里看到这个状态。
3. 僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("子进程:PID: %d PPID: %d\n", getpid(), getppid());
sleep(1);
exit(1);
}
}
else if (id > 0)
{
while (1)
{
printf("父进程:PID: %d PPID: %d\n", getpid(), getppid());
sleep(1);
}
}
else {
perror("fork失败");
exit(-1);
}
return 0;
}
下面这行脚本可以实时监控进程:
while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1;done
- 僵尸进程危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程)你交给我的任务,我办的怎么样了。如果父进程一直不读取,那子进程就一直处于Z状态
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护
- 一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,类比C中定义一个结构体变量,是要在内存的某个位置开辟空间
- 僵尸进程会导致内存泄漏
4. 孤儿进程
父进程先退出,子进程就称为孤儿进程
那么父进程提前退出,子进程后退出,进入Z状态之后,该如何处理呢?
此时孤儿进程会被1号init进程领养,子进程的资源由init进程回收
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
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);
exit(1);
}
}
else {
perror("fork失败");
exit(-1);
}
return 0;
}
5. 进程优先级
-
基本概念
- cpu资源分配的先后顺序,就是指进程的优先权(priority)
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
-
查看进程优先级
在linux或者unix系统中,用ps -al
命令则会类似输出以下几个内容
几个重要信息:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI(默认80) :代表这个进程可被执行的优先级,其值越小越早被执行
NI (默认0):代表这个进程的nice值
- PRI和NI
PRI是进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
NI就是nice值,其表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,加入nice值后,会使得PRI变为:新的PRI = 默认PRI + nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。 在Linux下,调整进程优先级,就是调整进程nice值。 nice的取值范围是-20至19,一共40个级别
- 修改nice值
执行sudo top
进入top
后按“r
” -> 输入进程PID -> 输入nice值