📝 我的笔记

还没有笔记

选中页面文字后点击「高亮」按钮添加

1 进程

📜 原文
📖 逐步解释
∑ 公式拆解
💡 数值示例
⚠️ 易错点
📝 总结
🎯 存在目的
🧠 直觉心智模型
💭 直观想象

1进程

COMS W3157

Dr. Borowski

2什么是进程?

3退出函数

在C程序中,main函数中的return ;等价于在任何地方调用exit(status)

_exit(int)_Exit(int)系统调用

4退出函数

exit (int)是一个C标准库函数

```

#include "apue.h"

static void my_exit1(void);

static void my_exit2(void);

int

main(void)

{

if (atexit(my_exit2) != 0)

err_sys("can't register my_exit2");

if (atexit(my_exit1) != 0)

err_sys("can't register my_exit1");

if (atexit(my_exit1) != 0)

err_sys("can't register my_exit1");

printf("main is done\n");

return(0);

}

static void

my_exit1(void)

{

printf("first exit handler\n");

}

static void

my_exit2(void)

{

printf("second exit handler\n");

```

5函数按照它们注册的相反顺序执行!

```

}

```

图 7.3 退出处理程序示例

图 7.2 C程序如何启动和终止

6命令行和环境变量

$ ./a.out arg1 arg2 34

int main(int argc, char argv[], char envp[])

envp

argv

envp提供了程序环境

它与env的值相同。

尝试在shell中export

7PID

每个进程都有一个唯一的进程ID (PID)。

PID内核分配。

已终止进程ID可以重用。

可以使用ps命令列出当前运行的进程

```

$ ps

21271 pts/0 00:00:00 bash

21308 pts/0 00:00:00 ps

```

8`ps -aux`

/sbin/init协调其余的启动进程并为用户配置环境。当init命令启动时,它成为系统上所有自动启动进程父进程祖父进程

| 用户 | PID | %CPU | %MEM | VSZ | RSS TTY | | STAT | START | TIME COMMAND | |

| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |

| root | 1 | 0.0 | 0.0 | 176988 | 3264 | ? | Ss | Feb26 | 0:03 | /sbin/init |

| root | 2 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [kthreadd] |

| root | 3 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:09 [ksoftirqd/0] |

| root | 5 | 0.0 | 0.0 | 0 | 0 | ? | S< | Feb26 | | 0:00 [kworker/0:0H] |

| root | 7 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 1:13 [rcu_sched] |

| root | 8 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [rcu_bh] |

| root | 9 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [migration/0] |

| user | 14965 | 0.0 | 0.1 | 1039740 | 15852 | ? | Sl | Feb29 | | 0:05 texstudio --start-always |

| user | 15626 | 0.0 | 0.0 | 128484 | 5144 | ? | S | Feb29 | | 0:00 orage |

| root | 17886 | 0.0 | 0.0 | 86644 | 3728 | ? | Ss | 07:35 | | 0:00 /usr/sbin/cupsd -f |

| user | 18016 | 4.1 | 7.8 | 1769348 | 639880 | ? | Sl | 13:19 | | 22:35 /usr/bin/iceweasel |

| user | 18246 | 0.0 | 0.0 | 5964 | 396 | ? | S | 13:23 | 0:00 cat | |

a = 显示所有用户的进程

u = 显示进程的用户/所有者

x = 也显示未连接到终端的进程

9我的PID是什么?

pid_t getpid(void);

返回调用(当前)进程进程ID

pid_t getppid(void);

返回调用进程父进程进程ID

10创建更多进程

pid_t fork();

11子进程继承的属性

12父进程与子进程之间的差异

13文件

14描述符

15事实上,fork的一个特性是

父进程中所有打开的文件描述符都在子进程中复制。

图 8.2 fork父进程子进程之间共享打开文件

16`fork()`的用法

```

#include

#include

#include

int main() {

pid_t pid;

if ((pid = fork()) < 0) {

perror("fork");

return -1;

} else if (pid == 0) {

/ child /

printf("I am the child\n");

return 0;

}

/ parent /

printf("I am the parent of %d\n", pid);

return 0;

}

```

17`fork()`的用法

18输出:

I am the child

I am the parent of 1200

```

Use of fork()

```

如果注释掉这个return语句会发生什么?

