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

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

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

目录

  • 一、fork 函数概述
  • 二、fork 函数之后的代码会执行2次
  • 三、fork 返回2次,父进程返回子进程ID,子进程返回0
  • 四、fork 创建的子进程会复制父进程资源
    • ✨4.1 复制数据段、bss段、堆、栈
    • ✨4.1 复制文件描述符
  • 五、总结


在这里插入图片描述

一、fork 函数概述

函数原型

#include 
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 
#include 
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 
#include 
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 
#include 
#include 
#include 

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;
}

运行结果:
【Linux C | 进程】创建进程 | fork函数详解_第1张图片
从运行结果看,子进程先打印几个变量的地址和值,这几个值就是我们设置好的,然后又打印了改变之后的值。5秒后,父进程也打印这些变量的值,发现打印的值是我们设置的而不是子进程修改过的。

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


✨4.1 复制文件描述符

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

#include 
#include 
#include 

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语言例子进行说明。

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

你可能感兴趣的:(#,✨Linux,C语言,linux,fork,函数,fork函数返回2次)