[Linux]进程状态
文章目录
- [Linux]进程状态
- 进程状态的概念
- 阻塞状态
- 挂起状态
- Linux下的进程状态
- 孤儿进程
进程状态的概念
了解进程状态前,首先要知道一个正在运行的进程不是无时无刻都在CPU上进行运算的,而是在操作系统的管理下,和其他正在运行的进程轮流循环使用CPU,而操作系统管理进程是否需要放到CPU上进行计算,所依据的就是进程状态。
阻塞状态
阻塞状态是进程因为等待某种资源就绪,而导致的一种不推进的状态。
阻塞状态从主观上给人的感觉就是进程“卡”住了,比如进程在下载某一个软件过程中,网络断了,进程下载软件的过程就会"卡"住,进程需要等待网络这种资源就绪,才能继续推进。因此进程处于阻塞状态时,一定是在等待某种资源。进程进入阻塞状态是想要通过等待的方式,等具体的资源被别人使用完后,给自身使用。
由于操作系统要管理各种各样的硬件,因此操作系统需要用某种结构的描述硬件,然后为了方便管理要将这些结构组织起来,描述这些硬件时,会有一个描述信息就是等待队列,这个队列会记录正在等待当前硬件资源的进程的PCB,操作系统就是通过这样的大致原理实现的让进程进入阻塞状态:
挂起状态
挂起状态是进程等待某种资源就绪时,操作系统系统将进程在内存中的代码和数据释放的状态。
挂起状态下进程的PCB还在硬件的等待队列上,操作系统认为进程等待的资源需要很长时间才会准备就绪时,就会释放进程在内存中的代码和数据,让多出来的内存空间用于做其他的事情,提高内存的利用率,当进程等待的资源准备就绪后,操作系统就会把进程的代码和数据再加载到内存中,将进程启动起来。
Linux下的进程状态
Linux系统下,在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 */
};
说明: Linux下的进程会在task_struct设置一个变量来记录进程状态。
R状态
R状态就是运行状态,说明进程的task_struct在操作系统中的运行状态等待队列之中,在操作系统轮循执行处于运行状态的进程的代码的过程中,被执行。
注意: 运行状态的进程不代表一直都在CPU上执行,而是和其他处于运行状态的进程一样,轮循的被执行。
为了验证R状态,编写如下代码:
#include <stdio.h>
int main()
{
while(1)
{}
return 0;
}
在Linux系统下运行程序并查看进程状态:
由于这段代码的执行不需要任何资源,不会进入阻塞状态,因此会一直保持运行状态。
S状态
S状态是可中断休眠状态,可中断休眠状态是Linux系统中阻塞状态的一种。
为了验证S状态,编写如下代码:
#include <stdio.h>
int main()
{
while(1)
{
printf("hello world\n");
}
return 0;
}
在Linux系统下运行程序并查看进程状态:
在这段代码中虽然程序是一个死循环操作,但是由于循环结构的代码运行很快,而将数据打印到屏幕上很慢,因此造成了程序运行时,大部分的时候都是在等待打印屏幕的资源,因此查询状态时大部分时间查询到的都是S状态。另外可中断休眠状态下的进程可以选择输入ctrl+z
终止进程。
D状态
D状态是不可中断休眠状态,不可中断休眠状态是Linux系统中阻塞状态的一种。
D状态是在进程将数据从内存传输到磁盘时,磁盘的压力过大,导致传输数据的速度很慢,进程必须等待数据传输完成处于休眠状态,为了避免操作系统将这个处于休眠状态的进程杀死,因此将进程设置成不可中断休眠状态。D状态不是一种常见的状态,如果进程处于D状态一般说明计算机磁盘压力过大。
T状态
T状态是暂停状态,暂停状态下进程不再继续运行。
为了验证T状态,编写如下代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
int i = 1;
while(1)
{
printf("hello world %d\n", i);
i++;
sleep(1); //Linux系统提供的休眠函数,头文件是unistd.h
}
return 0;
}
在Linux系统下运行程序使用kill -19 进程id
暂停进程并查看进程状态:
输入kill -18 进程id
可以重启进程:
补充: Linux下进程状态后面的+的含义是这是一个前台进程:
进程状态中没有+的是后台进程:
在Linux下前台进程运行时,bash是失效的,而在后台进程运行时bash是可用的,另外后台进程可以使用kill -9 进程id
来关闭。
t状态
t状态是追踪式暂停状态,是T状态的一种特例。
为了验证t状态,可以借用gdb工具,使用gdb开启调试一个程序:
Z状态
Z状态被称作僵尸状态,僵尸状态下进程已经完全停止了,但是保留了进程的task_struct和部分数据。进程进入僵尸状态是为了了解进程的运行结果是否出现问题,因为保留的数据中包含退出码等信息,而退出码是用于判断进程运行结果是否出现问题的数据。进程终止后都会进入僵尸状态等待父进程的回收处理。
为了验证T状态,编写如下代码:
#include <stdio.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);
}
}
return 0;
}
在Linux系统下运行程序使用kill -9 进程id
杀死子进程并查看进程状态:
僵尸进程的危害: 由于僵尸状态下进程的task_struct和数据得到保留,因此会占用内存空间,如果创建多个子进程不回收就会造成大量的空间被占用,造成严重的内存泄露问题。
X状态
X状态被称作死亡状态,死亡状态下进程完成停止了,并且操作系统会很快将进程的task_struct和代码和数据回收释放。
孤儿进程
孤儿进程是在进程运行时,父进程停止,父进程转为操作系统(1号进程),被操作系统管理的进程。
为了验证孤儿进程,编写如下代码:
#include <stdio.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)
{
//父进程
int cnt = 5;
while(1)
{
printf("我是父进程,我的pid:%d, 我的ppid:%d\n", getpid(), getppid());
sleep(1);
if (--cnt == 0) break;
}
}
return 0;
}
为了方便编译,编写如下makefile文件:
myproc:myproc.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f myproc
准备好代码和makefile文件后编译得到程序,然后运行程序,并使用while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; done
指令来每隔一秒查询一次进程状态:
进程myproc中的父进程的父进程是bash,它停止后进入僵尸状态后,bash作为其父进程会进行回收操作,因此无法看到其僵尸状态,进程myproc中的子进程仍在进行,但是原有父进程停止了,需要有新的父进程来回收它,否则会造成其停止后,僵尸状态无法回收的情况。
变成孤儿进程后,就变成了后台进程,可以选择使用killall 进程名
指令杀死同一进程名的所有进程。