【Linux】进程状态

一个进程一定会有不同的状态属性,来标志进程现在的状态

操作系统学科中的进程状态

操作系统学科中的进程状态主要分为三种:运行、阻塞、挂起状态

运行状态

  • CPU中存在一个runqueue的结构体,结构体中保存着需要被运行的PCB结构体对象,这些结构体对象被使用链表的数据结构进行链接
  • 运行状态(R)表明PCB在运行队列中排队或者正在被运行的状态
  • 在运行队列中的PCB说明这个进程准备被随时被调度
  • 重新创建出一个进程,这个进程需要被运行,只需要将这个进程放在运行队列中进程排队,等待被调度即可。

        一个进程被放在CPU上开始运行,并不是一直被执行完毕,才将自己从CPU放下来。每一个进程都会在自己task_struct中设置一种叫做时间片的概念。如果进程在CPU中被运行的时间超过这个时间片,调度器就会将这个进程从CPU中拿下来,重新放在运行队列的尾部,重新等待被调度。

所谓的时间片,在task_strcut中就是一个int time_sapce;

        所以在一个时间段内,所有的进程代码都会被执行。这种情况在计算机中称为并发执行,所以系统中一定会存在将进程从CPU放上去,取下来的操作——这种情况称为进程切换。


阻塞状态

  • 进程需要在外设中获取数据,但是外设并没有进行任何操作,而导致进程会一直在等待外设操作,处于这样一种的状态的多个进程会在这个外设中的等待队列中进行排队,这种现象称之为阻塞状态
  • 每一个设备都有一个等待队列,如果想要在外设中获取数据就需要在外设的等待队列中进行等待,如果获取到数据,就可以回到运行队列中,否则就会一直处理阻塞状态

挂起状态

  • 如果处于阻塞状态中的进程,由于操作系统内部的资源严重不足。操作系统为了保证正常的情况下,会省出来内存资源,而处于阻塞状态中或者运行状态中的进程,在没有被运行的时候,这些进程的代码和数据是处于空闲状态。
  • 操作系统为了保证正常运行,会将这些处于空闲状态的代码和数据从内存中交换到磁盘中,仅保留PCB(task_struct)。当进程需要被运行时,操作系统会将代码和数据从磁盘中获取出来。这个过程称为唤出和唤入
  • 一个进程在内存中只有PCB存在,其代码和数据并不在内存,这种状态称为挂起状态。

Linux操作系统的进程状态

R运行状态

