Linux进程建立之fork浅析

  在Linux 内核中,无时无刻不维护着进程,从进程的建立到进程销毁,每个环境都有着复杂的细节。本篇介绍Linux 内核如何建立进程,深刻理解 fork 函数以及子进程的建立,对理解多进程开发也相当重要。编程

fork() 函数

  首先来看下fork() 函数,其做用是建立子进程。头文件与函数原型以下多线程

#include <unistd.h>

// 参数	: void 
// 返回值: pid_t 建立的子进程ID
pid_t fork(void);

  返回值:fork() 返回值会返回两次,分别在父进程和子进程中返回。函数

  1. 在父进程中返回子进程的ID,在子进程中返回0。因此能够经过fork 的返回值来区分父进程与子进程。
  2. 在父进程中返回 -1 ,表示建立子进程失败,并设置 errorno。以下面两种状况致使建立失败:
  • 当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置为EAGAIN
  • 系统内存不足,这时 errno 的值被设置为 ENOMEM

fork() 示例

下面建立一个子进程,来讲明父进程与子进程的执行顺序。线程

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


int main(){
	// 建立进程
	pid_t pid = fork();

	// 判断当前进程是父进程 仍是子进程
	if (pid > 0){			// 进程号 > 0,即为子进程的进程号,当前为父进程
		printf("pid: %d\n", pid);
		printf("I am parent process, pid: %d, ppid: %d\n", getpid(), getppid());
	}
	else if (pid == 0){		// 进程号 == 0,表示当前为子进程
		printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());
	}
	
	for (int i = 0; i < 5; i++){
		printf("pid: %d, i : %d\n", getpid(), i);
        sleep(1);
	}
	return 0;
}

  编译执行,能够看到,子进程建立成功,两个进程同时执行,父进程ID 为412552,子进程ID为512553,因为sleep() 函数,使得函数阻塞,因此父进程与子进程交替执行。3d

fork-

父子进程的虚拟地址空间

  经过 fork函数建立进程后,能够经过返回值判断是父进程仍是子进程。对于父进程与子进程如何执行,下面介绍fork函数调用后,父子进程如何执行,在进程中虚拟地址空间中如何体现。指针

  
  首先,咱们先看一下上述示例的执行顺序,父进程执行执行fork后,返回子进程ID,pid 大于0,因此输出 if(pid>0) 的内容。
父进程code

  子进程在建立成功后,在子进程中返回 0,从当前位置开始执行,因此 pid=0 会输出 else 语句。
子进程执行魂虚
blog

  对于虚拟空间地址来讲,子进程会拷贝父进程的虚拟地址空间。因此,fork后子进程的用户区与父进程的用户区相同,也会拷贝内核区内容,仅仅是进程的 pid不一样。进程

父子进程的虚拟地址空间

  对于父进程中的栈空间的变量,也会原封不动的拷贝至子进程的栈空间中。但这两个变量互不影响,父进程改变变量不会影响子进程变量。看以下程序展现父子进程中栈空间变量的使用。内存

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


int main(){
	// 建立进程
	pid_t pid = fork();

	// 局部变量
	int num = 10;

	// 判断当前进程是父进程 仍是子进程
	if (pid > 0){			// 进程号 > 0,即为子进程的进程号,当前为父进程
		printf("I am parent process, pid: %d, ppid: %d\n", getpid(), getppid());

		printf("parent process num : %d\n", num);
		num += 10;
		printf("parent process num + 10 : %d\n", num);

	}
	else if (pid == 0){		// 进程号 == 0,表示当前为子进程
		printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());

		printf("child process num : %d\n", num);
		num += 100;
		printf("child process num + 100 : %d\n", num);
	}
	
	return 0;
}

编译执行,能够看到,父进程中的局部变量num 与子进程的局部变量互不影响。
num
  
读时共享,写时拷贝技术
  实际上,准确的来讲,Linux的fork 是经过 写时拷贝 (copy-on-write)实现。写时拷贝是一种能够推迟甚至不用避免拷贝的技术。更具体来说,在执行fork语句后,内核并不复制父进程的整个地址空间,而是父子进程共享父进程的地址空间(此时父子进程对于地址空间是只读指令),在父进程或者子进程进行写指令时,子进程才会复制一份地址空间,从而使得父子进程拥有本身的虚拟地址空间,在本身的地址空间进行写操做。也就是说,资源的复制是在须要写入时才会进行,在此以前,只会以只读方式进行共享

  对于文件资源,fork以后的父子进程共享文件,fork以后的父进程与子进程的文件描述符表指向相同的文件表,引用计数增长,共享文件偏移指针。

  fork函数就介绍到这里了,本篇介绍了建立子进程的过程,理解父子进程间虚拟地址空间的共享与复制。在多线程开发中,能够轻松分析父子进程的执行顺序与内存共享。

点关注,不迷路。一键三连是对我最大的支持,欢迎关注编程小镇,天天涨一点新姿式😄。

相关文章
相关标签/搜索