Linux fork、进程的退出和等待详解

初识fork函数

它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);


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

最简单的fork使用示例

#include<stdio.h>
#include<unistd.h>
int main()
{
    pid_t pid=fork();
    if(pid<0)
    {
        printf("创建出错\n");
    }
    else if(pid==0)
    {
        printf("子进程创建成功\n");
    }
    else{
         printf("父进程\n");
    }
    printf("父进程\n");
    return 0;
}

父进程的判断可以不写,只写子进程和出错的判断就ok

代码详解

如果想要子进程运行完自己的部分后不执行原程序部分则需要加入退出部分

创建进程fork

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret=fork();
if(ret<0)
{
    perror("fork");
    return 1;
}
else if(ret==0)
{
    printf("child_id : %d! , ret %d\n " ,getpid(),ret);
}
else //father ret>0  
    printf("father_id : %d! , ret %d\n " ,getpid(),ret);

sleep(1);
return 0;
    }





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

int main() {
    pid_t pid;

    // 创建子进程
    pid = fork();

    if (pid < 0) {
        // fork失败
        fprintf(stderr, "fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("This is the child process. PID: %d\n", getpid());
    } else {
        // 父进程
        printf("This is the parent process. Child PID: %d\n", pid);
    }

    // 父子进程都会执行以下代码
    printf("PID: %d - End of the program\n", getpid());

    return 0;
}

fork进程创建基本功能

  • fork() 调用在父进程中调用一次,但返回两次:一次在父进程中返回,一次在子进程中返回。
  • 在父进程中,fork() 返回子进程的进程ID。一般都是>0
  • 在子进程中,fork() 返回0。
  • 如果 fork() 调用失败,它返回一个负值。

fork工作原理的理解

fork的优点

  • 利用子进程计算一些数据
  • 让子进程和父进程分开执行不同代码块

写实拷贝

  • 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

进程退出与等待

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  • 从main返回
  •  调用exit
  • _exit

异常退出:

  • ctrl + c,信号终止

_exit函数

#include <unistd.h>
void _exit(int status);


参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

-1 的补码表示是:1111 1111转成10进制位255

注意事项:

  • _exit() 调用会立即终止进程,不会执行任何清理操作,例如不会刷新标准I/O缓冲区,不会调用任何终止处理程序
  • _exit() 会关闭进程打开的所有文件描述符(通过open等函数打开文件返回后的值)。
  • _exit() 调用后,进程所占用的资源(如内存、文件描述符等)将被操作系统释放。
  • _exit() 中的参数只需要修改低8位即可

exit函数

#include <unistd.h>
void exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit

由此可知,C语言用的是exit()进而能在退出前显示缓冲区内容

而系统调用_exit()则不会显示缓冲区内容

代码结果反应退出函数区别

代码

int main()
{
printf("hello");
exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

区别

exit() 函数:

  • exit() 是C标准库函数,定义在 <stdlib.h> 头文件中。
  • 当调用 exit()时,它会刷新所有标准I/O流(例如 stdout),关闭所有标准库中打开的文件描述符,调用通过 atexit() 注册的所有退出处理函数,然后终止程序。
  • 因此,当你在调用 printf("hello") 后调用 exit(0),printf 的输出会被刷新到标准输出,你会在终端看到 “hello”。(exit()读取缓存文件)

_exit() 系统调用:

  • _exit() 是一个系统调用,定义在 <unistd.h> 头文件中。
  • 当调用 _exit() 时,它会立即终止程序,而不会刷新标准I/O流,也不会调用任何退出处理函数。
  • 因此,当你在调用 printf("hello") 后调用 _exit(0)printf 的输出缓冲区不会被刷新,“hello” 不会被打印到终端。(_exit()不读取缓存文件)

不推荐使用_exit(),使用exit()和return满足日常需求

return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

注意:

exit() 函数和 return 语句通常不能同时使用,因为它们都会导致程序终止。

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);


返回值:
        成功返回被等待进程pid,失败返回-1。
参数:
        输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
        当正常返回的时候waitpid返回收集到的子进程的进程ID;
        如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
        如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;



参数:
       1. pid:

  •  Pid=-1,等待任一个子进程。与wait等效。
  •  Pid>0.等待其进程ID与pid相等的子进程。

       2.status:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  •  WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

       3.options(阻塞方式):

  • WNOHANG:如果子进程尚未终止,waitpid() 函数不会阻塞父进程,而是立即返回 -1,并设置 errno 为 ECHILD。这通常用于非阻塞等待即父进程可以在等待子进程的同时继续执行其他任务。
  • WUNTRACED:如果子进程是暂停状态(例如,因为它正在等待输入或正在后台运行),waitpid() 函数会返回子进程的进程ID,并设置 status 变量。
  • WNOHANG 和 WUNTRACED 结合使用(WNOHANG|WUNTRACED):如果指定的子进程尚未终止,waitpid() 函数不会阻塞调用进程,而是立即返回 -1,并设置 errno 为 ECHILD。如果指定的子进程是暂停状态,waitpid() 函数会返回子进程的进程ID,并将子进程的退出状态和信号信息存储在 status 变量中。(组合使用意味多种情况多种选项,不单单只适合一种情况)
  • WCONTINUED:如果子进程是暂停状态,并且被信号唤醒(例如,因为它等待的输入已到达),waitpid() 函数会返回子进程的进程ID,并设置 status 变量。

当第三个参数设置为 0 时,它等同于 WNOHANG 和 WUNTRACED 的组合。这意味着 waitpid() 函数不会阻塞父进程,并且如果子进程是暂停状态,它会返回子进程的进程ID,并设置 status 变量。如果子进程尚未终止,waitpid() 函数不会阻塞父进程,而是立即返回 -1,并设置 errno 为 ECHILD


等待函数注意事项

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

代码结果反应等待函数

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

int main() {

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("子进程创建成功,倒数3s\n");
        int n = 3;
        while (n--) {
            printf("%d ", n);
        }
        printf("\n");
        exit(2);  // 子进程正常退出
    } else {
        // 父进程代码
        int status;
        pid_t ret = waitpid(pid, &status, 0);  // 等待子进程结束
        if (ret == -1) {
            perror("waitpid failed");
            return 1;
        }

        if (WIFEXITED(status))// 子进程正常退出
        {
            printf("子进程的退出状态: %d\n", WEXITSTATUS(status));
        }
        else if (WIFSIGNALED(status))// 子进程因信号终止 
        { 
            printf("子进程被信号终止,信号: %d\n", WTERMSIG(status));
        }
    }

    return 0;
}

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值