当前位置: 首页 > article >正文

Linux高并发服务器开发 第十六天(execlp/execl 进程回收/孤儿进程/僵尸进程 wait/waitpid回收 进程间的通信)

目录

1.exec函数族

1.1execlp

1.2execl

1.3练习

2.进程回收                                                                          

3.孤儿进程

4.僵尸进程

5.wait回收

6.waitpid回收

7.进程间通信 IPC

7.1 进程间通信的方法

7.2管道pipe


1.exec函数族

- 工作原理:

    - 将当前进程的 .text、.data ... 替换为所要加载的程序的 .text、.data ... ,然后让进程从新的 .text 第一条指令开始执行。但 进程 ID 不变

- 工作特性:

    - exec函数族函数,一旦调用成功执行新程序,不会返回!只有调用失败才返回, 错误值 -1, errno
    - 通常使用时,我们只需在 execxxx() 函数后,调用 perror 和 exit, 无需 if 判断。

1.1execlp

- p: PATH 。 该函数在使用时,自动借助 环境变量 PATH,找寻可执行程序。
    - 可以用来调用,系统的程序。

// 该函数,通常用来执行系统程序:ls、data、cp、cat ... 命令

int execlp(const char *file, const char *arg, .../* (char  *) NULL */);


参数:
    参1:带加载的程序名字。需要配合 PATH 使用。
    参2:argv0 -- 可执行文件名
    参3:argv1
    参4:argv2
    ...:argvN
    哨兵:NULL,最后必须传入一个 NULL 指针来标识参数列表的结束
返回值:
    成功:不返回
    失败:-1, errrno

- 示例:

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;
}

1.2execl

- 直接指定要加载的程序绝对访问路径。可以是系统可以执行文件,也用户自定义可执行文件。

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;
}

1.3练习

- 编写程序创建子进程,子进程使用 exec族函数,获取当前系统中的进程详细信息,打印到一个文件中。
- 实现命令:ps  aux > out

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;
}

2.进程回收                                                                          

- fork后的子进程,其父进程有义务在子进程结束时,回收该子进程pcb。隔辈进程无回收关系。
- 进程终止:
    1. 关闭所有文件描述符
    2. 释放用户空间分配的内存。
    3. 进程的 pcb 残留在内核。保存进程结束的状态(正常:退出值。异常:终止其运行的信号编号)

3.孤儿进程

父进程,先于子进程终止。子进程沦为 “孤儿进程”。会被 init 进程领养。

使用命令:ps ajx 

ppid(父进程id)pid(进程id)gid(进程组id)sid(会话id)

4.僵尸进程

子进程终止,父进程未终止,但尚未对子进程回收。在此期间,子进程为 “僵尸进程”。

杀死进程命令:kill -9  进程id 。 只能杀死活跃的进程,对僵尸进程无效!!!

5.wait回收

- 只有 “父、子” 进程之间存在,回收关系。 爷孙进程、兄弟进程、叔侄进程... 不存回收关系!

#include <sys/wait.h>

pid_t wait(int *wstatus);
参:    
    传出参数。回收进程的状态。传 NULL,只回收进程的pcb,不获取退出状态。
返回值:
    成功:回收的进程pid
    失败:-1, errno

- 函数的作用:
    1. 阻塞等待子进程退出(终止)。
    2. 回收子进程残留在内核的 pcb。
    3. 获取子进程的退出状态(正常、异常)。—— 传出参数 :wstatus
- 回收子进程退出状态:
    - 正常退出:
        - 判断 WIFEXITED(status) 为真。
        - 进一步使用  WEXITSTATUS(status) 获取退出值。
    - 异常退出:
        - 判断 WIFSIGNALED(status) 为真。
        - 进一步 使用 WTERMSIG(status) 获取杀死子进程的信号的编号。
- 回收示例:

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));
        }
    }

6.waitpid回收

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   

示例:waitpid 回收 N 个子进程。

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;
}

总结:

==**一次wait、waitpid 调用,只能回收一个子进程!!!!**==

想回收 N 个子进程,需要将 wait、waitpid 调用 放于 循环中。

7.进程间通信 IPC

- 进程间通信的原理,借助 多个进程使用同一个 内核,借助内核,传递数据。

7.1 进程间通信的方法

1.  管道:最简单。
2.  信号:开销小。
3.  mmap映射:速度快,非血缘关系间。
4.  socket(本地套接字):稳定性好!

7.2管道pipe

- 实现原理:Linux 内核 使用环形队列机制,借助缓冲区(4k)实现。
- 特质:
    1. 本质:伪文件(实为内核缓冲区)
    2. 用于进程间通信,用两个文件描述符引用,一个读端,一个写端。
    3. 规定,数据从管道写端流入,从读端流出。
- 局限性:
    1. 自己写,不能自己读。
    2. 管道中的数据,读走没!不能反复读取!
    3. 半双工通信。(对讲机)
    4. 应用于血缘关系进程间。

使用的函数:

// 函数,调用成功,自动创建匿名管道,返回两个文件描述符,无需open,但需手动 close
int pipe(int pipefd[2]);
参:
    fd[0]:管道读端。r
    fd[1]:管道写端。w
返回值:
    成功:0
    失败:-1, errno

- 父子进程 管道通信 IPC。


http://www.kler.cn/a/539112.html

相关文章:

  • Docker容器访问外网:启动时的网络参数配置指南
  • 深入探究 Rust 测试:灵活控制测试的执行方式
  • 算法15(力扣347)——前k个高频元素
  • Qt通过FFmpeg打开RTSP并截图一帧作为背景
  • day44 QT核心机制
  • 51单片机俄罗斯方块计分函数
  • 【BUUCTF杂项题】刷新过的图片
  • [8-2-2] 队列实验_多设备玩游戏(红外改造)_重录
  • LLMs之DeepSeek r1:TinyZero(复现 DeepSeek R1 Zero 的核心功能)的简介、安装和使用方法、案例应用之详细攻略
  • SpringBoot 接口内容加密方案(RSA+AES+HMAC校验)认知
  • Python基础语法精要
  • 笔记:理解借贷相等的公式
  • Linux debugfs虚拟文件系统
  • COBOL语言的安全开发
  • 背包问题1
  • 交易一张股指期货需要多少钱?
  • Snipaste 截图软件下载与使用教程:附百度网盘地址
  • Leetcode 3449. Maximize the Minimum Game Score
  • 【MQ】Spring3 中 RabbitMQ 的使用与常见场景
  • 2025.2.9机器学习笔记:PINN文献阅读
  • excel拆分表格
  • Processing P5js姓氏数据可视化项目
  • Maven 与企业项目的集成
  • python--sqlite
  • K8s —基础指南(K8s - Basic Guide)
  • DeepSeek本地安装+集成VScode使用