🌟hello,各位读者大大们你们好呀🌟
🍭🍭系列专栏:【Linux初阶】
✒️✒️本篇内容:进程状态的概念,进程状态在普遍操作系统层面和Linux层面的理解,Linux常见进程状态,进程状态的两种查看方法,ls>makefile指令,makefile$@^特殊符号的应用方法展示,僵尸进程和孤儿进程的讲解
🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-
目录
一、进程状态
1.普遍操作系统层面理解
(1)运行状态
(2)阻塞状态
(3)挂起状态
(4)状态合作
2.Linux层面理解
(1)R运行状态(running)
(2)S睡眠状态(sleeping)
(3)D磁盘休眠状态(Disk sleep)
(4)T停止状态(stopped)
(5)t - 被追踪状态 (tracing stop)
(6)X死亡状态(dead)
(7)Z(zombie) - 僵死状态
二、进程状态查看 & makefile特殊符号$@^应用
1.基础进程查看方法
2.进程循环打印查看方法
3.ls > makefile(重定向)
4.makefile特殊符号$@^应用
三、特殊状态下的进程 - 僵尸进程&孤儿进程
1.僵尸进程
(1)僵尸进程的概念
(2)僵尸进程的危害
2.孤儿进程
一、进程状态
进程状态有很多,运行、挂起、阻塞、死亡等都属于进程状态。进程那么多状态,实际上都是为了满足不同的运行场景。我们可以从普遍的操作系统层面和Linux层面两个层面去理解。在深入学习进程状态的知识前,我们先要了解几个重要的概念。
- 一个CPU有一个运行队列
- 让进程进入队列,本质:将该进程的task_struct结构体对象(PCB)放到运行队列中,而不是将进程对应的代码放到运行队列中
- 进程在runqueue(运行队列)中,就是R,而不是说这个程序在运行,才是运行状态;进程状态是对PCB属性的描述,因此状态的数据也储存在内存中
- 不要以为你的进程只占用CPU资源,也有可能它需要占用外设资源,更进一步可能造成资源浪费(当大量进程需要和外设交互,而外设只能执行一个进程的时候)
- 所谓进程的不同状态,本质是进程在不同队列中等待某种资源!
1.普遍操作系统层面理解
(1)运行状态
一个CPU有一个运行队列,让进程进入队列,进程在runqueue(运行队列)中,就是R(运行状态),而不是说这个程序在运行,才是运行状态。
注意:进程在运行队列中,本质上是PCB在运行队列中,CPU可以根据PCB找到对应的进程。
(2)阻塞状态
将某个进程从运行队列中剥离下来,放到硬件的等待队列中,进程的状态就变为阻塞状态了。待进程完成与硬件的交互任务或者有需要时,先将进程状态重新变为运行状态,再将进程控制块(PCB)返回运行队列。
(3)挂起状态
为了更好的利用计算机资源,当内存中存在大量进程的时候,操作系统会将部分不需要立即调度的代码和数据暂时保存在磁盘上。这样,就可以实现内存空间资源的充分利用。
将进程的相关数据,加载或保存到磁盘,此时的进程状态被称为挂起状态。待有需要时,会先将进程状态重新改为运行状态,再将进程控制块(PCB)返回运行队列。
阻塞 vs 挂起 :阻塞不一定挂起,挂起一定阻塞!因为阻塞不一定会将PCB加载到磁盘,它也可以放到其它硬件的等待队列中,但是加载到磁盘后PCB一定在磁盘的等待队列中。
(4)状态合作
操作系统通过调整进程状态,充分调度计算机软硬件资源,实现计算机的高效运转。例如,当下我们的计算机可以同时运行多个程序,就是CPU配合操作系统在1s内将多个进程运行多次(CPU计算速度非常快),随时间对不同进程进行重复运行,才得以实现的。
2.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 */
};
(1)R运行状态(running)
- 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
(2)S睡眠状态(sleeping)
- 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断(interruptible sleep))。
当运行大型程序或者循环时,进程多处于S状态。这是因为CPU运行很快,但是外设很慢,我们获取显示器信息反馈相比于CPU计算速度相差很大,因此出现了阻塞,在Linux下就是睡眠状态S。睡眠状态是阻塞状态的一种。
(3)D磁盘休眠状态(Disk sleep)
- 有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO(输入输出)的结束。
- 休眠状态通常只能在高IO的情况下查看。
浅度睡眠,可以被终止(前台进程)(状态S)
深度睡眠,不可以被终止(后台进程)(状态D)
理解深度睡眠:为了避免存在于内存中的重要进程在计算机高负荷运转时被杀掉或覆盖,所以需要给它们设定一个特殊状态——休眠状态(D),它只能通过进程自己醒来(IO结束或断电),而无法被OS杀掉,从而有效的保护了重要进程和相关数据。
(4)T停止状态(stopped)
- 可以通过发送 SIGSTOP 信号给进程来停止进程。停止下来的进程会处于停止状态(T),这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- 可以通过 kill 命令改变进程状态
进程暂停下来的状态就是停止状态,暂停运行、继续运行指令如下
kill -19 3552(进程id) #暂停运行
kill -9 3552(进程id) #继续运行
通过Ctrl+C能终止的进程称为前台进程(状态符号后带有+号),不能被Ctrl+C终止的进程称为后台进程(状态符号后没有带有+号)
当一个进程从暂停状态再回到运行状态,它将转变为后台进程
(5)t - 被追踪状态 (tracing stop)
- 在此状态下,代表该进程正在被调试追踪
(6)X死亡状态(dead)
- 这个状态只是一个返回状态,你不会在任务列表里看到这个状态
kill -18 3552(进程id) #杀死进程
(7)Z(zombie) - 僵死状态
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时,就会产生僵死状态,而在这种状态下的进程我们称他们为僵尸进程。
二、进程状态查看 & makefile特殊符号$@^应用
1.基础进程查看方法
ps axj | head -1 && ps axj | grep myproc 或
ps axj | head -1 && ps axj | grep 4974(进程id)
- ps axj - 查看系统所有进程
- head -1(数字1) - 打印标题
- grep ‘myproc’ - 对除文件myproc外进行行过滤
进程在被调度运行的时候,进程就具有对应的动态属性
2.进程循环打印查看方法
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; done;
# while :; do -------- 死循环显示
# grep -v grep ------- 过滤grep进程(进程查看命令grep运行时,grep也算一个进程)
# sleep 1; ------ 每隔一秒循环一次
# done; --------结束
3.ls > makefile(重定向)
创建makefile文件,将当前 ls目录下的文件名加载到 makefile中
[ldx@VM-12-11-centos ~]$ ls > makefile
4.makefile特殊符号$@^应用
在makefile文件中可以使用下面这种写法
- $@ 代表 myprocess:myprocess.c 中冒号左边的部分(目标/生成文件)
- $^ 代表 myprocess:myprocess.c 中冒号右边的部分(依赖文件)
myprocess:myprocess.c
gcc - o $@ $^ -g
.PHONY:clean
clean :
rm - f myprocess
三、特殊状态下的进程 - 僵尸进程&孤儿进程
1.僵尸进程
(1)僵尸进程的概念
要学习僵尸进程,首先我们要知道进程被创造是为了完成某种任务的,在进程完成任务的过程中,我们需要知道它完成任务的情况。在一个进程结束的时候,我们是否也需要知道它情况呢?答案是,需要的。
实际上,在进程退出的时候,是不能立即释放该进程对应的资源的,需要保存一段时间,让父进程或者OS数据读取和回收。
结合前文我们对僵死状态的学习,我们知道了僵尸进程的来源,那就是子进程退出时,父进程没有接收到子进程退出的返回代码,此时子进程就会处于无法回收的状态,此时的子进程就称为僵尸进程。
- 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
代码演示
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
while (1) {
printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
exit(1);
}
}
else
{
//parent
while (1)
{
printf("I am parent proceass, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
在我们把上述代码运行起来后,打开另一个服务器窗口进行进程循环打印,我们可以看到一段时间后出现了下面这种情况
- 因为子进程已经结束,而父进程还在运行,进程 ./myprocess 的子进程状态改变为 Z+,父进程状态不变
- defunct - 死亡
- 停止进程 ./myprocess后,父子进程都被终止,僵尸进程消失,资源被操作系统回收
(2)僵尸进程的危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就会一直处于Z状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB就一直都要维护。
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 会造成内存泄漏?是的!
2.孤儿进程
- 在程序运行过程中,存在父进程先退出,子进程后退出的情况。
- 父进程先退出,子进程就称之为“孤儿进程”。
- 最终子进程会被1号进程(操作系统)领养。(1号进程的名称和操作系统的版本有关,常见的名称有init、system等,不过它们本质为操作系统)
- 为什么要这么干呢?如果不领养,那么子进程退出的时候,对应的僵尸进程就无法回收了。
- 如果前台进程的子进程,成为了孤儿进程,会自动变为后台进程。
代码演示
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
while (1) {
printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
//parent
while (1)
{
printf("I am parent proceass, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
在我们把上述代码运行起来后,再使用指令将父进程杀掉,我们可以看到出现了下面这种情况
kill -9 9703
子进程被1号进程领养,即该子进程的父进程 id 变为1。再通过查看指令,我们可以得知,1号进程就是我们的操作系统。
🌹🌹Linux进程状态的知识大概就讲到这里啦,博主后续会继续更新更多Linux操作系统的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