C语言使用fork()系统调用创建子进程

有些时候,创建多个子进程可用于提高任务处理效率或提高程序的并发性;在Linux系统下可使用fork()系统调用创建一个新的子进程;

fork()系统调用

要使用fork()函数,需引入unistd.h头文件;其函数原型如下:

pid_t fork(void);

调用fork()成功后,将存在两个进程,一个父进程和一个子进程(假设只调用了一次);

这两个进程执行相同的文本段,通俗来讲就是子进程和父进程的代码一样;但两个进程各自拥有不同的栈段,数据段,以及堆段的拷贝;

子进程在创建时,栈段、数据段的内存内容是对父进程内存相应部分的复制。执行fork()后,每个进程均可修改各自的栈段、数据段内容,却不影响另一个进程。

两个进程都从fork()的返回处开始执行,执行的先后顺序并不一定,这取决于系统是如何调度的;

程序可通过fork()的返回值来判断当前进程是父进程还是子进程;

fork()函数的返回值:

  • -1,表示无法创建子进程,原因可能是超过了进程数量的上限;
  • 大于0的值,表示当前进程为父进程,返回值表示子进程的进程ID;
  • 0,表示当前进程为子进程;

示例代码

这是一段演示fork()使用的代码,其中使用了getpid()函数可用于获取当前进程的进程ID;

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

static int i_data_segment = 123; //数据段的数据

//子进程和父进程有相同的文本段
void printdata(pid_t pid,int i1,int i2){
    
    printf("PID=%ld %s i_data_segment=%d i_stack_segment=%d\n",
        (long)getpid(),
        (pid==0)?"(child)":"(parent)",
        i1,
        i2
    );
}

int main(){

    int i_stack_segment = 456; //栈段的数据

    pid_t pid;

    pid = fork();
    //如果pid不为-1,从这里后将存在两个进程
    //pid==0时表示当前进程是子进程
    //pid>0时表示当前进程是父进程

    //根据pid来判断是父进程还是子进程
    switch(pid){
    case -1:
        /* fork() 返回 -1 表示子进程创建失败*/
        printf("fork() failed\n");
        exit(1);
        
    case 0:
        /*
         执行子进程的任务
         子进程拥有独立的数据段和堆段
         且初始内存为父进程对应的内存拷贝
        */

        //子进程修改的是自己栈段和数据段的变量
        //不会影响到父进程
        i_data_segment += 100; //修改数据段的变量
        i_stack_segment += 100; //修改栈段的变量

        //子进程拥有和父进程一样的文本段
        //所以能调用printdata函数
        printdata(pid,i_data_segment,i_stack_segment);

        //子进程任务完成后,你也可以直接使用exit()函数退出子进程

        break;

    default:
        /*
         父进程执行的任务
         */
        printf("parent: sleeping...\n");
        sleep(3); //父进程休眠3秒,确保子进程结束
        //这里等待子进程结束的方法只用于简单演示fork()的流程
        //!!!不要在你的程序中采用这种方法 !!!
        //要等待子进程结束,请了解 wait(),waitpid()等函数;
        printf("parent: wake up.\n");
        break;
    }

    //因为已跳出了switch块
    //子进程和父进程都将执行这里的代码;
    printdata(pid,i_data_segment,i_stack_segment);

    //如果父进程不会退出,需要调用wait()这类函数回收子进程资源,否则子进程将成为僵尸进程;
    //如果父进程先于子进程退出,子进程将成为孤儿进程;
}

运行效果

parent: sleeping...
PID=53 (child) i_data_segment=223 i_stack_segment=556
PID=53 (child) i_data_segment=223 i_stack_segment=556
parent: wake up.
PID=52 (parent) i_data_segment=123 i_stack_segment=456
fork()

可以看出,这里fork()后,父进程开始执行并进入休眠(实际上父进程和子进程哪个先执行并不确定);

子进程运行后,修改了自身数据段和栈段的变量并打印,至于为什么有两条一样的输出,请仔细查看代码注释;

父进程唤醒后打印自身数据段和栈段的变量,可以发现子进程并没有修改它;

原创内容,如需转载,请注明出处;

本文地址: https://www.perfcode.com/p/create-child-process-using-fork-system-call.html

分类: 计算机技术
推荐阅读:
Rust concat宏的用法和示例 在Rust中,concat宏用于在编译时将字面量以字符串的形式连接起来。它可以接受任意数量的字面量作为参数,并将它们连接成一个单独的字符串字面量。
Python tuple元组 tuple(元组)类似于列表,但元组当中的项不能被修改。
Python计算圆周率,精确到n位 本文将使用Python计算圆周率,可精确到n位,n值越大精度越高。
SQL删除数据库 如果要删除现有的数据库,则可以使用DROP DATABASE语句;
Golang实现判断文件或文件夹是否存在 golang通过使用 os包中的Stat()函数和IsNotExist()函数即可判断文件或文件夹是否存在。
Golang安装gin库的详细教程及错误解决方法 Gin是用Go(Golang)编写的Web框架。 它具有类似于martini的API,其性能比httprouter快40倍。 如果您需要性能和良好的生产率,您会喜欢Gin