首页 > 系统相关 >《Linux系统编程篇》fork/wait/waitpid/exit函数——基础篇

《Linux系统编程篇》fork/wait/waitpid/exit函数——基础篇

时间:2024-11-01 19:19:56浏览次数:3  
标签:fork 函数 pid exit Linux 进程 include wait

文章目录

命为志存。 —— 朱熹

引言

《Linux系统编程篇》——基础篇首页传送门

本节我们正式进入Linux的进程代码编写。

fork() 是 Unix 系统中一个重要的系统调用,用于创建一个新的进程。它是创建进程的核心函数之一,其特性和用法在进程管理中至关重要。本文将带你深入了解 fork() 函数的使用、返回值、常见问题及其在操作系统中的重要性。

fork() 函数概述

在 Linux 中,所有的进程都是通过 fork() 派生而来。当一个进程调用 fork() 时,操作系统会复制当前进程的上下文,为它创建一个几乎完全相同的副本,这个副本被称为子进程,而原始进程被称为父进程。

这也是人们口中的父子进程的概念

父子进程

进程a->fork->进程b

  • 我们称作进程a进程b的父进程,反之进程b则是进程a的子进程

兄弟进程

进程a->fork->进程b
进程a->fork->进程c

  • 我们称作进程b进程c互为兄弟进程,进程a则是他们共同的父亲

fork函数

我们在命令终端,可以轻易的使用man手册来看,这个函数的作用返回值等,以及这个函数所需要的头文件是什么。

man 2 fork

fork() 是一个在Unix系统中可用的系统调用函数,用于创建一个新的进程。

fork() 函数的原型如下:

#include <unistd.h>

pid_t fork(void);

fork() 函数在调用时会复制当前进程创建一个新的子进程。子进程将几乎完全复制父进程的状态,包括代码、数据、打开的文件等。父进程和子进程之间的唯一区别是进程 ID(PID)和返回值。

  • 如果 fork() 返回 -1,则表示创建子进程失败。
  • 如果 fork() 返回 0,则表示当前代码运行在子进程中。
  • 如果 fork() 返回一个正整数,表示当前代码运行在父进程中,并且返回的值是子进程的PID

好,接着我们写一段调用fork函数的一个小demo

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

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

        printf("pid = %d\n",pid);
        return 0;
}

在这里插入图片描述

编译运行后可以看到我的pid被打印了两次,也就是说我的printf函数被执行了两次。

结合我们提前整理的资料来看,使用fork之后返回的pid,一个是3403,一个是0,那么输出是0的这个应该是子进程打印的,那么另一个则是我的父进程打印的,这一区别使父子进程能够在相同的代码段内执行不同的操作。

例如,父进程可以根据 fork() 的返回值来监控子进程的运行状态,而子进程则可以独立执行任务。

我们升级这个程序继续观察一下。

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

int main()
{
	pid_t pid;
       
	printf("*********  test log 01 **********\n");

    pid = fork();
	printf("pid = %d\n",pid);
	
	if(pid == 0){
		printf("this is child\n");
		while(1){usleep(500);}
	}else{
		printf("this is father\n");
		while(1){usleep(500);}
	}


	return 0;
}

在这里插入图片描述
代码解释:
fork() 被调用时,系统会创建一个新进程。
代码中,pidfork() 的返回值,如果 pid == 0 表示这是子进程,否则就是父进程。
父进程可以通过 wait() 函数等待子进程执行完成。

这次在fork之前加了打印,可见在调用fork之前,我们的代码还是单进程再跑的。在fork之后也做了相关的判断,看到这里我想大家应该掌握了进程的奥秘了。

于是我们总结一下,写一个进程小框架,方便日后便捷使用。

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

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

    if (pid == -1) {
        // 创建子进程失败
        perror("fork failed");
    } else if (pid == 0) {
        // 子进程代码
        printf("Hello from child process!\n");
    } else {
        // 父进程代码
        printf("Hello from parent process!\n");
        printf("Child process ID: %d\n", pid);
    }

    return 0;
}

到了这里恭喜你,学会了进程,剩下的一些骚操作就是在此基础上不断的扩展延伸,以及迭代。
但是请注意
当一个进程调用fork()创建一个子进程时,子进程会复制父进程的内存空间,包括代码段、数据段、堆和栈等。这意味着在刚刚创建时,父进程和子进程的内存是一样的,但在后续的运行过程中,它们的内存是相互独立的。

现代操作系统中为了提高效率,通常使用写时复制(Copy-On-Write,COW )技术。

写时复制(Copy-On-Write,COW )
这意味着在 fork() 后,父子进程共享相同的内存页,直到其中一个进程试图修改这些页时,操作系统才会真正复制这些页。这样可以显著减少内存的占用,优化性能。

fork() 的常见问题

  • 僵尸进程(Zombie Process):如果父进程在子进程结束后未调用 wait() 或 waitpid() 函数读取子进程的状态信息,子进程将成为僵尸进程。这会占用系统资源,导致系统无法创建新的进程。
  • 多次 fork():如果不慎多次调用 fork(),可能会产生大量子进程,消耗系统资源,甚至造成“fork 炸弹”(即创建太多进程导致系统资源耗尽,系统崩溃)。
  • COW 效率:尽管写时复制提高了效率,但在高负载情况下,COW 的性能可能会受限。

