【Linux C | 进程】创建进程 | fork函数详解

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍fork函数 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭

本文未经允许,不得转发!!!


在这里插入图片描述

🎄一、fork 函数概述

函数原型

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

返回值:父进程返回子进程ID,子进程返回0,出错在父进程返回-1。


一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被成为子进程。使用fork函数有几个特点:

  • 1、fork函数之后的代码会执行两次,父进程执行一次,子进程执行一次;
  • 2、fork函数返回两次,在父进程返回子进程的进程ID,在子进程返回 0;
  • 3、fork函数创建的子进程会复制父进程除了代码区之外所有区域(包括数据段、bss段、堆、栈等);
  • 4、如果父进程有文件描述符,子进程会复制文件描述符,但不复制文件表,共用一个文件表;
  • 5、调用fork函数后,无法确定是子进程先执行,还是父进程先执行;

在这里插入图片描述

🎄二、fork 函数之后的代码会执行2次

fork函数是复制进程的。C程序一旦执行之后,就产生一个进程,当执行到fork函数时,就会复制当前进程来创建一个新的进程,也就是子进程。所以,fork函数执行后,父子进程都会执行fork之后的代码。

看例子:

#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("程序开始执行!\n");
    fork();
    printf("pid =%d 程序执行过程中....\n", getpid());
    
    while(1)
		sleep(1);
    printf("程序执行结束\n");
    return 0;
}

上面程序,在fork之后的printf语句执行了两次,使用getpid获取它们的进程ID,从结果可以看出是不同进程。
在这里插入图片描述


在这里插入图片描述

🎄三、fork 返回2次,父进程返回子进程ID,子进程返回0

fork 函数返回2次,其实是在父进程返回一次,在子进程返回一次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。

根据fork在父子进程返回值不同的特点,我们可以判断返回值分辨父进程、子进程,然后分别在父进程、子进程做不同的事情。

看例子:

#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("程序开始执行!\n");
    pid_t pid = fork();
	if(pid>0) // 父进程
	{
		printf("父进程执行过程中.... fatherPid=%d, childPid=%d\n", getpid(), pid);
	}
	else if(pid==0) // 子进程
	{
		printf("子进程执行过程中.... fatherPid=%d, childPid=%d\n", getppid(), getpid());
	}
	else
	{
		printf("fork error\n");
	}
    
    while(1)
		sleep(1);
    printf("程序执行结束\n");
    return 0;
}

在这里插入图片描述


在这里插入图片描述

🎄四、fork 创建的子进程会复制父进程资源

fork 函数执行后,创建的子进程会复制父进程的数据段、bss段、堆、栈、文件描述符等,与父进程共享代码段。

✨4.1 复制数据段、bss段、堆、栈

如果对数据段、bss段、堆、栈不了解的,可以看上一篇文章。

下面例子,定义了变量i(bss段)、变量j(数据段)、变量s(栈)、指向mallco分配内存的变量str(堆)。fork函数指向后,父进程延时5秒,让子进程先执行,子进程先打印这几个变量的地址和值,然后修改这些变量,在打印变量的值;5秒后,父进程也执行,打印这几个变量的值。

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

int i;		// 存在于程序的bss段
int j=100;	// 存在于程序的数据段
int main()
{
	short s=10; // 存在于栈
	char *str = malloc(20); // 存在于堆
    strcpy(str, "abcdef");
	
    printf("程序开始执行!\n");
	
    pid_t pid = fork();
	if(pid>0) // 父进程
	{
		sleep(5);
		printf("父进程执行过程中.... fatherPid=%d, childPid=%d\n", getpid(), pid);
		printf("&i=%p, &j=%p, &s=%p str=%p\n", &i,&j,&s,str);
		printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
	}
	else if(pid==0) // 子进程
	{
		printf("子进程执行过程中.... fatherPid=%d, childPid=%d\n", getppid(), getpid());
		printf("&i=%p, &j=%p, &s=%p str=%p\n", &i,&j,&s,str);
		printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
		
		i=1;
		j=2;
		s=3;
		strcpy(str, "ABCDEF");
		printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
	}
	else
	{
		printf("fork error\n");
	}
	free(str);
    while(1)
		sleep(1);
    printf("程序执行结束\n");
    return 0;
}

运行结果:
在这里插入图片描述
从运行结果看,子进程先打印几个变量的地址和值,这几个值就是我们设置好的,然后又打印了改变之后的值。5秒后,父进程也打印这些变量的值,发现打印的值是我们设置的而不是子进程修改过的。

虽然父进程、子进程打印的变量地址值是一样的,但这些地址是虚拟内存的地址,每个进程都有独立的3G的虚拟内存,所以是可以一样的。另外,如果这些变量是共享的话,那么子进程改变值之后,父进程后来打印的应该是子进程修改后的值,但结果显然不是,说明这些变量不是共享的,而是在各自进程都有一份,它们的地址值刚好相同而已。


✨4.1 复制文件描述符

如果父进程有文件描述符,子进程会复制文件描述符,但不复制文件表,共用一个文件表,所以也共用一个文件偏移量。如下图:
在这里插入图片描述
复制文件描述符有点像上一小节的复制变量,就只是复制了保存描述符的变量,导致父进程和子进程的描述符都指向同一个文件表,也就共用同一个文件偏移量,不管父进程写入还是子进程写入都会导致这个文件偏移量产生偏移。下面例子,父进程先执行,写入'a',那么子进程再写入时就会在'a'后面写入,所以文件内容就是"ab"

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

int main()
{
	int fd = open("./fork_fd", O_RDWR | O_CREAT | O_TRUNC, 0775);
    printf("程序开始执行!\n");
    pid_t pid = fork();
	if(pid>0) // 父进程
	{
		printf("父进程执行过程中.... fd=%d\n", fd);
		char c = 'a';
		write(fd, &c, 1);
	}
	else if(pid==0) // 子进程
	{
		sleep(1);
		printf("子进程执行过程中.... fd=%d\n", fd);
		char c = 'b';
		write(fd, &c, 1);
	}
	else
	{
		printf("fork error\n");
	}
    
    while(1)
		sleep(1);
    printf("程序执行结束\n");
    return 0;
}

在这里插入图片描述

🎄五、总结

本文详细介绍 fork 函数,并列举C语言例子进行说明。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值