```

#include

#include

#include

int main() {

pid_t pid;

if ((pid = fork()) < 0) {

perror("fork");

return -1;

} else if (pid == 0) {

/ child /

printf("I am the child\n");

}

/ parent /

printf("I am the parent of %d\n", pid);

return 0;

}

```

19`fork()`的用法 - 修改

20输出:

I am the child

I am the parent of 0

I am the parent of 1200

21另一个`fork()`示例

```

#include

#include

#include

int main() {

pid_t pid;

int w = 100;

if ((pid = fork()) < 0) {

perror("fork");

return -1;

} else if (pid == 0) {

/ child /

w = 200;

} else {

/ parent /

w = -100;

}

printf("I am %d, child of %d, w = %d\n",

getpid(), getppid(), w);

return 0;

}

```

22另一个`fork()`示例

```

#include

#include

#include

I am 22982, child of 13673, w = -100

int main() {

pid_t pid;

int w = 100;

if ((pid = fork()) < 0) {

perror("fork");

return -1;

} else if (pid == 0) {

/ child /

w = 200;

} else {

/ parent /

w = -100;

}

printf("I am %d, child of %d, w = %d\n",

getpid(), getppid(), w);

return 0;

}

```

23`fork()`与文件

```

#include "apue.h"

int globvar = 6; / external variable in initialized data /

char buf[] = "a write to stdout\n";

int

main(void)

{

int var; / automatic variable on the stack /

pid_t pid;

var = 88;

if (write(STDOUT_FILENO, buf, sizeof(buf)-l) != sizeof(buf)-l)

err_sys("write error");

printf("before fork\n"); / we don't flush stdout /

if ((pid = fork()) < 0) {

err_sys("fork error");

} else if (pid == 0) { / child /

globvar++; / modify variables /

var++;

} else {

sleep(2); / parent /

}

printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,

var);

exit(0);

}

```

图 8.1 fork函数示例

24图 8.1 中代码的输出

```

$ ./a.out

a write to stdout

before fork

pid = 430, glob = 7, var = 89 child's variables were changed

pid = 429, glob = 6, var = 88 parent's copy was not changed

$ ./a.out > temp.out

$ cat temp.out

a write to stdout

before fork

pid = 432, glob = 7, var = 89

before fork

pid = 431, glob = 6, var = 88

```

在第二种情况下,fork之前的printf被调用一次,但当fork被调用时,该行仍保留在缓冲区中。当父进程的数据空间复制到子进程时,此缓冲区也会复制到子进程。现在,父进程子进程都有一个包含此行的标准I/O缓冲区

25等待

26等待子进程

pid_t wait(int *wstatus);

| | 描述 |

| :--- | :--- |

| WIFEXITED (status) | 如果status是为正常终止的子进程返回的,则为True。在这种情况下,我们可以执行
WEXITSTATUS (status)
来获取子进程传递给exit_exit_Exit的参数的低8位。 |

| WIFSIGNALED (status) | 如果status是为异常终止的子进程返回的,由它未捕获到的信号引起。在这种情况下,我们可以执行
WTERMSIG(status)
来获取导致终止的信号编号。
此外,一些实现(但不是Single UNIX Specification)定义了
WCOREDUMP (status)
如果终止进程生成了核心文件,则返回true。 |

| WIFSTOPPED (status) | 如果status是为当前已停止的子进程返回的,则为True。在这种情况下,我们可以执行
WSTOPSIG(status)
来获取导致子进程停止的信号编号。 |

| WIFCONTINUED (status) | 如果status是为作业控制停止后已继续的子进程返回的(XSI选项;仅限waitpid)。 |

图 8.4 检查waitwaitpid返回的终止状态的

27等待特定PID

pid_t waitpid(pid_t pid, int *status, int options);

如果pid

wait(&status)等价于waitpid(-1, &status, 0)

waitpid只等待已终止的子进程,除非options是:

WNOHANG 如果没有子进程退出,则立即返回。

WUNTRACED 如果子进程已停止,也返回。

WCONTINUED 如果已停止的子进程已通过发送SIGCONT恢复,也返回。

28僵尸进程

29孤儿进程

30exec()函数族

exec()函数族用新的进程映像替换当前进程映像。当进程调用exec()函数时,该进程保留其进程ID及其与父进程的关系。然而,其内存地址空间被新可执行文件内存地址空间取代,新程序开始执行其main()函数。

1 - 接收参数列表

```

execl(), execlp(), execle()

```

v - 接收参数向量

