https://www.bilibili.com/video/BV1yJ411S7r6?vd_source=c97bb6a7ae803be0c349f31bf6ca8df0&spm_id_from=333.788.player.switch&p=49

1. 进程标识符 pid

  • 类型 pid_t: 传统意义上,有符号的 16 位整型数

  • ps 命令

1
2
3
4
5
6
7
man ps

ps axf

ps axm

ps ax -L
  • 进程号是顺次向下使用

  • getpid(): 获取当前进程的 pid

  • getppid():获取父进程的 pid

2. 进程的产生

fork()

1
2
3
#include <unistd.h>

pid_t fork(void);
  • 执行一次,返回两次

  • fork 之后,子进程执行的位置也与父进程相同 (duplicating)

  • fork 之后的不同点:

    • fork 的返回值不同
    • 父子进程的 pid 不同,ppid 也不同
    • 未决信号和文件锁不继承
    • 资源利用量清零
  • 父子进程是由调度器的调度策略来决定哪个进程先运行

  • fflush() 的重要性

  • 写时拷贝技术 (COW),谁改谁拷贝

示例1: 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
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

int main() {
pid_t pid;

printf("[%d]:Begin!\n", getpid());

pid = fork();
if (pid < 0) {
perror("Fork failed");
exit(1);
} else if (pid == 0) {
// Child process
printf("[%d]:Hello from Child!\n", getpid());
} else {
// Parent process
printf("[%d]:Hello from Parent! Child PID: %d\n", getpid(), pid);
}

printf("[%d]:End!\n", getpid());

getchar();

exit(0);
}
1
2
3
4
5
[5470]:Begin!
[5470]:Hello from Parent! Child PID: 5474
[5474]:Hello from Child!
[5470]:End!
[5474]:End!

Begin 为什么打印两次?

1
2
3
4
5
6
7
8
$ ./fork1 > /tmp/out
$ cat /tmp/out
[9529]:Begin!
[9529]:Hello from Parent! Child PID: 9530
[9529]:End!
[9529]:Begin!
[9530]:Hello from Child!
[9530]:End!

解决方法:

  • 终端是行缓冲区
  • 文件是全缓冲区
1
2
3
4
printf("[%d]:Begin!\n", getpid());

// 刷新所有成功打开的流
fflush(NULL);

示例2: 找质数

单进程版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

#define LEFT 30000000
#define RIGHT 30000200

