【Linux】进程状态--僵尸进程

进程状态

在操作系统中进程一般有以下几个状态:

  • 新建状态:进程被创建了但是还没有被操作系统分配资源
  • 就绪状态:进程得到了资源,等待被调度执行
  • 运行状态:进程正在运行并且占用CPU的资源
  • 阻塞状态:因为等待某种资源或其他原因进程无法继续进行而停止进入等待队列
  • 终止状态:进程执行完成或者被终止了,释放所有资源

进程的运行状态

CPU的每一个核心一次只能处理一个任务,但CPU的处理速度是远远大于内存的处理速度的,但对于进程来说,运行就是要将它放到CPU上面,开始被CPU进行解析,进程就开始运行了。

CPU它会维护一个运行队列,只要进入了运行队列就可以被称之为运行状态®,所有的进程都需要在运行队列中排队。

阻塞状态

阻塞就是不被CPU进行调度,这是由于当前进程需要等待某种资源,比如我正在下载一个资料,突然没网络了,现在这个下载进程就会被设置为阻塞状态,它缺少网卡资源,因此它就会被放入网卡的等待队列等待资源,当恢复网络的时候就会被继续运行。

挂起状态

挂起状态就是原本某个进程运行的好好的,但突然系统的内存不够用了,如果强行干掉进程就会导致数据丢失,这时候它就会把数据和代码放到磁盘当中存好

注:挂起状态对用户是不可见的,这是操作系统的一种行为。就像我们把钱存银行里,我们并不知道银行把我们的钱拿去干嘛了,银行可能把我们的钱借出去了或者给员工发工资了等等,我们作为客户不得而知,我们只知道如果存的是活期,可以随时到银行把钱取出来,如果存的是死期只有到期了才能取出来。

进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 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 */
};
  • R运行状态(running):并不意味着进程正在运行,只要在运行队列中就会被标记为R
  • S睡眠状态(sleeping):意味着进程在等待事件完成(可中断睡眠),也可以叫做阻塞状态。
  • D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
  • t追踪停止状态(tracing stop):一般是调试的时候会出现这个状态。
  • X:死亡状态。
  • Z:僵尸状态

R运行状态(running)

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
    
    
    while (1)
    {
    
    
        int a=10;
    }
    return 0;
}

这里显示状态为R+,这个+表示在前台运行可以用Ctrl+c结束进程。

但如果while循环内的内容是printf("hello world\n");状态就会变成S+,为什么会变成睡眠状态呢?明明已经运行了啊,这是因为printf操作是把数据打印到显示器上面,这个过程相对于cpu的处理速度慢太多了,等打印完cpu不知道又去处理了多少进程了,总而言之就是在你看见打印结果之前cpu就早早的把printf处理完了,处理完了你再看自然就是睡眠状态了。

S睡眠状态(sleeping)

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
    
    
	int a=10;
    scanf("%d",&a);
    return 0;
}

对于这段代码,只要我不输入值,那么这个进程就会一直等待键盘的相应。

可以看到,处于睡眠状态。

D磁盘休眠状态(Disk sleep)

D状态也是一种阻塞状态,在 Linux 系统层面我们称作深度睡眠,S状态称作浅度睡眠。浅度睡眠是可以被唤醒的,即可以响应外部的变化,我们可以通过 kill 指令(其他进程)将浅度睡眠的进程终止掉。下面通过一个情景剧来给大家介绍为什么要有 D 状态,以及 D 状态的作用。

引入一个小故事:

有一天,有一个进程要把一些数据和代码写入到磁盘当中,磁盘虽然慢,但还是要老老实实干活,然后这时候操作系统就说,我内存都没剩多少了,你还在这给我睡觉(S状态),操作系统就直接把进程kill掉了,然后磁盘就傻了,进程人怎么没了,人没了那刚传输的数据我存哪啊,就会导致数据丢失,那这个责任谁负?磁盘老老实实干活没错,进程下达命令也没错,操作系统内存不够快要崩溃了删掉一些睡眠进程也没错,那错就错在操作系统的设计者,所以就引入了一个D状态,一般来说有D状态的时候关机都无法关机的。

停止状态(T/t)

首先看下kill命令有哪些选项

kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

可以看到以下状态

9) SIGKILL //杀死进程
18) SIGCONT	//继续进程
19) SIGSTOP //停止进程

注:变成R没有加号(+)之后就是在后头运行了,无法通过Ctrl + C结束进程,要杀掉进程只能用kill -9 PID来处理。

僵尸进程

如果一个进程结束了不会立马销毁进程,而是将其设置为僵尸进程,假如我结束了进程就马上销毁进程,那么如果这个进程出了什么问题需要通过返回值判断,这不就拿不到信息了吗?因此,当一个进程结束后还会被维持一段时间,方便后序父进程(OS)读取子进程的退出结果。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
    
    
	pid_t id=fork();
    if(id==0)
    {
    
    
        while(1)
        {
    
    
            printf("我是子进程,PID是:%d,PPID:%d",getpid(),getppid()); 
            sleep(1);
            break;
        }
    }else
    {
    
    
        while(1)
        {
    
    
            printf("我是父进程,PID是:%d,PPID:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

僵尸进程的危害

1.进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就将一直处于 Z 状态。

2.维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 PCB 对象中,换句话说,Z状态一直不退出,PCB一直都要维护。

3.一个父进程如果创建了很多的子进程,就是不回收,会造成内存资源的浪费,因为 PCB 对象本身就要占用内存,造成内存泄漏。

孤儿进程

让父进程退出,子进程继续运行

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()    
{
    
        
    pid_t id = fork();    
    if(id == 0)    
    {
    
    
    	//子进程    
        int cnt = 500;    
        while(cnt)    
        {
    
        
            printf("我是子进程,PID是:%d,PPID:%d,cnt:%d\n",getpid(),getppid(),cnt);    
            sleep(1);    
            cnt--;    
        }    
       _exit(0);    
    }    
    else    
    {
    
    
    	//父进程    
        int cnt = 5;
        //这里的cnt是5,意味着父进程会先执行结束    
        while(cnt--)    
        {
    
        
            printf("我是父进程,PID是:%d,PPID:%d,cnt:%d\n",getpid(),getppid(),cnt);
            sleep(1);                                                                        
        }    
    }    
    return 0;    
}

可以看到父进程在执行结束后就只剩下子进程,为什么父进程不会处在 Z僵尸状态呢?答案是父进程也是 bash 的子进程,父进程在执行结束后,它的父进程 bash 会将其回收掉,并且过程非常快,所以我们我们没有看到父进程处在 Z僵尸状态。其次我们发现,当父进程结束后,它的子进程的父进程会变成1号进程,即操作系统。我们将父进程是1号进程的进程叫做孤儿进程,该进程被系统领养。因为孤儿进程未来也会退出,也要被释放,所以它需要被领养。

小Tips:所有的进程只对它的“儿子”,即子进程负责,不会对它的孙子进程负责,因为代码中只有创建子进程的逻辑,并没有创建孙子进程的逻辑,所以并不是不想让爷爷进程来回收孙子进程的资源,是因为爷爷进程没有这个本事,而操作系统会直接从内核层面进行回收,所以当一个进程的父进程结束后,会把该进程交给操作系统,让操作系统来充当它的父进程。

猜你喜欢

转载自blog.csdn.net/2301_79516932/article/details/132544876