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.
- int mkfifo(const char *pathname, mode_t mode);
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
- how:
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);
};
函数捕捉特性
- 进程正常运行时,默认PCB中有一个信号屏蔽字 mask,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所有的屏蔽信号不由 mask 决定。而是 sa_mask 来制定。调用完处理函数,再恢复为 mask。
- xxx 信号捕捉函数执行期间,xxx 信号自动被屏蔽。
- 阻塞的常规信号不支持排队,产生多次只记录一次。(后 32 个实时信号支持排队)
5.4.7 信号例子
#include <iostream>
#include <wait.h>
#include <unistd.h>
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;
}