LInux进程间通信

1 进程间通信

  • 进程间通信(IPC,InterProcess Communication)
  • 通信方式:
    • 管道(最简单)-- pipe
    • 信号(开销最小)–
    • 共享内存映射区(无需进程间有血缘关系)-- mmap
    • 本地套接字(最稳定)-- socket
    • 文件(已经过时),不会阻塞。

2 管道

2.1 基本概念

  • 管道是一种最基本的 IPC 机制,作用于血缘关系的进程之间,完成数据传递。调用 pipe 系统函数即创建一个管道。有如下特质:
    • 其本质是一个伪文件(实际是内缓冲区);
    • 由两个文件描述符引用,一个表示读,一个表示写;
    • 规定数据从管道的写端流入,从读端流出。
  • 实现原理:管道实现为内核使用环形队列机制,借助内核缓冲区(大小是 4k)实现。
  • 管道的局限性:
    • 数据不能自己写,自己读;
    • 数据不能反复读取。一旦读取,管道中不再存在这个数据;
    • 采用半双工通信方式,数据只能单方向上流动;
    • 只能在有血缘关系的进程间通信。(因为父子进程共享文件描述符)

2.2 pipe 函数

  • 函数说明:
    • int pipe(int fd[2]);
    • 参数:
      • fd[0],读端;fd[1],写端。
    • 返回值:
      • 成功,0,表示创建成功并打开;失败,-1.

2.3 管道读写行为

  • 读管道行为:
    • 管道有数据,read() 返回实际读到的字节数
    • 管道无数据:
      • 无写端打开,read() 返回 0;
      • 有写端打开,read() 阻塞。
  • 写管道行为:
    • 无读端。管道读端已经关闭,进程异常终止(SIGPIPE)
    • 有读端:
      • 管道已满,阻塞等待;
      • 管道未满,返回写入的字节数。

3 FIFO(有名管道)

3.1 mkfifo 函数

  • 函数说明
    • int mkfifo(const char *pathname, mode_t mode);
      • pathname: 创建的管道名称
      • mode: 0644
    • 返回值:
      • 创建成功,0;创建失败,-1.

4 存储映射I/o – mmap

  • 概念:存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相互映射。于是当从缓冲区中数据,就相当于读文件中的相应内容。

4.1 mmap 函数

  • 函数说明:
    • void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
    • 参数说明:
      • addr: 指定映射区的首地址。通常传NULL,表示系统自动分配
      • length: 共享内存映射区的大小。<=文件大小。
      • pro: 共享内存映射区的读写属性
      • flags: 标注共享内存的共享属性。是否是共享的。MAP_SHARED、MAP_REIVATE。
      • fd: 用于创建共享内映射区的那个文件的文件描述符
      • offset: 偏移位置。必须是4k的整数倍。
    • 返回值:成功,返回内存映射区的首地址;失败,返回MAP_FAILED((void *)-1,errno)

4.2 mmap 函数的保调用方式:

  • fd = opoen(“filename”, O_REWR);
  • mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

4.3 父子进程使用 mmap 进程间通信

  • 父进程先创建映射区。open(O_RDWR), mmap(MAP_SHARED);
  • fork() 创建子进程;
  • 一个进程读,另一个写。

4.4 无血缘关系的进程间 mmap 通信

  • 两个进程打开同一个文件,创建映射区。
  • 指定 flags 为 MAP_SHARED.
  • 一个进程写,另一个进程读。

4.5 进程间通信方式 mmap 和 fifo 之间的区别

  • mmap:数据可以重复读。
  • fifo:数据只能读取一次。

5 信号

5.1 信号的概念

信号信息的载体,Linux/unix 环境下,古老、经典通信手段。

  • 简单
  • 不能携带大量信息
  • 满足某种特设条件

5.2 信号机制

  • 信号是软件层面的中断。一旦信号产生,无论程序执行到什么位置,应该立即停止执行,处理信号,处理结束之后再继续执行后续指令。
  • 所有信号的产生和处理全部是内核完成的。

