Linux应用编程---2.fork()函数

news2024/9/29 7:32:09

Linux应用编程—2.fork()函数

​ fork()函数用来创建子进程,函数具体功能与使用方法一起看编程手册。Linux终端命令下输入:man fork,敲击回车键即可打开fork函数详情页。

2.1 fork()函数详情

image-20221122110550668

图1 fork函数详情

首先看SYNOPSIS:

image-20221122110632629

图2 fork函数概要

​ 我们可以知道调用fork函数所需的头文件,以及fork函数的函数原型。pid_t fork(void).这个函数不需要传参,返回值是一个pid_t类型的。

​ 其次看具体描述:

DESCRIPTION
       fork()  creates  a  new  process  by duplicating the calling process.  The new process is referred to as the child process.  The calling process is referred to as the parent
       process.

       The child process and the parent process run in separate memory spaces.  At the time of fork() both memory spaces have  the  same  content.   Memory  writes,  file  mappings
       (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.

​ 大意是讲,fork()函数通过拷贝调用进程创建了一个新的进程。这个新的进程被称作子进程,这个调用进程被称作父进程。子进程和父进程运行在独立的内存空间内,他们有着相同的内容。

​ 最后看返回值的含义:

RETURN VALUE
       On  success,  the PID of the child process is returned in the parent, and 0 is returned in the child.  On failure, -1 is returned in the parent, no child process is created,
       and errno is set appropriately.

​ 当fork()函数调用成功后,父进程返回子进程的id,子进程返回值为0,当返回值为-1的时候,子进程创建失败。

2.2 fork()编程应用

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

int main(void)
{
        pid_t pid_1;

        pid_1 = fork();

        printf("pid_1 = %d.\n", pid_1);
        printf("Hello World.\n");
        
        return 0;
}

​ 运行结果:

pid_1 = 4015.
Hello World.
pid_1 = 0.
Hello World.

​ 看到运行结果,总共有4条语句被打印了出来,这是fork函数的作用。执行 pid_1 = fork();这条语句时,创建了子进程,我们称它为子进程B,调用进程我们称为父进程A。子进程B是父进程A的拷贝,所以这两个进程都会执行后面的printf打印。所以如我们看到的打印结果,一共有4条语句。前两条是父进程A打印出来的,其中pid_1的值4015是子进程的id号。后两条是子进程B打印出来的,所以返回值为0。

​ 拓扑如下:

image-20221122113559729

图3 拓扑结构图

​ 总结:fork()函数会创建一个子进程,这个子进程是父进程的拷贝,运行在不同的内存空间里。所以子进程也会执行父进程里相同的代码。

2.3 fork()编程进阶应用

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

int main(void)
{
        pid_t pid_1, pid_2;

        pid_1 = fork();
        pid_2 = fork();

        printf("pid_1 = %d, pid_2 = %d.\n", pid_1, pid_2);

        return 0;
}

​ 运行结果:

pid_1 = 4209, pid_2 = 4210.
pid_1 = 4209, pid_2 = 0.
pid_1 = 0, pid_2 = 0.
sgk@ubuntu:~/Documents/Linux_Program$ pid_1 = 0, pid_2 = 4211.

​ main函数中只有一句printf函数,但实际有4条语句被打印出来。

2.3.1 为什么打印出来了4条语句呢?

​ 直接看拓扑结构:

image-20221122114426960

图4 拓扑结构图

​ 当执行pid_1 = fork()时,调用进程我们称为父进程A,它创建了子进程B。AB俩进程分别继续执行pid_2 = fork(),所以A进程继续创建了进程C,B进程也创新的它的子进程,我们称它为进程D。到此为止,一共有4个进程。每个进程分别执行printf里的内容,所以一共打印了4条。

2.3.2 这4条语句具体是由哪一个进程打印出来的呢?

​ 上面我们分析过,fork()函数的返回值有3种,1是父进程返回它创建的子进程的id号;2是子进程返回0;3是子进程创建失败,返回-1。

pid_1 = 4209, pid_2 = 4210.
pid_1 = 4209, pid_2 = 0.
pid_1 = 0, pid_2 = 0.
pid_1 = 0, pid_2 = 4211.

​ 第一条语句,它打印的结果是两个子进程的ID号。说明它第一次调用fork()函数时,是作为父进程,第二次调用fork()函数时也是作为父进程。所以它是进程A打印出来的。

​ 第二条语句,它打印结果是一个子进程的ID号,”好像是一个父进程“。打印的第二个值是0,说明第二次作为了子进程。似乎ABCD没有符合这样一个关系逻辑的进程。因为子进程是父进程的拷贝,父进程的内容有pid_1 = 4209,所以这个子进程也将pid_1 = 4209打印了出来,自己是作为一个子进程,第二次调用fork()函数时被创建,返回值为0。所以它是进程C打印出来的,它的父进程是进程A。

​ 第三条语句,pid_1 = 0,pid_2 = 0;第一次调用fork函数时,作为子进程被创建,第二次调用fork函数时,也作为子进程被创建。它的父进程也是有其它进程创建的一个子进程,所以,符合这个关系特点的进程时进程D,它的父进程是进程B。

​ 第四条语句,pid_1 = 0, pid_2 = 4211;第一次调用fork函数时,作为子进程被创建,第二次调用fork函数时,作为父进程创建了新的子进程,返回了子进程的ID号。满足这个关系的进程只有进程B,它的父进程是进程A。

​ 综上,上述语句从上到下依次由进程A、进程C、进程D与进程B打印。

2.3.2 ABCD四个进程的id号是多少?

​ 进程A第一次调用fork函数创建了子进程B,所以子进程B的id号为:4209。进程A第二次调用fork函数创建了进程C,所以进程C的id号为4210。进程B创建了子进程D,所以D的id号是:4211。至于父进程A的id号,目前打印的信息无法得到。但是可以通过进程B调用getppid函数获得。

2.4 子进程与父进程的特点

2.4.1

​ 函数中调用fork函数后,后面的代码父子进程就会分开执行。我们利用父子进程调用fork函数后返回的值不同来做一个判断,如果是父进程,则执行一段代码;如果是子进程,则执行另一段代码。

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

int main(void)
{
        pid_t pid;

        pid = fork();

        if(pid > 0)					// 父进程
        {
                while(1)
                {
                        printf("Parent process.\n");			// 死循环,每隔1秒钟,打印一次。
                        sleep(1);
                }
        }
        else if(pid == 0)			// 子进程
        {
                while(1)
                {
                        printf("Child process.\n");				 死循环,每隔1秒钟,打印一次。
                        sleep(1);
                }
        }
        else
                perror("child process create failed!");

        return 0;
}

运行结果:

Parent process.
Child process.
Parent process.
Child process.
Child process.
Parent process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Child process.
Parent process.
Child process.
Parent process.
Parent process.
Child process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
^C

​ 根据打印结果来看:打印内容每隔1秒钟,成对打印。父子进程看上去是并发执行的,也就是同时执行的。如果继续探讨父子进程谁先执行,谁后面执行。根据邴老师的讲解,Linux内核2.6版本以后,官方介绍,默认情况下父进程先运行,因为父进程的代码处于活跃状态,也是为了程序效率,所以父进程先运行。

2.4.2

​ 我们定义并初始化一个全局变量count = 0,分别在父子进程中对它进行+1,并且打印出来。如果父进程先执行+1操作,那么子进程中会从1开始进行+1操作嘛?

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

int count = 0;			// 定义一个全局变量,并初始化为0.

int main(void)
{
        pid_t pid;

        pid = fork();

        if(pid > 0)
        {
                while(1)
                {
                        printf("Parent process, count = %d.\n", count++);			// +1
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("Child process, count = %d.\n", count++);			// +1
                        sleep(2);
                }
        }
        else
                perror("child process create failed!");


        return 0;
}

​ 运行结果:

Parent process, count = 0.
Child process, count = 0.
Parent process, count = 1.
Parent process, count = 2.
Child process, count = 1.
Parent process, count = 3.
Parent process, count = 4.
Child process, count = 2.
Parent process, count = 5.
Child process, count = 3.
Parent process, count = 6.
Parent process, count = 7.
Child process, count = 4.
Parent process, count = 8.
Parent process, count = 9.
Child process, count = 5.
Parent process, count = 10.
Parent process, count = 11.
Child process, count = 6.
Parent process, count = 12.
Parent process, count = 13.
Child process, count = 7.
Parent process, count = 14.
Parent process, count = 15.
Child process, count = 8.
Parent process, count = 16.
Parent process, count = 17.
Child process, count = 9.
Parent process, count = 18.
Parent process, count = 19.
^C

​ 可以看出,父进程每隔1秒钟+1,从0加到了19。子进程每隔2秒+1,从0加到了9。尽管count是一个全局变量,但是在父子进程中分别独立,只是变量名相同。

2.4.3

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

int count = 0;

int main(void)
{
        pid_t pid;
        int i = 0;

        pid = fork();

        if(pid > 0)
        {
                for(i = 0; i < 5; i++)
                {
                        printf("Parent process, count = %d.\n", count++);
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("Child process, count = %d.\n", count++);
                        sleep(1);
                }
        }
        else
                perror("child process create failed!");


        return 0;

​ 运行结果:

Parent process, count = 0.
Child process, count = 0.
Parent process, count = 1.
Child process, count = 1.
Child process, count = 2.
Parent process, count = 2.
Parent process, count = 3.
Child process, count = 3.
Child process, count = 4.
Parent process, count = 4.
Child process, count = 5.
sgk@ubuntu:~/Documents/Linux_Program$ Child process, count = 6.
Child process, count = 7.
Child process, count = 8.
Child process, count = 9.
Child process, count = 10.
Child process, count = 11.
Child process, count = 12.
Child process, count = 13.
Child process, count = 14.
^C

​ 根据结果可以看到,父进程结束后,子进程任然在执行。所以,父进程不影响子进程。

2.5 总结

​ fork函数用来创建子进程,调用后,父进程返回子进程id号。子进程返回0,创建失败返回-1 。子进程是父进程的拷贝,与父进程执行相同的代码。严格意义上来说,父进程先运行,但是宏观上看,父子进程是并发执行的。父子进程运行在不同的代码空间内,相互不影响。父进程的生命周期对子进程也无影响。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一分钟带你上手JS对象的基本用法

前言 相信大家对 JavaScript 中的对象都不陌生&#xff0c;而且我们几乎每天都在使用它&#xff0c;那你对对象的认识有多少呢&#xff1f;本章就带大家一起出浅入深的了解 JavaScript 中的对象。 一、什么是对象&#xff1f; 到底什么是对象呢&#xff1f;大多数人可能都会脱…

生物信息学——基础篇——一至三代测序技术

生物信息学 生物信息学——基础篇——一至三代测序技术 文章目录生物信息学一、一代测序二、二代测序三、三代测序四、总结一、一代测序 概述&#xff1a;一代测序&#xff08;又称Sanger测序&#xff09;。 原理&#xff1a;Sanger测序利用一类特殊的核昔酸&#xff0c;即dd…

imx6ull内核添加exfat,并自动开机加载

下载地址&#xff1a;https://github.com/dorimanx/exfat-nofuse.git 方式一&#xff0c;移植到内核&#xff0c;通过内核开启 1、下载exfat源码&#xff08;将源码目录设置成exfat方便修改&#xff09;&#xff0c;将其放到内核的fs目录下 2、修改fs目录下的Kconfig文件 3、…

Go语言设计与实现 -- GC的简要介绍

GC实现原理 什么是GC&#xff1f; 垃圾回收也称为GC&#xff08;Garbage Collection&#xff09;&#xff0c;是一种自动内存管理机制 现代高级编程语言管理内存的方式分为两种&#xff1a;自动和手动&#xff0c;像C、C 等编程语言使用手动管理内存的方式&#xff0c;工程师…

OpenHarmony 标准系统 HDF 框架音视频驱动开发

OpenHarmony 标准系统 HDF 框架音视频驱动开发引言OpenHarmony 音频概述HDF 音频驱动框架概述HDF 音频驱动框架分析 —— 音频设备驱动HDF 音频驱动框架分析 —— supportlibs 实现HDF 音频驱动框架分析 —— hdi-passthrough 实现HDF 音频驱动框架分析 —— hdi-binder 实现HD…

从零开始计算机网络——计算机网络课程的了解初步认识计算机网络

目录 &#x1f358;计算机网络学科到底学什么&#xff1f; &#x1f9c7;两个参考模型的介绍 &#x1f96a;OSI参考模型&TPC参考模型&五层参考模型 &#x1f371; 计算机网络学科的重难点——网络协议 &#x1f958;如何学好计算机网络课程&#xff1f; &#x1f363;相…

Java使用BigDecimal(公式精确计算)+(精度丢失问题)

一、Java使用BigDecimal公式计算(精确计算) 介绍: 使用BigDecimal加减乘除方法运算&#xff0c;可以使用BigDecimal类提供的add、subtract、multiply、divide方法函数实现。 公式加法计算~add public static void main(String[] args){BigDecimal a BigDecimal.valueOf(5.6);…

动态规划合集

62 斐波那契数列 public class Solution {public int Fibonacci(int n) { return f(n);}public int f(int n){if(n1||n2){return 1;}return f(n-1)f(n-2);} }这种做法时间复杂度O(2^N),空间复杂度是用递归栈&#xff0c;O(n) 改进&#xff1a;用动态规划&#xff0c;可以…

Pytorch优化器全总结(三)牛顿法、BFGS、L-BFGS 含代码

目录 写在前面 一、牛顿法 1.看图理解牛顿法 2.公式推导-三角函数 3.公式推导-二阶泰勒展开 二、BFGS公式推导 三、L-BFGS 四、算法迭代过程 五、代码实现 1.torch.optim.LBFGS说明 2.使用LBFGS优化模型 优化器系列文章列表 Pytorch优化器全总结&#xff08;一&…

C 程序设计教程(09)—— 数据输出函数(printf)用法详解

C 程序设计教程&#xff08;09&#xff09;—— 数据输出函数&#xff08;printf&#xff09;用法详解 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用户阅读。 目录…

Python小案例

1、简单的打印输出 age =18 print("我的名字是%s,我的国籍是%s"%("小张","中国")) print("我的年纪是:%d岁"%age) print("www","baidu","com",sep=".") #sep是使用.分割的意思,这个输出是百…

微信小程序开发——小程序的宿主环境API,协同工作和发布

一.小程序API概述 小程序中的 API 是由宿主环境提供的&#xff0c;通过这些丰富的小程序 API &#xff0c;开发者可以方便的调用微信提供的能力&#xff0c;例如&#xff1a;获取用户信息、本地存储、支付功能等。 二.小程序API的3大分类 a.事件监听AP1 特点&#xff1a;以…

【服务器数据恢复】服务器硬盘掉线的数据库数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某公司服务器&#xff0c;配备24块FC硬盘&#xff0c;两块硬盘出现故障掉线&#xff0c;导致服务器上层的卷无法挂载。 服务器数据恢复过程&#xff1a; 1、查看服务器硬盘状态发现有两块硬盘离线&#xff0c;将服务器内的所有硬盘做好…

【数据结构-JAVA】栈(Stack)和队列(Queue)

栈1.1 栈的概念栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶&#xff0c;另一端称为栈底。栈中的数据元素遵守先进后出&#xff0c;后进先出的原则&#xff08;LIFO——Last In First Out&a…

【从零开始学习深度学习】41. 算法优化之RMSProp算法【基于AdaGrad算法的改进】介绍及其Pytorch实现

上一篇文章AdaGrad算法中提到&#xff0c;因为调整学习率时分母上的变量st\boldsymbol{s}_tst​一直在累加按元素平方的小批量随机梯度&#xff0c;所以目标函数自变量每个元素的学习率在迭代过程中一直在降低&#xff08;或不变&#xff09;。因此&#xff0c;当学习率在迭代早…

LeetCode 45. 跳跃游戏 II

45. 跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09; 解法1&#xff1a;&#xff08;动态规划 贪心&#xff09; 果然代码越短&#xff0c;思路越难。这题用的是动态规划贪心的思想。首先分析题意我们可以知道&#xff0c;从索引0这个点开始&#xff0c;我们走一步可以…

redis命令第二弹

1、redis命令-hash类型练习2、redis命令-list类型练习3、redis命令-set类型练习

YOLOV5环境搭建以及训练COCO128数据集

前言记录了自己训练coco128的全过程手把手教你YOLOV5环境搭建以及训练COCO128数据集。相关配置文件在百度网盘中。如果懒得话可以直接全部用我的数据一、准备工作1.1创建环境打开anaconda power shell&#xff08;最好以管理员身份运行&#xff0c;免得到后面相关文件权限进不去…

sentinel-介绍(一)

Sentinel Website&#xff08;Sentinel 官网网站&#xff09; Sentinel: 分布式系统的流量防卫兵 Sentinel 是什么&#xff1f; 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级、系…

ansible配置yum源仓库

1.挂载本地光盘到/mnt 2.配置yum源仓库文件通过多种方式实现 仓库1 &#xff1a; Name: RH294_Base Description&#xff1a; RH294 base software Base urt: file:///mnt/BaseOS 不需要验证钦件包 GPG 签名 启用此软件仓库 仓库 2: Name: RH294_S…