/*
*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操作系统中的进程状态有上述几种:

  • R运行状态(running):并不意味着进程一定在运行中,表明进程要么是在运行中要么在运行队列中。
  • S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)
  • D磁盘休眠状态(Disk sleep):也可以叫做不可中断睡眠状态,在这个状态的进程通常会等待IO的结束
  • T停止状态(stopped):课堂通过发送SIGTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,不会在任务列表中看的此状态。

Linux中如何查看对应的状态?

进程只有被调度到CPU中才会处于运行状态(R),而大部分情况下进程都会处于等待被调度的情况,也就是阻塞状态(S)

  • 操作系统中进程有的处于阻塞状态(S),有的处于运行状态(R)
  • 这里有的处于R+,有的处于S+。这里的+表示在前台运行,没有+表示在后台运行

S睡眠状态

int main()
{
	printf("Enter data:");
	int a = 10;
	scanf("%d", &a);

	return 0;
}

  • 这个特殊的例子可以很好的描述S睡眠状态,也就是阻塞状态。
  • S状态表示Linux中正在等待某种资源在就绪。
  • 系统中绝大部分的进程都是阻塞状态的。

D深度睡眠

S称为浅度睡眠,而D称为深度睡眠

  • 浅度睡眠可以被唤醒或者杀掉,表明可以随时响应外部的变化。

当进程需要往磁盘中写入大量的数据的时候,在写入的过程中,进程会在内存中进行等待。而当内存中进程过多时,操作系统会对一些不重要的进程杀死。为了防止进程写入数据失败,在进程等待磁盘写入期间不会被系统杀掉,操作系统会先对这个进程设置称为D(深度睡眠状态),系统规定,当一个进程处于D状态,任何用户任何操作系统都无法将这个进程杀掉。

  • 当系统中存在大量的D状态,说明这个磁盘的压力已经非常大
  • 当一个进程处于D深度睡眠状态,此进程不会响应任何请求。
  • Linux操作系统中D深度睡眠状态和S浅度睡眠状态都属于操作系统的阻塞状态

T/t停止状态

  • T暂停状态,也称为stop状态
  • Linux信号中有19号信号,输入指令kill -19 pid(进程id)就可以让一个进程处于T暂停状态.
  • 使用指令kill -18 pid(进程id) 可以让进程恢复运行

  • T状态和S状态虽然相似,但是应用于不同的场景——(例如:被调试的进程处于T状态)
  • S状态是处于进程在等待某种资源
  • T状态可能是处于进程在等待某种资源,也可以是被某种进程所控制。
  • S状态和T状态都可以看着是阻塞状态

X死亡状态

理论上当进程被终止后,进程会被处于X死亡状态。但是当一个进程在被终止的时候,其不会被处于X死亡状态,而是处于Z僵尸状态。

Z僵尸状态

一个进程在被退出的时候,操作系统会把这个进程的信息先保存一段时间,令这个进程处于Z僵尸状态,让父进程获取到这个进程的信息。我们将已经被退出的,需要被父进程获取状态信息的进程称为Z僵尸状态。

  • 僵尸状态(Z)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时,就会令这个进程处于僵尸进程
  • 僵尸进程会以终止状态保持在进程表表中,并且会一直在等待父进程读取退出状态代码。
  • 只要子进程退出,父进程还在运行,并且父进程没有回收或者读取子进程状态,会让子进程处于Z状态。

子进程退出父进程退出

int main()
{
        pid_t id = fork();
        if(id == 0)
        {
                // 子进程
                int cnt = 5;
                while(cnt)
                {
                        printf("child process: pid: %d ppid: %d \n",
                                getpid(), getppid());
                        cnt--;
                        sleep(1);
                }
        }
        else
        {
                // 父进程
                while(1)
                {
                        printf("father process : pid:%d ppid: %d \n",
                                getpid(), getppid());
                        sleep(1);
                }
                // 这里父进程没有对子进程没有进行任何操作
        }
        return 0;
}
  • exit(0); 终止一个进程

  • 刚开始,两个进程都在运行,都处于S+(阻塞状态)状态
  • 而10s后子进程退出,父进程没有进行任何的操作,子进程处于Z+(僵尸状态)状态
  • 此时的子进程已经死亡,只是在等待父进程回收子进程的资源、

进程一般退出的时候,如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z(僵尸)状态,进程的相关资源尤其是task_struct结构体不能释放,这部分资源就一直会被占用,此时就造成了内存泄漏。维护退出状态本身就是要用数据维护,也属于进程基本信息,所有保存在task——striuct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。

  • 父进程结束的时候是由bash进程进行父进程的父进程(也就是bash进程)回收,每一个进程只能被自己的父进程回收。

父进程退出子进程不退出(孤儿进程)

int main()
{
        pid_t id = fork();
        if(id == 0)
        {
                // 子进程
                int cnt = 10;
                while(cnt)
                {
                        printf("child process: pid: %d ppid: %d \n",
                                getpid(), getppid());
                        //cnt--;
                        sleep(1);
                }
                // 子进程不退出
        }
        else
        {
                // 父进程
                int cnt = 5;
                while(cnt)
                {
                        printf("father process : pid:%d ppid: %d \n",
                                getpid(), getppid());
                        cnt--;
                        sleep(1);
                }
                // 这里父进程没有对子进程没有进行任何操作
        }
        return 0;
}

父进程被回收后,父进程的子进程会被操作系统拿到,从上面看子进程的PPID变成了1。

  • 总结来说,父子进程先退出,子进程的父进程会被改写成1号进程(操作系统)
  • 我们将父进程称为1号进程的进程称为孤儿进程
  • 孤儿进程被系统所领养

为什么要被领养呢?

        【答】:因为孤儿进程未来也会退出,也要被释放,其父进程已经被退出,只能由操作系统拿到并由操作系统释放。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值