5.3 与信号相关的事件和状态

  • 产生信号:
    • 按键产生,如Ctrl + c
    • 系统调用产生, kill,raise,abort
    • 软件条件产生, alarm
    • 硬件异常产生,非访问内存,除0,内存对齐错误
    • 命令产生,kill 命令
  • 递达
    • 递送并且到达进程
  • 未决
    • 产生和递送之的状态。主要由于阻塞(屏蔽)导致该状态。
  • 信号处理方式
    • 执行默认方式
    • 忽略
    • 捕捉(调用用户处理程序)

5.4 信号四要素 和 常规信号一览

5.4.1 信号四要素

  • 编号
  • 名称
  • 事件
  • 默认处理动作

5.4.2 常规信号

  • SIGHUP:当用户退出 shell 时,由该 shell 启动的所有进程将收到这个信号,默认动作为终止进程。
  • SIGINT:当用户按下了 Ctrl + c 组合键时,用户终端向正在运行中的由该终端启动的程序发送次信号。默动作是终止进程。
  • SIGQUIT:
  • SIGILL:
  • SIGTRAP:
  • SIGABRT:

5.4.3 信号机操作函数

  • sigset_t set;
  • sigemptyset(sigset_t *set); 清空set
  • sigfillset(sigset_t *set); 全部置1
  • sigaddset(sigset_t *set, int signum); 讲一个信号添加到集合中
  • sigdelset(sigset_t *set, int signum); 讲一个信号从集合中移除
  • sigismember(const sigset_t *set, int signum); 判断一个信号是否在集合中。在,返回1;不在,返回0

5.4.4 设置和解除信号屏蔽字

  • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 参数说明:
    • how:
      • SIG_BLOCK:设置阻塞
      • SIG_UNBLOCK:取消阻塞
      • IBG_SETMASK:用自定义 set 替换 mask
    • set: 自定义 set
    • oldset: 旧的 mask

5.4.5 查看未决信号集

  • int sigpengding(sigset_t *set);

5.4.6 函数捕捉

signal 和 sigaction 的作用就是注册一个捕捉函数

signal 函数
  • sighandler_t signal(int signum, sighandler_t handler);
  • sighandler_t void (*sighandler_t)(int)
sigaction 函数
  • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • 参数说明:
  • signum: 捕获的信号num
  • act:
    struct sigaction {
       void     (*sa_handler)(int);   // 函数指针函数
       void     (*sa_sigaction)(int, siginfo_t *, void *);
       sigset_t   sa_mask;
       int        sa_flags;
       void     (*sa_restorer)(void);
    };
函数捕捉特性
  1. 进程正常运行时,默认PCB中有一个信号屏蔽字 mask,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所有的屏蔽信号不由 mask 决定。而是 sa_mask 来制定。调用完处理函数,再恢复为 mask。
  2. xxx 信号捕捉函数执行期间,xxx 信号自动被屏蔽。
  3. 阻塞的常规信号不支持排队,产生多次只记录一次。(后 32 个实时信号支持排队)

5.4.7 信号例子

#include 
#include 
#include 

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

void catch_child(int signo) {
    pid_t wpid;
    int status;

    while ((wpid = waitpid(-1, &status, 0) != -1)) {
        if (WIFEXITED(status)) {
            std::cout << "catch child SIGCHLD, pid = " << wpid << ". ret = " << WEXITSTATUS(status) << std::endl;
        }
    }
    return;
}

int main(int argc, char * argv[]) {
    pid_t pid;
    int i;
    for (i = 0; i < 15; i++) {
        if ((pid = fork()) == 0) {              // 创建多个子进程
            break;
        }
    }

    if (15 == i) {
        struct sigaction act;
        act.sa_handler = catch_child;           // 设置回调函数
        act.sa_flags = 0;                       // 设置默认值,本信号自动屏蔽
        sigemptyset(&act.sa_mask);              // 设置捕捉函数执行期间屏蔽字
        sigaction(SIGCHLD, &act, NULL);         // 注册捕捉函数

        std::cout << "This is parent, pid = " << getpid() << std::endl;
        while (1);
    } else {
        std::cout << "This is child, pid = " << getpid() << std::endl;
        return i;
    }
    return 0;
}

你可能感兴趣的:(Linux)