😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍fork函数 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
本文未经允许,不得转发!!!
函数原型
#include <unistd.h>
pid_t fork(void);
返回值:父进程返回子进程ID,子进程返回0,出错在父进程返回-1。
一个现有进程可以调用fork
函数创建一个新进程。由fork
创建的新进程被成为子进程。使用fork函数有几个特点:
执行两次
,父进程执行一次,子进程执行一次;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次,其实是在父进程返回一次,在子进程返回一次。两次返回的唯一区别
是子进程的返回值是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 函数执行后,创建的子进程会复制父进程的数据段、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的虚拟内存,所以是可以一样的。另外,如果这些变量是共享的话,那么子进程改变值之后,父进程后来打印的应该是子进程修改后的值,但结果显然不是,说明这些变量不是共享的,而是在各自进程都有一份,它们的地址值刚好相同而已。
如果父进程有文件描述符,子进程会复制文件描述符,但不复制文件表,共用一个文件表,所以也共用一个文件偏移量。如下图:
复制文件描述符有点像上一小节的复制变量,就只是复制了保存描述符的变量,导致父进程和子进程的描述符都指向同一个文件表,也就共用同一个文件偏移量,不管父进程写入还是子进程写入都会导致这个文件偏移量产生偏移。下面例子,父进程先执行,写入'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语言例子进行说明。
如果文章有帮助的话,点赞👍、收藏?,支持一波,谢谢 😁😁😁