进程间通信学习笔记
早期进程间通信
一、无名管道
1、特点:
- 只能用于具有亲缘关系的进程间通信
- 单工通信模式,具有固定的读端和写端
- 无名管道创建时会返回两个文件描述符,分别用于读写管道
2、相关函数:
#include <unistd.h>
int pipe(int pfd[2]);
- 成功返回0,失败返回EOF
- pfd包含两个元素的整形数组,用来保存文件描述符
- pfd[0]用于读管道,pfd[1]用于写管道
3、读管道
写端存在
- 有数据,read返回实际读取的字节数
- 无数据,进程读阻塞
写端不存在
- 有数据,read返回实际读取的字节数
- 无数据,read返回0
4、写管道
读端存在
- 有空间,write返回实际写入的字节数
- 无空间,进程写阻塞
读端不存在
- 无论有无空间,程序都会异常结束,管道断裂
二、有名管道
1、特点
- 对应管道文件,可用于任意进程之间进行通信
- 打开管道时可指定读写方式
- 通过文件IO操作,内容存放在内存中
- 只有读端存在时,有名管道打开会阻塞
无论是有名管道还是无名管道,当读写端全部关闭时,管道存放在内存中的内容都会被自动释放
2、相关函数:
#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *path,mode_t mode);
- 成功返回0,失败返回EOF
- path创建的管道文件路径
- mode管道文件权限,如0666
三、信号
1、信号的相关概念
- 信号时在软件层次上对中断机制的一种模拟,是一种异步通信方式
- linux内核通过信号通知用户进程,不同信号类型代表不同事件
- linux对早期unix信号机制进行了扩展
- 进程对信号有不同响应方式:
1、缺省方式
2、忽略信号
3、捕捉信号
2、信号发送
#include <unistd.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);
- 成功返回0,失败返回EOF
- pid接受进程的进程号;0代表同组进程;-1代表所有进程
- sig信号类型
- raise函数只能给自己这个进程发信号
int alarm(unsigned int seconds);
- 成功时返回上个定时器的剩余时间,失败返回EOF
- seconds定时器的时间
- 一个进程中只能设定一个定时器,时间到时产生SIGALRM
- 该函数常用于超时检测
int pause(void);
- 进程一直阻塞,直到被信号中断
- 被信号中断后返回-1,errno为EINTR
3、设置信号相应方式
#include <unistd.h>
#include <signal.h>
void (*signal(int signo,void (*handler)(int)))(int);
- 成功时返回原先的信号处理函数,失败时返回SIG_ERR
- signo要设置的信号类型
- handler指定的信号处理函数:SIG_DFL代表缺省方式;SIG_IGN代表忽略信号
void handler (int signo)
{
if (SIGINT == signo)
{
printf("I have got SIGINT\n");
}
if (SIGQUIT == signo)
{
printf("I have got SIGQUIT\N");
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGQUIT,handler);
while(1)
{
pause();
}
return 0;
}
System V IPC
一、概念
- IPC对象包含:共享内存、消息队列、信号灯集
- 每个IPC对象有唯一的ID
- IPC对象创建后一直存在,直到被显式地删除
- 每个IPC对象有一个关联的KEY
- ipcs查看IPC对象,ipcrm删除IPC对象
二、相关函数
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
- 成功时返回合法key值,失败时返回EOF
- path存在且可访问的文件的路径
- proj_id用于生成key的数字,不能为0
int main()
{
key_t key;
if((key = ftok(".",'a')) == -1)
{
peeror("key");
exit(-1);
}
}
三、共享内存
1、特点
- 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
- 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
- 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
2、使用步骤
- 创建/打开共享内存
- 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
- 读写共享内存
- 撤销共享内存映射
- 删除共享内存对象
3、相关函数
int shmget(key_t key, int size, int shmflg);
- 成功时返回共享内存的id,失败时返回EOF
- key 和共享内存关联的key,IPC_PRIVATE或ftok生成
- shmflg 共享内存标志位IPC_CREAT|0666
void *shmat(int shmid,const void *shmaddr, int shmflg);
- 成功时返回映射后的地址,失败时返回(void *)-1
- shmid 要映射的共享内存id
- shmaddr 映射后的地址,NULL表示由系统自动映射
- shmflg 标志位0表示可读写,SHM_RDONLY表示只读
void *shmdt(void *shmaddr);
- 成功时返回0,失败时返回EOF
- 不使用共享内存时应撤销映射
- 进程结束时自动撤销
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 成功时返回0,失败时返回EOF
- shmid 要映射的共享内存id
- cmd 要执行的操作 IPC_STAT,IPC_SET,IPC_RMID
- buf 保存或设置共享内存属性的地址
4、使用时注意事项
- 每块共享内存大小有限制 ipcs -l
- 共享内存删除的时间点
(1)shmctl添加删除标记
(2)nattach变成0时真正删除
四、消息队列
1、特点
- 消息队列由消息队列ID来唯一标识
- 消息队列就是一个消息的列表,用户可以在消息队列中添加消息、读取消息等
- 消息队列可以按照类型来发送/接收消息
2、使用步骤
- 打开/创建消息队列
- 向消息队列发送消息
- 从消息队列接收消息
- 控制消息队列
3、相关函数
int msgget(key_t key,int msgflg);
- 成功时返回消息队列id,失败返回EOF
- key 和消息队列关联的key,IPC_PRIVATE或ftok
- msgflg 标志位 IPC_CREAT|0666
int msgsnd(int msgid, const void *msgp,size_t size, int msgflg);
- 成功时返回0,失败返回EOF
- msgid 消息队列id
- msgp 消息缓冲区地址
- size 消息正文长度
- msgflg 标志位0或IPC_NOWAIT
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);
- 成功时返回0,失败返回-1
- msgid 消息队列id
- msgp 消息缓冲区地址
- size 指定接收的消息长度
- msgtype 消息类型
- msgflg 标志位0或IPC_NOWAIT
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
- 成功时返回0,失败返回-1
- msgid 消息队列id
- cmd 要执行的操作 IPC_STAT/IPC_SET/IPC_RMID
- buf 存放消息队列属性的地址
4、消息格式
- 通信双方首先定义好统一的消息格式
- 用户根据应用需求定义结构体类型
- 首成员类型为long,代表消息类型(正整数)
- 其他成员都属于消息正文
typedef struct
{
long mtype; // 消息类型 应当是一个正整数
char mtext[64]; // 消息正文
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))