Copy-On-Write(COW)是一种内存管理技术,通常用于优化进程之间的内存共享和复制操作。在 COW 机制中,当多个进程共享同一块内存时,只有在其中一个进程尝试修改这块内存时,系统才会执行复制操作,确保每个进程都能看到自己的独立副本,从而实现了延迟复制的效果。
COW 的效率主要体现在以下几个方面:

  1. 节省内存开销:COW 允许多个进程共享同一块内存,避免了不必要的内存复制。只有在必要的时候才会进行复制,从而节省内存开销。
  2. 减少复制时间:由于只有在写入操作时才会执行复制,因此在大多数情况下,COW 可以减少复制所需的时间,提高了操作的效率。
  3. 提高性能:通过延迟复制操作,COW 可以提高程序的性能,特别是在需要频繁复制内存数据的情况下,避免了不必要的复制开销。

fork() 的优势与限制

  • 优势:fork() 是创建子进程的简单方式,适合多任务并行处理的应用场景。写时复制机制降低了资源消耗,提升了系统效率。
  • 限制:在资源受限的嵌入式系统或实时系统中,频繁使用 fork() 可能导致性能下降。此外,不恰当的 fork() 使用可能引发僵尸进程或系统资源耗尽等问题。

引入waitwaitpid(解决僵尸进程)

除了fork之外我们如何检测子进程的结束状态呢,换言之,我想让子进程帮我办个事,办完事之后我想让他和我反馈一下,有没有这样一个函数,可以检测它的反馈并做处理呢,当然有那就是waitwaitpid

waitwaitpid 函数用于等待子进程的结束,并获取子进程的终止状态。父进程可以通过这些函数来等待子进程的退出,以避免僵尸进程的产生。

wait函数

  • wait 函数用于父进程等待其子进程的结束。当子进程结束时,父进程将从 wait 函数中返回,并获取子进程的终止状态以及退出原因。如果子进程尚未结束,父进程将阻塞直到子进程退出。

当子进程结束时,父进程将从wait调用中返回,并获取子进程的终止状态以及退出原因。

  • status参数是一个整型指针,用于存储子进程的退出状态。如果父进程不关心子进程的退出状态,可以将status设置为NULL

wait系统调用的工作原理如下:

pid_t wait(int *status);
  1. 如果当前进程没有子进程,wait会立即返回-1并设置errnoECHILD

  2. 如果有子进程正在运行,父进程将被阻塞,直到其中一个子进程终止。

  3. 当子进程终止时,wait会返回子进程的PID,同时将子进程的退出状态存储在status指向的位置。

  4. 父进程可以通过检查status来确定子进程的退出状态,通常通过WIFEXITED(status)WEXITSTATUS(status)来获取子进程的退出状态值。

例子(wait 系统调用的基本用法):

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

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

    if (pid == 0) {
        // 子进程
        printf("子进程正在运行\n");
        sleep(2); // 模拟子进程执行任务
        exit(42); // 子进程退出,退出状态为42
    } else if (pid > 0) {
        // 父进程
        printf("父进程正在等待子进程\n");
        int status;
        wait(&status); // 等待子进程结束
        if (WIFEXITED(status)) {
            printf("子进程退出,退出状态为: %d\n", WEXITSTATUS(status));
        }
    } else {
        perror("fork");
        exit(1);
    }

    return 0;
}

运行结果:
在这里插入图片描述

父进程通过wait等待子进程结束,并获取子进程的退出状态。通过这种方式,父子进程之间可以进行协同工作和信息交换。

waitpid函数:

  • wait 类似,但具有更灵活的功能。waitpid 允许父进程指定要等待的子进程的 PID,从而可以选择性地等待某个特定的子进程退出,而不是等待任何子进程。
pid_t waitpid(pid_t pid, int *status, int options);
  • pid 参数用于指定要等待的子进程的 PID,具体取值如下:

    • 如果 pid 大于 0,则等待具有指定 PID 的子进程。

    • 如果 pid 等于 -1,则等待任意子进程。

    • 如果 pid 等于 0,则等待与调用进程在同一个进程组的任意子进程。

    • 如果 pid 小于 -1,则等待进程组 ID 等于 pid 绝对值的任意子进程。

  • status 参数是一个整型指针,用于存储子进程的退出状态。

  • options 参数用于指定一些选项,如是否非阻塞等。

例子(waitpid 系统调用的基本用法):

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

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

    if (pid == 0) {
        // 子进程
        printf("子进程正在运行\n");
        sleep(2); // 模拟子进程执行任务
        exit(42); // 子进程退出,退出状态为42
    } else if (pid > 0) {
        // 父进程
        printf("父进程正在等待子进程\n");
        int status;
        pid_t child_pid = waitpid(pid, &status, 0); // 等待特定子进程结束
        if (child_pid == -1) {
            perror("waitpid");
            exit(1);
        }
        if (WIFEXITED(status)) {
            printf("子进程 %d 退出,退出状态为: %d\n", child_pid, WEXITSTATUS(status));
        }
    } else {
        perror("fork");
        exit(1);
    }

    return 0;
}

