Linux环境下的进程创建-fork函数的使用与写时拷贝, 进程退出exit和_exit的区别,以及进程等待waitpid和status数据的提取方法

目录

一、进程创建

1.fork函数

1)进程调用fork函数是如何创建子进程的

2)代码示范

2.写时拷贝

二、进程退出

1.退出码

1)什么是退出码?

2)为什么要有退出码?

3)退出码是怎么做到的?

4)echo $?:查看最近一个进程的退出码

2.进程的三种退出状态

1)又可分为能运行完和没运行完

2)exit( )和_exit( )的区别

三、进程等待

1.进程等待是什么

2.为什么要进程等待

3.父进程是怎么等待的

1)wait和waitpid

2)写入型参数status的使用方式

①终止信号

②退出状态

③status相关宏定义

3)使用示范

4)阻塞等待与非阻塞等待的区别

总结



一、进程创建

1.fork函数

在linux种fork函数的作用是:从存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值子进程中返回0,父进程返回子进程id,出错返回-1。

这里的pid_t 可以看成是对int的封装

1)进程调用fork函数是如何创建子进程的

①通过分配新的内存块和内核数据结构给子进程

②将父进程部分数据结构内容拷贝至子进程

③添加子进程到系统进程列表当中 ,fork返回。

2)代码示范

这里利用fork对父子进程返回的id值的不同,使得父子进程运行不同得条件语句。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t id=fork();

    if(id<0)
    {
        printf("创建子进程失败\n");
        exit(1);
    }
    else if(id>0)
    {
        //父进程
        while(1)
        {
            printf("我是父进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else
    {
        //子进程
        while(2)
        {
            printf("我是子进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());
            sleep(2);
        }
    }
    return 0;
}

运行结果试图:(可以通过CTRL+C终止进程)

2.写时拷贝

在通过fork函数创建子进程时,父进程将部分数据结构内容拷贝至子进程,此时在内存中,操作系统为节省内存,父子进程的页表指向的是其实是同一块物理地址,换句话说,此时父子代码共享,数据也是共享的

写时拷贝的概念是:若当父子进程中有一方要修改某个变量时,操作系统会临时将该变量拷贝一份放在另一块内存中,并修改该进程的页表指向临时拷贝出来的变量,以确保进程的独立性

 理解fork函数需要对进程的地址空间有着一定的理解,如果读者发现看不懂上面的代码或者对进程地址空间有疑欢迎翻看笔者之前有关介绍进程地址空间的文章:进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客

二、进程退出

1.退出码

1)什么是退出码?

退出码就是main函数的而返回值,其中包括return返回或者exit返回

2)为什么要有退出码?

父进程会通过检查子进程的退出码从而确定子进程执行情况,然后再将子进程的资源释放——删除残留的task_struct(PCB),以结束子进程的僵尸状态

3)退出码是怎么做到的?

Linux环境下的退出码共有134种,可分为两种情况:

退出码方便计算机识别程序运行中出现的不同错误,但对人而言就不是很友好。

我们可以通过strerror()函数将退出码翻译为解释语言:

#include<stdio.h>
#include<string.h>

int main()
{
    for(int i=0;i<150;++i)
        printf("%d:%s\n",i,strerror(i));

    return 0;
}

读者若感兴趣可自我尝试,这里截取一些常见的退出码:

如上,共134种错误。

4)echo $?:查看最近一个进程的退出码

正如上述,在Linux种有一个变量“?”记录着最近一个进程的退出码。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    exit(10);

    return 0;
}

值得注意的是如果连续两次使用echo $?指令,第二次打印出的总是0,有误echo也是个程序也有返回值。

2.进程的三种退出状态

1)又可分为能运行完和没运行完

2)exit( )和_exit( )的区别

除了通过return返回外,程序还可通过exit和_exit两个函数返回。

exit是“stdlib.h”中的函数,是基于_exit做的封装

_exit是系统调用,在“unistd.h”中。

他们的作用都是“终止调用进程”,但执行起来有些微区别:

exit在结束进程时会刷新缓冲区,且会执行自定义的清理函数而_exit不会。