int main() {
int i, j, is_prime;
for (i = LEFT; i <= RIGHT; i++) {
is_prime = 1;
for (j = 2; j * j <= i; j++) {
if (i % j == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
printf("%d is a prime number.\n", i);
}
}

exit(0);
}
  • 统计执行时间
1
2
3
4
5
$ time ./hello >> /dev/null

real 0m0.003s
user 0m0.001s
sys 0m0.002s

多进程版本

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
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include <sys/types.h>

#define LEFT 30000000
#define RIGHT 30000200

int main() {
int i, j, is_prime;
pid_t pid;
for (i = LEFT; i <= RIGHT; i++) {
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
} else if (pid == 0) {
// Child process
is_prime = 1;
for (j = 2; j * j <= i; j++) {
if (i % j == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
printf("%d is a prime number.\n", i);
}

// Child process exits after checking
exit(0);
}
}

exit(0);
}
  • 统计执行时间
1
2
3
4
5
$ time ./hello >> /dev/null

real 0m0.027s
user 0m0.002s
sys 0m0.025s

孤儿进程

  • 子进程退出前 sleep(1000)
1
2
3
sleep(1000);
// Child process exits after checking
exit(0);
1
2
3
4
5
6
7
8
9
$ ps axf
16066 ? S 0:00 | \_ /init
16068 pts/6 Ss+ 0:00 | \_ -bash
17451 pts/6 S 0:00 | \_ ./src/hello
17452 pts/6 S 0:00 | \_ ./src/hello
17453 pts/6 S 0:00 | \_ ./src/hello
17454 pts/6 S 0:00 | \_ ./src/hello
17455 pts/6 S 0:00 | \_ ./src/hello
...

僵尸进程

  • 父进程退出前 sleep(1000)
1
2
3
4
5
6
7
8
9
10
11
$ ps axf
16065 ? Ss 0:00 \_ /init
16066 ? S 0:00 | \_ /init
16068 pts/6 Ss 0:00 | \_ -bash
18521 pts/6 S+ 0:00 | \_ ./src/hello
18522 pts/6 Z+ 0:00 | \_ [hello] <defunct>
18523 pts/6 Z+ 0:00 | \_ [hello] <defunct>
18524 pts/6 Z+ 0:00 | \_ [hello] <defunct>
18525 pts/6 Z+ 0:00 | \_ [hello] <defunct>
18526 pts/6 Z+ 0:00 | \_ [hello] <defunct>
...

init 进程 ( 1 号)

所有进程的祖先进程

vfork()

vfork() 函数与 fork(2) 的作用相同,但如果由 vfork() 创建的子进程出现以下情况之一,则其行为是未定义的:

  1. 修改了除用于存储 vfork() 返回值的 pid_t 类型变量以外的任何数据;

  2. 从调用 vfork() 的函数中返回;

  3. 在成功调用 _exit(2) 或 exec(3) 函数族中的任何一个之前,调用了任何其他函数。

vfork() 是一个历史遗留的系统调用,设计初衷是为了在 fork() 后立即执行 exec() 时节省内存复制的开销。
它与 fork() 的关键区别在于:

  • 不复制地址空间:子进程在 exec() 或 _exit() 之前,共享父进程的内存空间。
  • 阻塞父进程:在子进程调用 exec() 或 _exit() 之前,父进程会被阻塞。

3. 进程的消亡及释放资源

  • 5 条正常终止,3条异常终止

API

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
// wait for process to change state

#include <sys/types.h>
#include <sys/wait.h>

// 阻塞
pid_t wait(int *wstatus);

// options
// WNOHANG: return immediately if no child has exited.
// pid
// > 0: wait for the child whose process ID is equal to the value of pid.
// = 0: meaning wait for any child process whose process group ID is equal to that of the calling process at the time of the call to waitpid().
// -1: meaning wait for any child process.
// <-1: meaning wait for any child process whose process group ID is equal to the absolute value of pid.
pid_t waitpid(pid_t pid, int *wstatus, int options);

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

// returns true if the child terminated normally
WIFEXITED(wstatus)

// returns the exit status of the child.
// This macro should be employed only if WIFEXITED returned true.
WEXITSTATUS(wstatus)

// returns true if the child process was terminated by a signal.
WIFSIGNALED(wstatus)

// returns the number of the signal that caused the child process to terminate.
// This macro should be employed only if WIFSIGNALED returned true.
WTERMSIG(wstatus)

示例

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
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include <sys/types.h>
#include <sys/wait.h>

#define LEFT 30000000
#define RIGHT 30000200

int main() {
int i, j, is_prime;
pid_t pid;
for (i = LEFT; i <= RIGHT; i++) {
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
} else if (pid == 0) {
// Child process
is_prime = 1;
for (j = 2; j * j <= i; j++) {
if (i % j == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
printf("%d is a prime number.\n", i);
}
// Child process exits after checking
exit(0);
}
}
for (i = LEFT; i <= RIGHT; i++) {
wait(NULL);
}
exit(0);
}

进程分配

  1. 分块(分 3 块,第 1 块的任务最重)

  2. 交叉分配

示例:交叉分配

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
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include <sys/types.h>
#include <sys/wait.h>

#define LEFT 30000000
#define RIGHT 30000200
#define N 3

int main() {
int i, j, n, is_prime;
pid_t pid;

for (n = 0; n < N; n++) {
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) {
// Child process
for (i = LEFT + n; i <= RIGHT; i += N) {
is_prime = 1;
for (j = 2; j * j <= i; j++) {
if (i % j == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
printf("[%d] %d is a prime number.\n", n, i);
}
}
// Child process exits after checking
exit(0);
}
}
for (i = 0; i <= N; i++) {
wait(NULL);
}
exit(0);
}

4. exec 函数族

为什么 bash 创建的子进程是 hello ?

1
2
3
4
5
6
7
8
9
$ ps axf
16066 ? S 0:00 | \_ /init
16068 pts/6 Ss+ 0:00 | \_ -bash
17451 pts/6 S 0:00 | \_ ./src/hello
17452 pts/6 S 0:00 | \_ ./src/hello
17453 pts/6 S 0:00 | \_ ./src/hello
17454 pts/6 S 0:00 | \_ ./src/hello
17455 pts/6 S 0:00 | \_ ./src/hello
...

API

  • 执行一个文件
  • The exec() family of functions replaces the current process image with a new process image.
1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

// 环境变量:与 argv 的存储方式很像
extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg , ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

示例

  • 注意 fflush 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include <cstdlib>

/*
date +%s
*/
int main() {
puts("Begin!");

fflush(NULL); // !!!

execl("/bin/date", "date", "+%s", NULL);
perror("execl()");
exit(1);

puts("End!");

exit(0);
}
1
2
3
$ ./hello
Begin!
1770123896
1
2
$ ./hello > /tmp/out
1770123982

示例:few

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
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include <sys/types.h>
#include <sys/wait.h>

/*
date +%s
*/
int main() {
pid_t pid;

puts("Begin!");
fflush(NULL);

pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
}

if (pid == 0) {
execl("/bin/date", "date", "+%s", NULL);
perror("execl()");
exit(1);
}

wait(NULL);

puts("End!");

exit(0);
}
1
2
3
4
$ ./hello
Begin!
1770124593
End!

5. 用户权限及组权限

6. 观摩课:解释器文件

7. system()

8. 进程会计

9. 进程时间

10. 守护进程

11. 系统日志