linux之《进程等待》

引言

我们知道在linux系统中,一个父进程创建了一个子进程,此时子进程如果退出了,他就会保持僵尸状态,而僵尸状态的进程是有弊端的,虽然已经退出,但是
还在消耗内存资源。linux系统对内存的管理当然是相当的严格高效的,绝对是不允许这种浪费资源的事情发生的,那么linux系统会对僵尸状态的进程进行怎
么样的处理呢?

在解决这个问题之前,我们先了解一下预备知识

一,fork()

  • 函数fork()是用来启动另一个进程的函数,启动起来的进程称为子进程在调用fork()函数的程序称为父进程
  • 子进程的大部分数据都是拷贝于父进程的,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定

1. 常规用法

  • 一个进程要执行不同的程序。eg:子进程从fork()返回后,调用exec()。
  • 一个父进程希望复制自己,使父子进行不同的代码段。eg:父进程等待客户端请求,子进程来处理请求。

2. 它和写时拷贝的关系

上面说道,子进程的大部分数据都是来源于父进程的,在linux这个对内存要进行高效利用的系统中,子进程的创建显然不会对他从父进程里继承的全部数据马上分配内存空间,这时系统会先给子进程和父进程一样的虚拟地址,当子进程真正需要修改数据时系统才会进行拷贝一块内存给子进程用,这就是写时拷贝。
在这里插入图片描述

二,进程终止

三种情况

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止(本质:因为某些原因,收到操作系统的信号)

1. 退出码

我们在学习C语言时,是不是常常在main函数里面return 0,这其中的0就是我们学习的退出码,0是众多退出码的一种,表示程序代码正常终止。
我们来看看退出码的数量和含义:

在C语言中,有一个专门打印退出码含义的函数strerror(),下面我们通过代码来演示

#include<stdio.h>
#include<string.h>
int main()
{
    int i = 0;
    for ( i = 0; i < 150; ++i)
	printf("%d:%s\n",i,strerror(i));
    return 0;
}

在这里插入图片描述
从图中我们可以看出,一共有134个退出码,常用的是0表示正常退出,在linux操作系统下,我们可以通过echo $?来查询上一个执行完的程序的退出码
在这里插入图片描述
第一个echo $?获取到的退出码是刚刚执行的程序是正常退出的,第2,3个都是系统指令返回的退出码,第4,5表示echo $?自身执行成功返回的退出码。

2. exit()和_exit()的关系

我们先看关于exit()和_exit()的两段代码,同过比较,来学习一下两者的相同点和不同点。

  • exit()
#include<stdio.h>
#include<stdlib.h>
int main()
{
	printf("%s",strerror(0));
	exit(2);
	printf("\n");
    return 0;
}

在这里插入图片描述

  • _exit()
