Unix系统分叉函数fork()

本文探讨了fork函数的工作原理及其在操作系统中的行为特点,通过一个具体的代码示例,详细分析了fork函数如何影响程序的执行流程及输出结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一直在学习操作系统的知识,今天闲逛在BBS上看到一道题挺有意思:

int main()
{
	int x = 5;
	if(fork())
	{
		x += 30;
		cout<<x<<endl;
	}
	else
	{
		cout<<x<<endl;
	}
	return 0;
}

问题是输出是什么?


于是查询fork()函数,可知:

头文件:
#include<unistd.h>       /* #包含<unistd.h> */
#include<sys/types.h>       /* #包含<sys/types.h> */


函数原型:
pid_t fork( void);
(pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中)
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1


函数说明:

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。
为什么fork会返回两次?
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。过程如下图。


fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
(1)在父进程中,fork返回新创建子进程的进程ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值。
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
引用一位网友的话来解释fork函数返回的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fork函数返回的值指向子进程的进程id, 因为子进程没有子进程,所以其fork函数返回的值为0.
调用fork之后,数据、堆栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回,箭头表示各自的执行处。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂。


因此我们可以推断出,最开始的题目有三种可能的输出结果:

(1)输出为5,35:当父进程成功fork出子进程后,父进程返回子进程ID,而子进程返回0.所以父进程进入else段,输出5,而子进程输出为35.通过上述介绍中红色字体部分可知,无法确认父进程先运行还是子进程先运行.所以最终输出可能是(5,35)也可能是(35,5)

(2)父进程fork子进程失败,返回值为一个负数,那么代码直接进入else段,输出5.


所以最终答案为:(5,35)或(35,5)或(5)


### 在 Windows 系统中实现 `fork` 功能 #### 替代方案概述 由于 Windows 并未原生支持 POSIX 的 `fork()` 函数,因此需要通过其他方式来模拟其行为。常见的替代方法包括使用多线程库、创建新进程以及利用第三方工具或框架。 --- #### 使用 `_beginthreadex` 和 `CreateProcess` 在 Windows 上可以借助 `_beginthreadex` 创建线程或者通过 `CreateProcess` 来启动一个新的独立进程。这两种方法都可以用来模仿 `fork()` 的部分功能。 ##### 示例代码:基于 `_beginthreadex` 实现简单的并发逻辑 ```cpp #include <windows.h> #include <process.h> #include <stdio.h> unsigned __stdcall ThreadFunc(void* arg) { printf("Child thread running\n"); return 0; } int main() { HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, NULL, 0, NULL); if (hThread != NULL) { WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } printf("Parent process continuing...\n"); return 0; } ``` 此代码片段展示了如何在一个进程中创建并运行另一个线程[^5]。 ##### 示例代码:基于 `CreateProcess` 启动新进程 ```cpp #include <windows.h> #include <tchar.h> #include <stdio.h> void CreateNewProcess() { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // Start the child process. if (!CreateProcess( NULL, // No module name (use command line). _T("notepad.exe"), // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi // Pointer to PROCESS_INFORMATION structure. )) { printf("CreateProcess failed (%d).\n", GetLastError()); return; } // Wait until child process exits. WaitForSingleObject(pi.hProcess, INFINITE); // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } int main() { CreateNewProcess(); printf("Parent process continues after creating a new process.\n"); return 0; } ``` 上述代码演示了如何调用 `CreateProcess` API 来生成一个全新的子进程[^6]。 --- #### 使用 Cygwin 或 MinGW 提供的兼容层 Cygwin 是一种允许开发者在 Windows 下编译和运行 Unix/Linux 风格应用程序的技术栈。它内部实现了自己的版本 `fork()` 调用,尽管性能可能不如 Linux 原生环境高效。 安装好 Cygwin 工具链之后可以直接编写如下标准 C/C++ 程序: ##### 示例代码:Cygwin 中的标准 fork() ```c #include <unistd.h> #include <stdio.h> int main() { pid_t pid = fork(); if (pid == 0) { // 子进程 printf("I am the child process with PID %d\n", getpid()); } else if (pid > 0) { // 父进程 printf("I am the parent process with PID %d\n", getpid()); } else { // 错误处理 perror("Failed to fork"); } return 0; } ``` 注意,在某些情况下可能会遇到资源占用较高的问题,因为 Cygwin 的仿真机制并非完全轻量级[^7]。 --- #### JDK Fork/Join 框架作为高级解决方案 如果目标是在 Java 应用程序上下文中寻找类似的分叉操作,则可以考虑采用 JDK 自带的 Fork/Join 框架。该框架特别适合解决大规模数据集上的复杂计算任务分割需求。 以下是简单实例展示如何定义自定义任务类继承自 `RecursiveAction` 类型完成工作分配过程: ##### 示例代码:Java Fork/Join 构建树形结构任务划分 ```java import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; public class MyForkJoinTask extends RecursiveAction { private final int startValue; private final int endValue; public MyForkJoinTask(int startValue, int endValue) { this.startValue = startValue; this.endValue = endValue; } @Override protected void compute() { if ((endValue - startValue) <= 10) { System.out.println(String.format("Processing range [%d-%d]", startValue, endValue)); } else { int midPoint = (startValue + endValue) / 2; invokeAll(new MyForkJoinTask(startValue, midPoint), new MyForkJoinTask(midPoint + 1, endValue)); } } public static void main(String[] args) throws InterruptedException { ForkJoinPool pool = new ForkJoinPool(4); pool.invoke(new MyForkJoinTask(0, 100)); pool.shutdown(); } } ``` 这里我们看到的是典型的递归分解模式,其中原始大范围被逐步拆分为更小的部分直到达到预定阈值为止[^8]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值