Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

进程作为操作系统中的基本管理单位,起着重要作用,那么进程到底是怎么被创建的,创建进程时到底都发生了些什么,本文将会讨论这些问题。

进程是如何被创建的

从现象上看,当我们执行一个main方法时,会创建一个进程,当我们执行一条linux命令时,也会创建一个进程。要说进程究竟是如何被创建的,这里给出两个回答:

  1. 进程是被它的父进程创建的
  2. 进程是被fork出来的

这两个回答都对,只是从不同层面回答了这个问题。

进程是被它的父进程创建的

如果去一台linux机器上ps -eaf一下,你会看到每个进程都会对应一个父进程,下图中第二列为当前进程的pid,第三列为父进程的pid。并且所有的进程都会有一个祖先进程,即pid为1的进程。

This is a picture without description

如果将这些进程的父子关系组织起来,那它们应该是一个树状的结构,可以看到,所有进程都有一个公共的根进程,即pid为1的systemd:

执行pstree命令

那么问题来了,这个pid为1的进程到底是何方神圣?

pid为1的进程是什么?

Pid为1的进程被称为init进程,它是系统启动时最早启动的用户级进程,是所有其他用户级进程的祖先。init进程负责执行系统引导过程中所需的初始化任务,然后它会启动其他系统服务和用户级别的进程。实际上,init进程有很多不同的实现,如SysVinit, launched, upstart,systemd等,现行的linux操作系统重,基本systemd已成为主流。

This is a picture without description

进程是被fork出来的

如前文所述,进程是被它的父进程创建的,而创建的具体方式,正是fork. 理论上来讲,fork的时候子进程会得到父进程地址空间的一个副本,包括代码段、数据段、堆栈等,但是由于通常子进程和父进程执行并不是同样的内容,而是会通过exec系统调用装填一段新的代码进去,因此出于效率考虑,linux使用了一种叫做写时复制(Copy-On-Write)的概念,即当fork发生时,子进程并不会完全拷贝一份父进程地址空间的内容,而是把父进程的地址空间内容改为只读状态,此时如果父进程或者子进程尝试修改这些区域,内核会为要被修改的区域所在的页制作一个副本。

COW机制

我们在linux上使用c语言创建一个进程,并fork出一个子进程,来解释这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>


int main() {
int value = 42;

pid_t pid = fork();

if (pid == 0) {
// 子进程
printf("Child process. PID: %d\n", getpid());
printf("Child: Initial value = %d\n", value);

// 修改值,触发COW机制
value = 99;
printf("Child: Modified value = %d\n", value);

} else if (pid > 0) {
// 父进程
sleep(5);
printf("Parent process. PID: %d\n", getpid());
printf("Parent: Initial value = %d\n", value);

// 等待子进程执行完毕
wait(NULL);

// 输出修改后的值,观察是否受到子进程修改的影响
printf("Parent: After child's modification, value = %d\n", value);
} else {
// 错误处理
fprintf(stderr, "Fork failed\n");
return 1;
}

return 0;
}

在上面代码中,我们在父进程中初始化了一个int类型的value变量,值为42,并在子进程中修改value, 最后在父进程中观察value的值。理论上来讲,由于COW机制,当子进程修改value时,操作系统会将父进程中value所在的页复制出来,并分配给子进程,因此,子进程对value的修改并不会影响到父进程中的value.

输出如下:

1
2
3
4
5
6
Child process. PID: 2059
Child: Initial value = 42
Child: Modified value = 99
Parent process. PID: 2058
Parent: Initial value = 42
Parent: After child's modification, value = 42

可以看到,父进程中的value依然是42.

需要注意的是,COW对于共享型的内存映射(shared mmap)是不生效的(毕竟听名字就知道,它是进程共享的),举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>

int main() {
// 创建匿名映射,大小为一个整数的大小
size_t size = sizeof(int);
int *shared_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

if (shared_data == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}

// 初始化共享数据
*shared_data = 42;

// 使用 fork() 创建子进程
pid_t pid = fork();

if (pid == 0) {
// 这是子进程
printf("Child process. PID: %d\n", getpid());

// 修改共享数据
*shared_data = 99;

// 输出子进程的共享数据
printf("Child: Modified value = %d\n", *shared_data);

// 释放映射
munmap(shared_data, size);

} else if (pid > 0) {
// 这是父进程
printf("Parent process. PID: %d\n", getpid());

// 输出父进程的共享数据
printf("Parent: Initial value = %d\n", *shared_data);

// 等待子进程执行完毕
wait(NULL);

// 输出修改后的值
printf("Parent: After child's modification, value = %d\n", *shared_data);

// 释放映射
munmap(shared_data, size);

} else {
// 错误处理
perror("fork");
exit(EXIT_FAILURE);
}

return 0;
}

对于上面代码,我们创建了一个匿名的共享的mmap,它的初始值为42,并在子进程中修改该内存中的值为99,那么该值的变化最后将会反映到父进程中,输出如下:å

1
2
3
4
5
Parent process. PID: 2364
Child process. PID: 2365
Child: Modified value = 99
Parent: Initial value = 99
Parent: After child's modification, value = 99

如果我们把第十行的SHARED改为PRIVATE:

1
int *shared_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

那么子进程对共享内存的修改将不会影响到父进程中的该值(当然了,多进程之间使用匿名mmap的用之一就是用于通信,如果设为PRIVATE就失去了其意义,这里这么做只是为了说明COW的效果。)

参考

https://coolshell.cn/articles/17998.html

评论