一、基本概念
什么进程间通信(IPC):指两个或多个进程之间交换数据的过程叫进程间通信
进程之间为什么需要通信?
当需要多个进程协同工作高效率完成任务时,因为每个进程都是独立的个体(资源单位)
,进程之间就需要进行通信。
进程间通信方式:
1、简单进程间通信:命令行参数,环境变量表、信号、文件
2、传统进程间通信:管道
3、XSI进程间通信:共享内存、消息队列、信号量
4、网络进程间通信:socket
二、传统的进程间通信-管道
管道是UNIX系统最古老的进程间通信方式(基本不在使用),历史上的管道通常是半双工
(只允许单向数据流动),现在的系统大都可以全双工,数据可以双向流动
1、有名管道(创建实体文件) tmp
命令:mkfifo
函数:mkfifo
int mkfifo(const char *pathname, mode_t mode);
功能:创建管道文件
pathname:文件路径
mode:权限
返回值:成功返回0,失败返回-1
编程模型:
进程A 进程B
创建管道(mkfifo) ....
打开管道(open) 打开管道
读/写管道(read/write) 读/写数据
关闭管道(close) 关闭管道
删除管道(unlink) ....
2、无名管道(用于通过fork创建的父子
int pipe(int pipefd[2]);
功能:创建无名管道
pipefd:用来存储内核返回的文件描述符
pipefd[0];用于读操作
pipdfd[1]:用于写操作
练习1:使用有名管道进行通信,管道创建者读,对方写
练习2:使用无名管道进程通信,父进程读,子进程写
三、XSI进程间通信
X/open组织为UNIX系统设计一套进程间通信机制,有共享内存、消息队列、信号量
1、IPC标识
内核会为每个XSI的进程间通信对象维护一个IPC对象(XSI对象)
该对象通过一个非负整数来引用(类似于文件描述符)
与文件描述符不同的是,每用一个IPC对象标识符就持续+1,达到最大值时再从零开始
IPC标识需要程序员自己创建(类似于创建文件)
2、IPC键值
#include <sys/types.h>
#include <sys/ipc.h>
创建IPC键值的依据(类似创建文件的文件名),也是一个非负整数
1、自定义(不建议,可能会冲突)
2、自动生成(项目路径,项目编号)
key_t ftok(const char *pathname, int proj_id);
注意:项目路径一定要是有效路径,生成IPC键值依靠的是路径而不是字符串
3、IPC对象的创建用到的宏
IPC_PRIVATE 创建IPC对象时永远创建成功
IPC_CREAT 对象存在则获取,不存在则创建
IPC_EXCL 如果对象已经创建,则创建失败
4、IPC对象销毁/控制用到的宏
IPC_STAT 获取IPC对象的属性
IPC_SET 设置IPC对象的属性
IPC_RMID 删除IPC对象
四、共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
共享内存就是内核中开辟一块由IPC对象管理内存,进程A和进程B都用自己的虚拟
地址与它映射,这样就共享了同一块内存,然后就可以通信了
特点:
1、不需要复制信息,是最快的一种进程间通信机制
2、需要需要同步问题(必须借助其它的机制,如信号)
编程模型:
进程A: 进程B:
生成IPC键值 ftok 生成IPC键 ftok
创建共享内存 shmget 获取共享内存 shmget
映射共享内存 shmat 映射共享内存 shmat
使用共享内存 *ptr 使用共享内存 *ptr
取消映射共享内存 shmdt 取消映射 shmdt
删除共享内存 shmctl .....
int shmget(key_t key, size_t size, int shmflg);
功能:创建/获取共享内存
key:IPC键,由ftok函数生成
size:共享内存的大小,最好时4096的整数倍,获取共享内存时,此值无效
shmflg:
0 获取共享内存
IPC_CREAT 创建
IPC_EXCL 如果存在则创建失败
返回值:成功返回共享内存标识(IPC标识),失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存
shmid:共享内存标识符,shmget函数的返回值
shmaddr:进程提供的虚拟地址,与内核中的内存映射用的,也可以时NULL
(内核会自动选择一个地址映射)
shmflg:
SHM_RDONLY 只读权限
SHM_RND 当shmaddr不为空时自动选择一个地址映射
返回值:映射成功后的虚拟地址,失败返回 (void*)-1
int shmdt(const void *shmaddr);
功能:取消虚拟地址与共享内存的映射
shmaddr:被映射过的虚拟地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除共享内存,获取/设置共享内存
shmid:共享内存标识符
cmd:
IPC_STAT 获取共享内存的属性
IPC_SET 设置共享内存的属性
IPC_RMID 删除IPC共享内存
struct shmid_ds {
struct ipc_perm shm_perm; /* 内存所有者及权限 */
size_t shm_segsz; /* 内存的大小 */
time_t shm_atime; /* 最后映射时间 */
time_t shm_dtime; /* 最后取消映射时间 */
time_t shm_ctime; /* 最后修改时间 */
pid_t shm_cpid; /* 创建者进程ID */
pid_t shm_lpid; /* 最后映射/取消映射的进程ID */
shmatt_t shm_nattch; /* 映射的次数 */
...
};
struct ipc_perm {
key_t __key; /* IPC键值 */
uid_t uid; /* 有效用户ID */
gid_t gid; /* 有效组ID */
uid_t cuid; /* 创建者的用户ID */
gid_t cgid; /* 创建者的组ID */
unsigned short mode; /* 权限 */
unsigned short __seq; /* 对象id */
};
五、消息队列
消息队列就是由内核负责管理的一个管道,可以按顺序发送消息包
(消息类型+消息内容),可以全双工工作,可以不按消息的顺序接收
int msgget(key_t key, int msgflg);
功能:创建/获取消息队列
key:IPC键值,由ftok函数自动生成
msgflg:
0 获取消息队列
IPC_CREAT 创建消息队列
IPC_EXCL 如果存在则创建失败
返回值:消息队列标识
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送消息
msqid:消息度列标识,msgget函数返回值
msgp:结构指针
struct msgbuf {
long mtype; /* 消息类型 */
char mtext[1]; /* 消息内容 */
};
msgsz:消息的长度,不包括消息类型,sizeof(msgbuf)-4
msgflg:
0 阻塞,当消息队列满时,等待
1 不阻塞,当消息队列满时,不等待
------------------------------------
返回值:成功发送返回0,失败返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中按类型获取消息
msqid:消息度列标识,msgget函数返回值
msgp:结构指针
struct msgbuf {
long mtype; /* 消息类型 */
char mtext[1]; /* 消息内容 */
};
msgsz:要接收的消息的长度,可以长一些
msgtyp:要接收的消息类型
0 接受任意类型的消息(接受队列中第一个消息)
>0 只接收msgtyp类型的消息
<0 接收消息队列中小于等于msgtyp绝对值的消息,取最小的那个
msgflg:
0 阻塞,消息队列中是否有对应类型的消息
1 不阻塞,当消息队列满时,不等待
------------------------------------
MSG_NOERROR:
消息类型正确,而消息的实际长度大于msgsz,则不接收消息并返回-1
如果msgflg带MSG_NOERROR标志,则把多余的消息截取,成功接收
IPC_NOWAIT:
如果消息队列没有要接收的消息,则不等待,返回-1
MSG_EXCEPT:
接收消息队列中第一个消息不是msgtyp的消息,编译时添加-D_GNU_SOURCE参数
返回值:成功接收到消息的字节数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除消息队列,设置或获取消息队列属性
msqid:消息度列标识,msgget函数返回值
cmd:
IPC_STAT 获取消息队列的属性
IPC_SET 设置消息队列的属性
IPC_RMID 删除IPC消息队列
struct msqid_ds {
struct ipc_perm msg_perm; /* 权限 */
time_t msg_stime; /* 最后一个消息发送时间 */
time_t msg_rtime; /* 最后一个消息接收时间 */
time_t msg_ctime; /* 最后一次修改时间 */
unsigned long __msg_cbytes; /* 消息队列中的字节数 */
msgqnum_t msg_qnum; /* 消息队列中消息的个数 */
msglen_t msg_qbytes; /* 消息队列中容纳的最大字节数 */
pid_t msg_lspid; /* 最后一个发送消息的进程 */
pid_t msg_lrpid; /* 最后一个接收消息的进程 */
};
struct ipc_perm {
key_t __key; /* IPC键值 */
uid_t uid; /* 有效用户ID */
gid_t gid; /* 有效组ID */
uid_t cuid; /* 创建者的用户ID */
gid_t cgid; /* 创建者的组ID */
unsigned short mode; /* 权限 */
unsigned short __seq; /* 对象id */
};
返回值:成功返回0,失败返回-1
六、信号量
内核维护的计数器,用于多进程之间共享资源
例如:有个变量n表示资源的数量,当有进程想要独占一个资源时,n的值要减1,如果n的值
等于0(不够减多个),则进程阻塞,直到n的值可以减再被唤醒,当资源使用完毕后n的值要加1
(可能加多个)
int semget(key_t key, int nsems, int semflg);
功能:创建/获取信号量
key:IPC键值
nsems:信号量的数量
semflg:
0 获取信号量
IPC_CREAT 创建信号量(已存在则获取,不存在则创建)
IPC_EXCL 如果已经存在则创建失败
返回值:信号量标识
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:操作信号量(对信号进行加/减操作)
semid:信号量标识,semget的返回值
sops:结构体数组
nsops:数组的长度
struct sembuf{
unsigned short sem_num; /* 信号量的下标 */
short sem_op; /* 操作 */
short sem_flg; /* 标记 */
IPC_NOWAIT 当信号量不够减时,不阻塞
SEM_NUDO 当进程结束时,信号量的值自动归还
}
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout);
功能:带时间限制的操作信号量
struct timespec{
_time_t tv_sec; 秒
long int tv_nsec; 纳秒
}
int semctl(int semid, int semnum, int cmd, ...);
功能:初始化信号量,删除信号量,获取、设置信号量的属性
cmd:
IPC_STAT 获取信号量的属性
IPC_SET 设置信号量的属性
IPC_RMID 删除信号量
IPC_INFO 获取信号量的信息
SEM_INFO 设置信号量的信息
GETALL 获取所有信号量的值
GETNCNT 获取信号量的数量
GETVAL 获取某个信号量的值
SETALL 设置所有信号量的值
SETVAL 设置某个信号量的值
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) */
};
//
struct ipc_perm {
key_t __key; /* Key supplied to semget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
编程模型:
进程A 进程B
创建信号量 semget 获取信号量
初始化信号量的值 semctl ......
加减信号量 semop 加减信号量
删除信号量 semctl ......
注意:信号量是用来计数的,一定要有资源对应
七、IPC命令
显示IPC对象
ipcs -m
ipcs -q
ipcs -s
ipcs -a
删除IPC对象
ipcrm -m ID
ipcrm -q ID
ipcrm -s ID