父进程使用 waitpid 指定要等待的子进程的 PID,以等待特定的子进程结束并获取其退出状态。通过 waitpid,父进程可以更精确地控制对子进程的等待。

运行结果:
在这里插入图片描述

exit函数

exit 函数用于终止当前进程的执行,并返回一个状态码给父进程。当进程执行完毕时,应该调用 exit 函数来正常退出,以避免产生僵尸进程。

void exit(int status);
  • status 参数是一个整型值,用于表示进程的退出状态。通常情况下,0 表示正常退出,而其他非零值则表示异常退出,可以用于指示具体的错误码或其他信息。

在上述例子中我们有调用到这个函数,这个函数就是的作用就是:

当调用 exit 函数时,当前进程会立即终止,并返回一个整型值作为退出状态码。这个退出状态码通常用来表示程序的结束状态,可以被父进程或操作系统获取。

上一个例子当中我们调用了exit这个函数并返回了状态码为42,并被父进程的waitpid接收到。

结论

fork() 是 Linux 系统中关键的系统调用,理解它的行为和特性是掌握 Linux 进程管理的关键一步。掌握 fork() 的用法和注意事项,可以帮助开发者设计出高效、健壮的多进程应用。在实际开发中,fork() 通常与 execwait 等函数配合使用,以满足多任务系统的要求。

标签:fork,函数,pid,exit,Linux,进程,include,wait
From: https://blog.csdn.net/qq_52749711/article/details/141110490

相关文章

  • 《Linux系统编程篇》消息队列(Linux 进程间通信(IPC))——基础篇
    文章目录引言消息队列(MessageQueue)消息队列的特点消息队列的特性消息队列的操作ipcs-q拓展ipcrm拓展注意事项结论“山重水复疑无路,柳暗花明又一村。”——陆游引言《Linux系统编程篇》——基础篇首页传送门想象一下,你正在开发一个多任务处理的应用程序,其中......
  • Chromium127编译指南 Linux篇 - 同步第三方库以及Hooks(六)
    引言在成功克隆Chromium源代码仓库并建立新分支之后,配置开发环境成为至关重要的下一步。这一过程涉及获取必要的第三方依赖库以及设置钩子(hooks),这些步骤对于确保后续的编译和开发工作能够顺利进行起着决定性作用。本指南旨在详细阐述这些配置步骤的执行方法,为开发者提供清晰......
  • Chromium127编译指南 Linux篇 - 编译前环境搭建(一)
    前言在当前的浏览器开发中,Chromium作为一个开源项目,已经赢得了广泛的关注和使用。它不仅构成了GoogleChrome的核心框架,同时也是诸如MicrosoftEdge、Opera和Brave等多款浏览器的基础。凭借其广泛的应用和出色的可定制性,许多开发者选择在Chromium的基础上进行再开发......
  • Linux nginx 配置
    Nginx的配置类型丰富多样,可以根据不同的需求进行灵活配置。以下是使用不同域名介绍的10种Nginx配置类型:基本Web服务器配置域名:http://www.example1.com配置说明:这是Nginx作为Web服务器的基本配置,包括监听端口、服务器名称、根目录设置等。示例配置:nginxserver{ listen8......
  • Linux-shell实例手册-网络操作
    本文章讲解的是在linux下跟网络相关的一些操作和命令,喜欢就点赞收藏哦,方便随时查阅!文章目录1Linux下网络基本命令2netstat3ssh4网卡配置文件5route6解决ssh链接慢7ftp上传8nmap9 流量切分线路10snmp1Linux下网络基本命令   rz  #通过ssh上传......
  • Linux的常用命令
    普通用户不具备修改权限命令su-(进入root账号)查询资料是因为$代表普通用户模式,权限不够,可以进入root帐号在建立文件夹进入root帐号,打su-(su-切换到root用户,并转到root用户的家目录下,即改变到了root用户的环境。)命令选项传参command-optionsparameterCommand:命......
  • 【Linux】动静态库(超详细)
     ......
  • Linux sshd升级
    1.ubuntussh升级到9.6sshd_update_ubt#定义变量dir="/etc/xinetd.d/"sshd_pid=`ps-ef|grepsshd|awk'$3==1{print$2}'`#结束sshd进程stop_sshd(){ [-z"${sshd_pid}"]||{ kill${sshd_pid} }}#下载telnetapt_telnet(){apt......
  • 100 道 Linux 常见面试题,慢慢读~_linux基础面试题
    1Linux概述1.1什么是LinuxLinux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络......
  • 【Linux内核】Cgroup原理和使用
    1.Cgroup简介cgroups(ControlGroups)是Linux内核的一个特性,用于对进程组的物理资源(如CPU、内存、磁盘I/O等)进行细粒度的控制和监控。cgroups可以帮助你限制、记录和隔离资源使用,但它本身并不直接用来“拉高CPU负载”。相反,cgroups通常用于限制进程可以使用的资源量,以防止它们消耗......