C++学习之LINUX进程

目录

1.并发访问

2.cpu简单介绍

3MMU简介

4.pcb成员

5.进程的状态

6.环境变量

7.fork创建子进程

8fork子进程相关琐碎知识

9.循环创建n个子进程

10.父子进程异同和共享

11.函数fork的gdb调试

12.excelp函数

13.excel函数

14.其他exec函数

15.回收子进程概念

16.孤儿进程

17.僵尸进程

18.wait回收子进程

19.wait回收子进程-获取退出状态

20.waitpid函数

21.waitpid回收N个子进程


1.并发访问

# 进程控制

- fork 之后,父、子进程,共同争夺cpu ,执行先后顺序随机。
- **ps aux | grep 关键字**。  —— 搜索系统中包含关键字的进程。

- ./a.out 进程的父进程 bash 
- 系统调用、库函数区别:
    - 1. 访问内核数据结构。  2. 访问硬件资源。
    - 系统调用:  以上两者二占其一。
    - 库函数:以上两者均不占。

# 循环创建 n 个子进程

![1583979574129](课堂笔记07.assets/1583979574129.png)

- 为了避免 子进程也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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值