#include<stdio.h>
#include<unistd.h>
int main()
{
	printf("%s",strerror(0));
	exit(2);
	printf("\n");
    return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 相同点:exit和_exit都可以在任何地方正常退出程序,并返回退出码。eg:上述代码就返回了2,并都没有执行printf("\n");.
    -不同点:exit()除了会正常退出程序外,他还会清理函数,刷新缓冲区,_exit()则直接正常退出程序。
底层可以这么理解,exit 调用 _exit
exit(int code)
{
	关闭文件,清理函数;
	刷新缓冲区;
	_exit();
}

三,进程等待

通过上面的知识了解,现在我们来解决开头的问题:父进程如何对僵尸进程进行处理

1. 什么是进程等待

父进程为了拿到子进程的运行信息结果对子进程进行资源回收的等待过程

2. 为什么要进程等待

  1. 我们知道子进程结束后,如果父进程不做任何处理,那么子进程就会变成僵尸状态,而僵尸状态的进程刀枪不入,kill -9 也杀不死它,所以会造成资源浪费,所以需要父进程调用wait() / waitpid() 来回收子进程的资源。
  2. 获取子进程的结果。
  3. 保证时序。让子进程先退出,父进程后退出。

3. 进程如何等待

1. wait()

在这里插入图片描述

父进程执行到wait()时,就进入阻塞状态,一直等待子进程的结束。便于观察,我多加了几个sleep()。代码如下:

void test_wait()
{
    pid_t pid = fork();
    if(pid == 0)
    {
		int i = 5;
		while(i--)
		{
		    printf("%d,子进程,pid=%d\n",i,getpid());
		    sleep(1);
		}
		exit(0);
    }
    sleep(6);
    printf("等待wait回收子进程资源...\n");
    wait(NULL);
    printf("子进程终于结束了,我是父进程,获取到子进程的pid=%d\n",pid);
    sleep(2);
}

在这里插入图片描述
红色框:父子进程一并运行。
蓝色框:子进程结束,进入僵尸状态,等待父进程用wait()来进行资源回收。
黄色框:子进程的僵尸状态消失,只剩父进程在运行。

ps:这里解释一下为什么父子进程全程都是S+(阻塞状态)。根据代码,如果去掉sleep(),其中在红色框时,子进程应该是R+(运行状态),父进程是S+,因为父进程调用了wait(),需要等待子进程运行完毕才会继续下面代码的执行。后面蓝色和黄色框框父进程是R+。

2. waitpid()

来看看waitpid()的形式参数

在这里插入图片描述
有三个,其中第二个和wait(*status)一样,上面没说,那么现在来学习一下。

  1. pid:子进程的pid,-1表示任意子进程
  2. status:一种状态码(一会就详谈)
  3. options:子进程状态。常用的:0表示阻塞状态,WNOHANG表示非阻塞状态

wait(NULL)相当于waitpid(pid, NULL,0),pid表示子进程的pid。可以简单理解为waitpid()wait()的升级版。

status

status的大小为4个字节,有32位,我们探究其后16位。
在这里插入图片描述
现在我们通过代码来看看

void test_wait()
{
    pid_t pid = fork();
    if(pid == 0)
    {
	int i = 5;
	while(i--)
	{
	    printf("%d,子进程,pid=%d\n",i,getpid());
	    sleep(1);
	}
	exit(3);
    }
    sleep(6);
    printf("等待wait回收子进程资源...\n");
    int status = 0;
    wait(&status);
    printf("子进程终于结束了,我是父进程,获取到子进程的pid=%d\n",pid);
    printf("退出码:%d,终止信号:%d\n",(status>>8)&0xff,status&0x7f);
    sleep(2);
}

在这里插入图片描述

  • 这里3是子进程退出的返回值,我们用了exit(3)来显示表示,终止信号0表示正常运行,没有发生异常。
  • status的引入可以让我们更好了解到子进程的运行信息。
  • waitpid()/wait() 通过修改子进程的pcb来返回status的信息。

status其实可以用系统提供的宏定义WIFEXITED(STATUS)来判断是非是正常退出,用WIFSTATUS(STATUS)获取进程退出码

options

在学习wait()的时候,我们有提到过,子进程在运行时,父进程在阻塞,这是对的,经过我们验证的。因为**wait()是默认让父进程进行阻塞状态**,而waitpid()却有options这个参数来控制父进程的状态。
下面我们来看看

void test_wait()
{
    pid_t pid = fork();
    if(pid == 0)
    {
	int i = 5;
	while(i--)
	{
	    printf("%d,子进程,pid=%d\n",i,getpid());
	    sleep(1);
	}
	exit(0);
    }
    //sleep(6);
    //printf("等待wait回收子进程资源...\n");
    int status = 0;
    waitpid(pid, &status, WNOHANG);
    int i = 0;
    for(i = 0; i < 5; ++i)
    {
	printf("我是父进程,我先做自己的事情...\n");	
	sleep(1);
    }
    printf("子进程终于结束了,我是父进程,获取到子进程的pid=%d\n",pid);
    printf("退出码:%d,终止信号:%d\n",(status>>8)&0xff,status&0x7f);
    sleep(2);
}

在这里插入图片描述

可以看到,父进程并没有等待子进程运行完才执行自己的事情,而是并发执行,两种执行顺序没有先后之分,由操作系统来调度。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux中的进程等待(Process Waiting)是指一个进程在执行时需要等待某些条件满足后才能继续执行的情况。 在Linux中,进程等待通常有以下几种情况: 1. I/O等待:当一个进程需要进行输入输出操作时,比如读写文件或者网络通信,由于这些操作是相对慢速的,进程需要等待数据的读取或写入完成才能继续执行。 2. 锁等待:多个进程访问临界资源时,为了避免竞态条件,需要使用锁来实现同步。当一个进程试图获取已经被其他进程占用的锁时,它会被阻塞,并等待锁被释放。 3. 睡眠等待:当一个进程调用了sleep()或wait()等系统调用后,它会主动释放CPU资源,并进入睡眠状态,等待指定的时间或者某个事件发生后才会被唤醒。 4. 信号等待:当一个进程正在等待某个信号的到来时,它会进入阻塞状态,直到该信号被发送给该进程进程才会被唤醒并继续执行。 针对进程等待的情况,Linux提供了一些机制来管理这些等待进程,比如使用信号量、条件变量、管道等方式来实现进程间的同步与通信。此外,Linux还提供了一些工具和命令来查看进程等待的状态,比如top命令可以查看每个进程等待时间,ps命令可以查看进程的状态等。 总之,Linux中的进程等待是一个重要的概念,合理管理进程等待可以提高系统的性能和资源利用率。进程等待是多任务操作系统中的常见现象,对于了解和掌握Linux进程管理至关重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值