#include<stdio.h>
#include<stdlib.h>
#inlcude<unistd.h>

int main()
{

    printf("hello Linux");//注意没有换行符
    exit(0);
    //_exit(0);
    return 0;
}

exit执行情况:

_exit执行情况:

三、进程等待

1.进程等待是什么

每个子进程在运行结束后都会有一段僵尸进程状态,而进程等待是父进程通过系统调用等待子进程的一种方式。

僵尸进程是指 已终止但未被父进程回收(reap)的进程

2.为什么要进程等待

①释放子进程的僵尸状态;②获取子进程的运行状态

3.父进程是怎么等待的

1)wait和waitpid

waitpid参数解释:

pid——要等待(回收)的子进程;

*status写入型参数——将读取到的子进程状态写入status中;

options为等待方式,传0则是阻塞式等待,传入WNOHANG则是非阻塞等待(若有疑问的读者可直接跳转至“阻塞等待与非阻塞等待的区别”查看)

2)写入型参数status的使用方式

写入型参数,顾名思义就是系统会向这个形参中写入数据,通过写入的数据便可以判别进程的运行状态。

一般status创建为整型初始化为0,而系统写入的数据一般在status变量的低十六位

解释:4字节的整形status由32位二进制组成,而系统写入的数据则在低16位,也就是2^0-2^15之间,而就这16位二进制包含了两份数据——从0位到7位的二进制表示终止信号从8位到15的二进制表示退出状态

①终止信号

还记得上面讲述的进程的三种退出状态吗

二者三种状态又可分为两种情况:

①情况一:程序无异常,代码能跑完,无退出码

②情况二:程序出现异常,中途退出,无退出码

终止信号数据用于记录第二种情况——进程没跑完,中途出现异常的情况

若进程代码顺利跑完,则终止信号默认为0(status初始化值就是0)

获取终止信号的方式:

(status & 0X7F),0X7F是十六进制表示111 1111(其他全为0),&是位运算,(上下)同为1结果才是1

Linux环境下的终止信号有个64个,可以通过kill -l指令查看:

②退出状态

status二进制的次八位,也就是退出码

获取退出状态(退出码)的方式:

(status>>8)& 0XFF,>>是位运算符,作用是将status的二进制右移动八位,前边补0.

Linux环境下的退出码有134种。

③status相关宏定义

若是觉得status中的两个数据使用过于麻烦,系统为我们提供了两个宏以便使用:

一个是WIFEXITED(status),他的作用是检查进程是否运行完毕,有无异常。若进程正常退出则WIFEXITED(status)值为非零,否则为0.

另一个是WEXITSTATUS(status),他的值是进程的退出码。

3)使用示范

值得注意的是waitpid的返回值就是等待进程的id

#include<stdio.h>
#include<string.h>
#include<stdlib.h>//exit
#include<sys/types.h>//waitpid
#include<sys/wait.h>//waitpid
#include<unistd.h>//fork

int main()
{
    
    pid_t id=fork();

    if(id==0)
    {
        //子进程
        int cnt=5;
        while(cnt--)
        {
            printf("我的id:%d,父进程id:%d\n",getpid(),getppid());
            sleep(1);
        }
        exit(10);
    }
    //父进程
    int _status=0;
    int s_id=waitpid(id,&_status,0);
    printf("我的id:%d,子进程id:%d,sig number:%d,child exit code:%d",getpid(),s_id,(_status & 0X7F),(_status>>8&0XFF));
    sleep(1);
    return 0;
}

4)阻塞等待与非阻塞等待的区别

阻塞等待与非阻塞等待各有优劣,使用哪种等待方式全看当时情况:

比如,父进程只需等待单个子进程,且无其他任务需并行处理时就适合用阻塞等待,原因是可以将cpu资源让出来供其他进程使用。


总结

本文从进fork函数入手介绍了什么是进程创建,再通过strerror函数引出退出码以及exit和_exit的区别与联系,最后通过waitpid引出status的使用与相关数据的提取。本文还介绍了阻塞等待与非阻塞等待的区别。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值