目录
fork函数
返回值
内存分配
父子进程是操作系统一个重要的概念,特别是在多任务处理和并发编程中,在Linux中,每个进程都有一个唯一的进程ID,并且每个进程都有可能创建其他进程。当一个进程创建了一个新的进程时,新创建的进程就成为了原始进程的子进程。
同样用生活中的例子来理解。在一个家庭中(类比一个操作系统中),父母决定做晚饭(做饭就是一个进程),他们分配给孩子一个任务,让大儿子洗菜,小儿子烧水(洗菜和烧水就是两个子进程),父母和孩子之间相互协作,共同完成了这一顿晚饭,这就是父子进程之间相互协作和独立执行任务的特性。
fork函数
“fork()”函数是在Linux中用于创建新进程的系统调用之一。调用fork()函数时,操作系统会创建当前进程的一个副本,即子进程。
函数原型
#include <unistd.h>
pid_t fork(void);
fork函数的定义在unistd.h头文件中完成。
返回值
考虑一个问题:fork函数的返回值是什么呢?我们用下面这段代码来验证一下
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
printf("hello proc : %d!, ret: %d\n", getpid(), ret);
sleep(1);
return 0;
}
结果如下:
诶?明明应该只有一行输出结果,为什么这里会有两行呢,而且结果还不一样
事实上,这两行输出就是父子进程并发执行的结果。第一行是父进程的运行结果,此时的进程id(PID)是“65781”,fork的返回值是子进程id(PPID)“65782”;第二行是子进程的运行结果,fork返回值是“0”。
下面这段代码就可以更清晰地观察父子进程:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
运行结果如下:
内存分配
在fork函数调用之后,父进程和子进程都将拥有相同的内存空间映像,但它们是相互独立的。这意味着,当其中一个进程修改了内存中的数据,另一个内存不会受到影响。那么这是如何实现的呢?如果父子进程都指向同一块内存空间,那么数据的修改会互相影响,所以它们是指向两块不同的空间,只是这两块空间存放相同的数据,是这样吗?nonono,系统用了一个很聪明的方法,那就是写时拷贝。
写时拷贝是一种延迟复制技术,它使得在fork函数中进行子进程的创建时,实际上并不立即复制父进程的内存空间,而是等到子进程尝试修改其中某个页面时才进行复制。这样可以减少内存开销,提高效率,特别是当父子进程在大部分时间内只读取数据而不修改时。下面用一张图来加深理解:
下面用代码来验证fork函数的写时拷贝:
#include <stdio.h>
#include <unistd.h>
int main() {
int x = 1;
pid_t pid = fork();
if (pid == 0) {
// 子进程
sleep(1);
printf("x的值:%d,x的地址:%p\n",x,&x);
x = 10; // 修改子进程的 x 值
} else if (pid > 0) {
// 父进程
x = 20; // 修改父进程的 x 值
sleep(2);
printf("x的值:%d,x的地址:%p\n",x,&x);
} else {
// fork() 调用失败
fprintf(stderr, "fork() failed\n");
return 1;
}
return 0;
}
这里的sleep()函数是为了达到 父进程修改变量-->显示子进程信息,子进程修改变量-->显示父进程信息 的进程效果,如果父子进程之间变量的修改没有互相影响,就说明进行了写时拷贝。
来看结果:
可以看出父子进程对x的修改并没有影响到对方,但是为什么这里x的地址是相同的呢?
因为在fork函数调用后,操作系统为父子进程分配了不同的栈空间,因此它们各自的变量‘x’都位于不同的栈空间之中。x的地址实际上是相对于各自的栈空间的偏移量,并不是指向相同的物理内存地址。实际上它们所指的是不同的物理内存地址。
换句话说,虽然地址看起来相同,但实际上它们处于不同的内存空间,所以这并不违背写时拷贝的原理。
一上就是父子进程与fork函数的相关知识了,欢迎在评论区留言,觉得俺的博客对你有帮助的可以点赞关注支持一波窝~😉