fork() 函数详解

fork() 函数是 linux/unix 下一种特别的创建子进程的函数,它不同与 Windows,这个函数在执行成功后会有两个返回值,一个返回值==0代表创建了子进程,一个返回值大于0代表还是当前程序进程,而这个大于0的值就是创建的子进程的进程PID。这个函数比较抽象,我们来看一下代码并对比一下图片就能知道具体该函数的用途了。

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

int main(int argc, char* argv[])
{
	pid_t pid = fork();

	if (pid == 0)
	{
		// 新创建的子进程
		while (1)
		{
			printf("child : PID = %d, ParentID = %d\n", getpid(), getppid());
			sleep(1);
		}
	}
	else if (pid > 0)
	{
		// 当前进程
		while (1)
		{
			printf("parent: PID = %d, ParentID = %d\n", getpid(), getppid());
			sleep(1);
		}
	}

	return 0;
}

该程序运行后,会产生两个分支,一个分支是创建的新子进程所执行的分支,它会不断的打印自身的PID和父进程的PID。

另外一个分支是原有的进程分支,它也同样会不断的打印自身的进程PID和父进程的PID,运行后效果图如下:

2015-06-29_121339

图中可以看出,新创建的子进程 PID = 3606,其父进程 PID = 3605 也就是我们运行的 fork 程序的 PID,而另外一个分支 fork 程序的 PID = 3605,其父进程 PID = 2012,就是终端所属的 bash 进程的 PID。这样我们就可以得出结论,我们手动通过终端(PID 2012)调用了 fork 程序 (PID 3605),然后 fork 程序又创建了一个子进程 (PID 3606)。

以上就是  fork 函数的具体功能,它看上去更像是在进程中创建了一个线程,但实际并不是,这是 linux/unix 一种特有的创建进程的方式。当子进程创建时,其复制父进程 0~3G的虚拟地址空间和父进程内核中的PCB(PCB虽然复制但是ID号不同),他们的数据在读取时是共享的,在写时执行复制。

所谓“读时共享,写时复制”到底是怎么样一个概念?我们看一下下面的例子:

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

int main(int argc, char* argv[])
{
	pid_t pid;
	char *message;
	int n;
	pid = fork();
	if (pid < 0) {
		perror("fork failed");
		exit(1);
	}
	if (pid == 0) {
		message = "This is the child\n";
		n = 6;
	} else {
		message = "This is the parent\n";
		n = 3;
	}
	for(; n > 0; n--) {
		printf("%s", message);
		sleep(1);
	}
	return 0;
}

对于代码中的变量 n 来说,在创建了子进程以后,对 n 进行了修改,此时就会复制父进程虚拟地址空间中的信息提供给子进程继续向下执行,两个进程使用完全不同的地址空间,所以父进程最后for循环只会执行3次,而子进程则会执行6次。如下图(因为父进程提前执行完毕,所以没等子进程打印完信息,终端就恢复运行了):

2015-06-29_145823

【gdb调试相关】

使用gdb调试的时候,gdb只能跟踪一个进程。可以设置gdb跟踪父进程或者是跟踪子进程。

  • set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
  • set follow-fork-mode parent 设置跟踪父进程。
  • 默认跟踪父进程。

发表评论