进程间通信机制:
unix继承:管道、信号
system V IPC对象:共享内存、消息队列、信号灯集
套接字
传统的UNIX进程间通信方式包括无名管道、有名管道以及信号。
System V进程间通信(IPC)包括System V消息队列、System V信号量以及System V共享内存。
现在在Linux中使用较多的进程间通信方式主要有以下几种:
1)无名管道 及 有名管道
2)信号
3)共享内存
4)消息队列
5)信号灯集
6)套接字。这是一种使用更广泛的进程间通信机制,它可用于网络中不同主机之间的进程间通信,应用非常广泛。本篇文章主要介绍前五种进程通信方式,套接字将在之后的网络编程中讲到。
管道分为 无名管道 和 有名管道,区别在于创建的管道能否在文件系统中是否可见。
特点:
创建之后在文件系统中不可见
以半双工的方式进行通信
拥有固定的读端和写端
只能用于具有亲缘关系的进程间通信
①无名管道的创建 pipe()
#include
int pipe(int pipefd[2]; 参数: pipefd: 存放无名管道读端 和 写端的数组首地址 pipefd[0]: 读端 pipefd[1]: 写端 返回值: 成功返回 0, 失败返回 -1; 练习: 在一个进程中创建一个子进程,子进程从键盘获取数据,父进程打印输出
② 无名管道的读写特性
读特性:
写端存在:
管道有数据: 返回读到的字节数
管道无数据: 阻塞
写端不存在:
管道有数据: 返回读到的字节数
管道无数据: 返回 0
写特性:
读端存在:
管道有空间: 返回写入的字节数
管道无空间: 阻塞,直到有空间为止
读端不存在:
- 无论管道有无空间,管道破裂
练习: ① 计算无名管道空间大小
②验证管道破裂
① 有名管道的创建 mkfifo()
#include
#include
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname: 创建管道文件的文件名
mode: 创建管道文件的权限
返回值:
成功返回 0, 失败返回 -1;
练习: 创建一个有名管道,一个进程向管道中输入数据,另一个进程输出数据
信号:是中断在软件层次上的一种模拟
信号的处理方式:
默认处理
忽略
捕获信号
kill -l 查看当前系统中的所有信号
kill -信号编号 -进程号 向指定进程发送对应编号的信号
eg.
kill -9 1123 向进程 1123发送信号9
kill -9 -1123 向进程组 1123发送信号9
kill -9 -1 向除了init进程以外的其他所有进程发送信号9
kill() / raise()
#include
#include
int kill(pid_t pid, int sig);
参数:
pid: 指定进程号
sig: 指定信号
返回值:
成功返回 0,失败返回 -1;
----------------------------------------------------------------
#include
int raise(int sig);
//发送信号给本进程
参数:
sig: 指定信号
返回值:
成功返回 0,失败返回 非零
定时器时间到,当前进程会接收到编号为14的信号 – SIGALRM
alarm() / pause()
#include
unsigned int alarm(unsigned int seconds);
参数:
seconds: 表示定时秒数
返回值:
成功返回 0,或者上一个定时器定时剩余时间
-------------------------------------------
#include
int pause(void);
功能:
阻塞当前进程,等待定时器结束
#include
typedef void (*sighandler_t)(int);
//void func1();
sighandler_t sinal(int signum, sinhandler_t handler);
eg.
sinal(SIGALRM, func1);
参数:
signum: 指定信号
handler: 信号处理函数
SIG_IGN: 选择以忽略方式处理指定信号
SIG_DFL: 选择以默认方式处理指定信号
返回值:
练习:创建一个子进程,子进程结束时,父进程提示子进程退出信息
ipcs: 查看IPC对象
所有的IPC对象只能通过用户显式地进行删除 ipcrm命令
共享内存是进程间通信效率最高的一种通信机制。
key 值的获取
#include
#include key_t ftok(const char *pathname, int proj_id); 参数: pathname: 任意路径 proj_id: 任意字符 返回值: 成功返回 key值,失败返回 -1;
使用共享内存流程:
1.创建或者打开共享内存 – shmget()
#include
#include int shmget(key_t key, size_t size, int shmflg); 参数: key: 通过ftok得到或者使用IPC_PRIVATE(创建私有共享内存) size: 共享内存的大小 shmflg: 创建共享内存权限,一般填0664 | IPC_CREAT 返回值: 成功返回 共享内存ID,失败返回 -1; 2.映射共享内存 – shmat()
#include
#include void *shmat(int shmid, const void *shmaddr, int shmflg); 参数: shmid: 共享内存id shmaddr: 映射地址,填NULL表示系统自动分配 shmflg: SHM_RDONLY 只读 0 读写 返回值: 成功返回映射地址, 失败返回 (void *)-1; 3.访问共享内存
4.取消映射 – shmdt()
#include
#include int shmdt(const void *shmaddr); 参数: shmaddr: 映射地址 返回值: 成功返回 0,失败返回 -1; 5.操作共享内存(查/删/改) – shmctl()
#include
#include int shmctl(int shmid, int cmd, struct shmid_ds *buf); 参数: shmid: 共享内存id号 cmd: 操作共享内存的命令 IPC_STAT: 获取共享内存的信息,需要用到第三个参数 IPC_SET: 设置共享内存信息 IPC_RMID: 删除共享内存,第三个参数填NULL; 返回值: 成功返回 0,失败返回 -1;
练习: 一个进程输入数据,一个进程打印输出数据
消息队列的使用流程:
1.创建、打开消息队列 – msgget()
#include
#include #include int msgget(key_t key, int msgflg); 参数: key: 通过ftok得到的key值或者使用IPC_PRIVATE创建私有消息队列 msgflg: 创建消息队列的权限,一般填 0664|IPC_CREAT 返回值: 成功返回消息队列id,失败返回 -1; eg. int msgid = msgget(IPC_PRIVATE, 0664); 2.发送消息 – msgsnd()
#include
#include #include int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 参数: msgid: 消息队列id msgp: 发送消息相关的结构体的首地址 msgsz: 消息结构体中正文内容的大小 msgflg: 发送消息方式 0 以阻塞方式发送消息 IPC_NOWAIT 以非阻塞方式发送消息 返回值: 成功返回 0, 失败返回 -1; 用法: #define LEN (sizeof(MSG)-sizeof(long)) /* 发送消息相关结构体 */ typedef struct msgbuf { long mtype; // type > 0 char mtext[64]; // message data }MSG; MSG msg; char buf[64] = {0};//缓冲区 fgets(buf,64,stdin); msg.mtype = 100; strcpy(msg.mtext, buf); msgsnd(msgid, &msg, LEN, 0); 3.接收消息 – msgrcv()
#include
#include #include ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 参数: msgid: 消息队列id msgp: 接收消息相关的结构体的首地址 msgsz: 消息结构体中正文内容的大小 msgtyp: 指定接收类型 msgflg: 接收消息方式 0 以阻塞方式接收消息 IPC_NOWAIT 以非阻塞方式接收消息 返回值: 成功返回 0, 失败返回 -1; 4.操作消息队列 – msgctl()
#include
#include #include int msgctl(int msqid, int cmd, struct msqid_ds *buf); 参数: msgid: 消息队列id号 cmd: 操作消息队列的命令 IPC_STAT: 获取消息队列的信息,需要用到第三个参数 IPC_SET: 设置消息队列信息 IPC_RMID: 删除消息队列,第三个参数填NULL; 返回值: 成功返回 0,失败返回 -1;
信号量使用流程:
1.创建信号灯集 – semget()
#include
#include #include int semget(key_t key, int nsems, int semflg); 参数: key: 产生信号灯id需要使用的键值,ftok()或者IPC_PRIVATE获取 nsems: 信号灯集中包含信号的个数 semflg: 标志位,信号灯的访问权限。如,创建需要添加 IPC_CREAT 返回值: 成功返回信号灯集ID号,错误返回 -1; 2.操作信号灯(查/改/删) – semctl()
#include
#include #include int semctl(int semid, int semnum, int cmd, ...); 参数: semid: 信号灯集id semnum: 要控制的信号灯编号(编号从零开始) cmd: 控制命令 IPC_RMID 删除信号灯集合 IPC_STAT 查看信息 IPC_SET 设置初始化属性 GETVAL 获取信号灯的值 参数4: 对于cmd参数的补充 cmd为GETVAL时,返回值为信号量的当前值 cmd为IPC_RMID时,为空 cmd为IPC_SET时,传入共用体(需要自行定义) union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; 返回值: 失败返回 -1; 3.实现P、V操作 – semop()
#include
#include #include int semop(int semid, struct sembuf *sops, size_t nsops); 参数: semid: 信号灯集id sops: 传递结构体数组的首地址,或者一个结构体的地址 /* 该结构体已在头文件中定义 */ struct sembuf { unsigned short sem_num; /* 要操作的信号灯编号 */ short sem_op; /* 0 等待信号灯的值变为0 1 V操作 -1 P操作 */ short sem_flg; /* 通常为0,表示阻塞等待 IPC_NOWAIT IPC_UNDO */ } nsops: 表示需要控制多少个信号灯 返回值: 成功返回0,失败或其他返回 -1;
pipe: 具有亲缘关系的进程间,半双工,数据在内存
fifo: 可用于任意进程间,双工,有文件名,数据在内存
signal: 唯一的异步通信方式
msg: 常用于cs模式中,按消息类型访问,可有优先级
shm: 效率最高(直接访问内存),需要同步、互斥机制
sem: 配合共享内存使用,用以实现同步和互斥