目录
1.并发访问
# 进程控制
- fork 之后,父、子进程,共同争夺cpu ,执行先后顺序随机。
- **ps aux | grep 关键字**。 —— 搜索系统中包含关键字的进程。
- ./a.out 进程的父进程 bash
- 系统调用、库函数区别:
- 1. 访问内核数据结构。 2. 访问硬件资源。
- 系统调用: 以上两者二占其一。
- 库函数:以上两者均不占。
# 循环创建 n 个子进程

- 为了避免 子进程也fork产生 “孙进程”。 需要每次fork 后,将子进程 结束运行,不参与下一次fork。
2.cpu简单介绍
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int i = 0;
for (i = 0; i < 5; i++) {
if (fork() == 0)
break; // 循环期间,子进程不参与循环.
}
if (5 == i) { // 循环从 表达式 2 结束. --- 父进程
sleep(5);
printf("I'm parent \n");
} else {
sleep(i);
printf("I'm %dth child\n", i+1);
}
return 0;
}
```
3MMU简介
# fork后父子进程异同
“刚刚” fork 后:
父子相同:
- 全局变量、.data、.text、栈、堆、环境变量、用户ID、进程工作目录、宿主目录、信号处理方式...
父子不同:
- 进程ID、fork返回值、进程运行时间、父进程ID、闹钟(定时器)、未决信号集。
4.pcb成员
## 读时共享,写时复制
- fork后,对于父进程的用户空间的数据,系统采用 ==读时共享,写时复制== 原则。
- 重点常用:全局变量。
## fork后父子进程共享
1. 文件描述符(对应 打开的文件结构体)
2. mmap 创建的映射区。
## gdb调试父子进程
- 在 fork 函数调用之前:
- 跟踪父进程执行逻辑:set follow-fork-mode parent(默认)
- 跟踪子进程执行逻辑:set follow-fork-mode child
5.进程的状态
- 工作原理:
- 将当前进程的 .text、.data ... 替换为所要加载的程序的 .text、.data ... ,然后让进程从新的 .text 第一条指令开始执行。但 进程 ID 不变。
- 工作特性:
- exec函数族函数,一旦调用成功执行新程序,不会返回!只有调用失败才返回, 错误值 -1, errno
- 通常使用时,我们只需在 execxxx() 函数后,调用 perror 和 exit, 无需 if 判断。
6.环境变量
```c
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数:
参1:带加载的程序名字。需要配合 PATH 使用。
参2:argv0 -- 可执行文件名
参3:argv1
参4:argv2
...:argvN
哨兵:NULL
返回值:
成功:不返回
失败:-1, errrno
// 该函数,通常用来执行系统程序:ls、data、cp、cat ... 命令
```
7.fork创建子进程
```c
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid == 0) {
//execlp("ls", "-l", "-F", "-a", NULL); 这样传参错误!!!
execlp("ls", "ls", "-l", "-F", "-a", NULL);
perror("/bin/ls exec error");
exit(1);
} else if (pid > 0) {
sleep(1);
printf("parent\n");
}
return 0;
}
```
8fork子进程相关琐碎知识
## execl
- 直接指定要加载的程序绝对访问路径。可以是系统可以执行文件,也用户自定义可执行文件。
```c
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数:
参1:带加载的带有路径的程序名字。
参2:argv0 -- 可执行文件名
参3:argv1
参4:argv2
...:argvN
哨兵:NULL
示例:
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid == 0) {
// execl("/bin/ls", "ls", "-l", "-F", "-a", NULL);
execl("./while", "while", "aa", "bb", "cc", "dd", NULL);
perror("/bin/ls exec error");
exit(1);
} else if (pid > 0) {
sleep(1);
printf("parent\n");
}
return 0;
}
```
9.循环创建n个子进程
编写程序创建子进程,子进程使用 exec族函数,获取当前系统中的进程详细信息,打印到一个文件中。
- 实现命令:ps aux > out
```c
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid == 0) {
int fd = open("out", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd == -1)
sys_err("open error");
// 重定向
dup2(fd, STDOUT_FILENO);
execlp("ps", "ps", "a", "u", "x", NULL);
perror("execlp error");
exit(1);
} else if (pid > 0) {
sleep(1);
printf("I am parent\n");
}
return 0;
}
```
10.父子进程异同和共享
# 进程回收
- fork后的子进程,其父进程有义务在子进程结束时,回收该子进程pcb。隔辈进程无回收关系。
- 进程终止:
1. 关闭所有文件描述符
2. 释放用户空间分配的内存。
3. 进程的 pcb 残留在内核。保存进程结束的状态(正常:退出值。异常:终止其运行的信号编号)
11.函数fork的gdb调试
int main(int argc, char *argv[])
{
int status = 0;
pid_t wpid = 0;
pid_t pid = fork();
if (pid == -1)
sys_err("fork err");
else if (pid == 0) {
printf("I'm child pid = %d\n", getpid());
#if 1
execl("./abnor", "abnor", NULL);
perror("execl err");
exit(1);
#endif
sleep(1);
exit(73);
} else {
wpid = wait(&status); // 保存子进程退出的状态.
if (wpid == -1)
sys_err("wait err");
if (WIFEXITED(status)) { // 宏函数为真,说明子进程正常终止.
// 获取退出码
printf("I'm parent, pid = %d child, exit code = %d\n", wpid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { // 宏函数为真, 说明子进程被信号终止.
// 获取信号编号
printf("I'm parent, pid = %d child, killed by %d signal\n", wpid, WTERMSIG(status));
}
}
12.excelp函数
```c
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid:
>0: 通过pid指定 回收某一个子进程。
-1: 回收任意子进程。
0: 回收 与父进程属于同一个进程组的 子进程。
—— 子进程创建成功后,默认,会自动加入父进程进程组。
wstatus:(传出)回收子进程状态。 --- 类似于 wait() 的参数。
options:WNOHANG —— 指定回收方式为 “非阻塞”。
0 —— 指定回收方式为 “阻塞”。等同于 wait()
返回值:
> 0: 表成功回收的进程pid
0: 函数调用时参3指定了 WNOHANG,子进程没有结束。
-1:失败。 errno
```
13.excel函数
```c
int main(int argc, char *argv[])
{
int i = 0;
pid_t pid, wpid;
for(i = 0; i < 5; i++) {
pid = fork();
if (pid == 0)
break;
}
if (5 == i) { // 父进程
/*
while ((wpid = wait(NULL))!=-1) { // 阻塞等待子进程结束,回收
printf("wait child %d\n", wpid);
} */
/*
while ((wpid = waitpid(-1, NULL, 0))!=-1) { // 使用 waitpid 阻塞等待子进程结束,回收
printf("wait child %d\n", wpid);
} */
//while ((wpid = waitpid(-1, NULL, WNOHANG))!=-1) { // 使用 waitpid 非 阻塞回收子进程
while ((wpid = waitpid(0, NULL, WNOHANG))!=-1) { // 使用 waitpid 非 阻塞回收子进程
if (wpid > 0) {
printf("wait child %d\n", wpid); // 正常回收一个子进程
} else if (wpid == 0) {
sleep(1);
continue;
}
}
printf("catch All child finish\n");
} else {
sleep(i);
printf("%dth child, pid = %d\n", i+1, getpid());
}
return 0;
}
14.其他exec函数
==**一次wait、waitpid 调用,只能回收一个子进程!!!!**==
想回收 N 个子进程,需要将 wait、waitpid 调用 放于 循环中。
# 进程间通信 IPC
- 进程间通信的原理,借助 多个进程使用同一个 内核,借助内核,传递数据。
## 进程间通信的方法
1. 管道:最简单。
2. 信号:开销小。
3. mmap映射:速度快,非血缘关系间。
4. socket(本地套接字):稳定性好!
15.回收子进程概念
# 进程回收
- fork后的子进程,其父进程有义务在子进程结束时,回收该子进程pcb。隔辈进程无回收关系。
- 进程终止:
1. 关闭所有文件描述符
2. 释放用户空间分配的内存。
3. 进程的 pcb 残留在内核。保存进程结束的状态(正常:退出值。异常:终止其运行的信号编号)
16.孤儿进程
## 孤儿进程
父进程,先于子进程终止。子进程沦为 “孤儿进程”。会被 init 进程领养。
使用命令:ps ajx 查看 ppid(父进程id)pid(进程id)gid(进程组id)sid(会话id)
17.僵尸进程
## 僵尸进程
子进程终止,父进程未终止,但尚未对子进程回收。在此期间,子进程为 “僵尸进程”。
杀死进程命令:kill -9 进程id 。 只能杀死活跃的进程,对僵尸进程无效!!!
18.wait回收子进程
## wait回收
- 只有 “父、子” 进程之间存在,回收关系。 爷孙进程、兄弟进程、叔侄进程... 不存回收关系!
```c
#include <sys/wait.h>
pid_t wait(int *wstatus);
参:
传出参数。回收进程的状态。传 NULL,只回收进程的pcb,不获取退出状态。
返回值:
成功:回收的进程pid
失败:-1, errno
```
19.wait回收子进程-获取退出状态
函数的作用:
1. 阻塞等待子进程退出(终止)。
2. 回收子进程残留在内核的 pcb。
3. 获取子进程的退出状态(正常、异常)。—— 传出参数 :wstatus
- 回收子进程退出状态:
- 正常退出:
- 判断 WIFEXITED(status) 为真。
- 进一步使用 WEXITSTATUS(status) 获取退出值。
- 异常退出:
- 判断 WIFSIGNALED(status) 为真。
- 进一步 使用 WTERMSIG(status) 获取杀死子进程的信号的编号。
- 回收示例:
20.waitpid函数
## 管道pipe
- 实现原理:Linux 内核 使用环形队列机制,借助缓冲区(4k)实现。
- 特质:
1. 本质:伪文件(实为内核缓冲区)
2. 用于进程间通信,右两个文件描述符引用,一个读端,一个写端。
3. 规定,数据从管道写端流入,从读端流出。
- 局限性:
1. 自己写,不能自己读。
2. 管道中的数据,读走没!不能反复读取!
3. 半双工通信。(对讲机)
4. 应用于血缘关系进程间。
21.waitpid回收N个子进程
```c
// 函数,调用成功,自动创建匿名管道,返回两个文件描述符,无需open,但需手动 close。
int pipe(int pipefd[2]);
参:
fd[0]:管道读端。r
fd[1]:管道写端。w
返回值:
成功:0
失败:-1, errno
```
- 父子进程 管道通信 IPC。