```

execv(), execvp(), execvpe()

```

e - 通过envp参数接收环境

```

execle(), execvpe()

```

p - 如果文件名不包含/,则在PATH环境变量中搜索可执行文件

```

execlp(), execvp(), execvpe()

```

31exec()函数族

```

#include

int execl(const char pathname, const char arg0, ... /*

(char )0 / );

int execv(const char pathname, char const argv []);

int execle(const char pathname, const char arg0, ... /*

(char )0, char const envp[] */ );

int execve(const char pathname, char const argv[], char

*const envp []);

int execlp(const char filename, const char arg0, ... /*

(char )0 / );

int execvp(const char filename, char const argv []);

int fexecve(int fd, char const argv[], char const

envp[]);

```

32exec()函数族

图 8.15 七个exec函数的关系

33fork练习

34使用fork图来预测执行

```

void function1() {

pid_t pid;

if ((pid = fork()) == 0) {

sleep(1);

printf("C1 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(2);

printf("C2 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(3);

printf("C3 ");

fflush(stdout);

}

// Small hack to wait for all

// descendent processes

while (wait(NULL) != -1) { }

}

```

输出是什么?

它具有确定性吗?

此函数执行需要多长时间?

提示:画一个图!

```

void function1() {

pid_t pid;

if ((pid = fork()) == 0) {

sleep(1);

printf("C1 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(2);

printf("C2 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(3);

printf("C3 ");

fflush(stdout);

}

// Small hack to wait for all

// descendent processes

while (wait(NULL) != -1) { }

}

```

输出:C1 C2 C3 C2 C3 C3 C3

```

void function1() {

pid_t pid;

if ((pid = fork()) == 0) {

sleep(1);

printf("C1 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(2);

printf("C2 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(3);

printf("C3 ");

fflush(stdout);

}

// Small hack to wait for all

// descendent processes

while (wait(NULL) != -1) { }

}

```

多条路径加起来需要 3 秒,因此这是非确定性的。

另一个输出是:C1 C2 C2 C3 C3 C3 C3

```

void function1() {

pid_t pid;

if ((pid = fork()) == 0) {

sleep(1);

printf("C1 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(2);

printf("C2 ");

fflush(stdout);

}

if ((pid = fork()) == 0) {

sleep(3);

printf("C3 ");

fflush(stdout);

}

// Small hack to wait for all

// descendent processes

while (wait(NULL) != -1) { }

}

```

总执行时间:最长路径 6 秒

```

void function2() {

if (fork() || fork()) { What is the output?

fork();

} Is it deterministic?

printf("1 ");

fflush(stdout); How long does this function take to

while (wait(NULL) != -1) { } execute?

}

```

```

void function2() {

if (fork() || fork()) {

fork();

}

printf("1 ");

fflush(stdout);

while (wait(NULL) != -1) { }

}

```

输出是什么? $\mathbf{11111}$

它具有确定性吗? 是的,因为只打印 1

此函数执行需要多长时间? 几乎是瞬间的。

```

void function3() {

if (fork() && fork()) { What is the output?

fork();

} Is it deterministic?

printf("2 ");

fflush(stdout); How long does this function take to

while (wait(NULL) != -1) { } execute?

}

```

```

void function3() {

if (fork() && fork()) {

fork();

}

printf("2 ");

fflush(stdout);

while (wait(NULL) != -1) { }

}

```

输出是什么? 2222

它具有确定性吗? 是的,因为只打印 $\mathbf{2s}$

此函数执行需要多长时间? 几乎是瞬间的。

```

void function4() {

pid_t pid[2] = {fork(), fork()};

for (int i = 0 ; i < 2; i++) {

printf("%d", pid[i] == 0); Is it deterministic?

fflush(stdout);

}

while (wait(NULL) != -1) {};

}

What is the output?

How long does this function take to execute?

```

```

void function4() {

pid_t pid[2] = {fork(), fork()};

for (int i = 0 ; i < 2; i++) {

printf("%d", pid[i] == 0);

fflush(stdout);

}

while (wait(NULL) != -1) {};

}

```

输出是什么? $\mathbf{8}$位数字 - $\mathbf{4}$个零,$\mathbf{4}$个一,来自上面显示的成对。数字可以是任意顺序,甚至可以是交错的对。

它具有确定性吗? 否。

此函数执行需要多长时间? 几乎是瞬间的。