System V IPC
- IPC对象包含:共享内存,消息队列和信号灯集
- 每个IPC对象有唯一的ID
- IPC对象创建后一直存在,直到被显式地删除
- 每个IPC对象有一个关联的KEY
- ipcs/ipcrm
ftok(file to key)
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *path,int proj_id);
- 成功时返回合法的key值,失败时返回EOF
- path:存在且可访问的文件的路径
- proj_id:用于生成key的数字,不能为0
测试
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<unistd.h>
int main(int argc,const char **argv){
key_t key;
if((key = ftok(".",'a'))== -1){
perror("key");
exit(-1);
}
return 0;
}
共享内存
- 共享内存时一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
- 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
- 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
- 每块共享内存的大小有限制
ipcs -l
查看/proc/sys/kernel/shmmax
修改此路径下的配置文件可更改
- 共享内存删除的时间是nattch为0时;
ipcs
命令可查看当前共享内存段
共享内存的使用
- 创建/打开共享内存 - shmget
- 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问 - shmat
- 读写共享内存
- 撤销共享内存映射 - shmdt
- 删除共享内存对象 - shmctl
共享内存创建 - shmget
#include<sys/ipc.h>
#include<sys/shm.h>
int shmget(key_t key,int size,int shmflg);
- 成功时返回共享内存的id,失败时返回EOF
- key:和共享内存关联的key,IPC_PRIVATE(0)或ftok生成
- size:共享内存的大小(以字节为单位)
- shmflg:共享内存标志位 IPC_CREAT|0666
测试
创建一个私有的共享内存,大小为512字节,权限为0666
int shmid;
if((shmid = shmget(IPC_PRICATE,512,0666))<0){
perror("shmget");
exit(-1);
}
创建一个公有的共享内存,大小为1024字节,权限为0666
key_t key;
int shmid;
if((key = ftok(".",'m'))== -1){
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,1024,IPC_CREAT|0666))<0){
perror("shmget");
exit(-1);
}
共享内存映射 - shmat
#include<sys/ipc.h>
#include<sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg);
- 成功时返回映射后的地址,失败时返回(void *)-1
- shmid:要映射的共享内存id
- shmaddr:映射后的地址,NULL表示由系统自动映射
- shmflg:标志位 0表示可读写;SHM_RDONLY表示只读
测试
共享内存读写
- 通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型。
在共享内存中存放键盘输入的字符串
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define N 1024
int main(){
char *addr;
key_t key;
int shmid;
if((key = ftok(".",'m'))== -1){
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,1024,IPC_CREAT|0666))<0){
perror("shmget");
exit(-1);
}
if((addr = (char *)shmat(shmid,NULL,0))==(char *)-1){
perror("shmat");
exit(-1);
}
//fgets(addr,N,stdin);
//省略读取操作
shmdt(addr);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
共享内存撤销映射 - shmdt
#include<sys/ipc.h>
#include<sys/shm.h>
int shmdt(void *shmaddr);
- 成功时返回0,失败时返回EOF
- 不使用共享内存时应撤销映射
- 进程结束时自动撤销
共享内存控制 - shmctl
#include<sys/ipc.h>
#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
- 成功时返回0,失败时返回EOF
- shmid:要操作得共享内存的id
- cmd:要执行的操作,eg:IPC_STAT(状态) IPC_SET(设置) IPC_RMID(删除共享内存ID/第三个参数为NULL)
- buf:保存或设置共享内存属性的地址
消息队列
- 消息队列是System V IPC对象的一种
- 消息队列由消息队列ID来唯一标识
- 消息队列就是一个消息的队列。用户可以在消息队列中添加消息,读取消息等
- 消息队列可以按照类型来发送/接收消息
消息队列的使用
- 打开/创建消息队列 - msgget
- 向消息队列发送消息 - msgsnd
- 从消息队列接收消息 - msgrcv
- 控制消息队列 - msgctl
打开/创建消息队列 - msgget
#include<sys/ipc.h>
#include<sys/msg.h>
int msgget(key_t key,int msgflg);
- 成功时返回消息队列的id,失败时返回EOF
- key:和消息队列关联的key IPC_PRIVATE或ftok
- msgflg:标志位 IPC_CREAT|0666
测试
int msgid;
key_t key;
if((key = ftok(".",'q'))== -1){
perror("ftok");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
发送消息 - msgsnd
#include<sys/ipc.h>
#include<sys/msg.h>
int msgsnd(int msgid,const void *msgp,size_t size,int msgflg);
- 成功时返回0,失败时返回EOF;
- msgid:消息队列id
- msgp:消息缓冲区地址
- size:消息正文长度
- msgflg:标志位0或IPC_NOWAIT
消息格式
- 通信双方首先定义好统一的消息格式
- 用户根据应用需求定义结构体类型
- 首成员类型为long,代表消息类型(正整数)
- 其他成员都属于消息正文
测试
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
buf.type = 100;
fgets(buf,mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
接收消息 - msgrcv
#include<sys/ipc.h>
#inlcude<sys/msg.h>
int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg);
- 成功时返回收到的消息长度,失败时返回EOF
- msgid:消息队列id
- msgp:消息缓冲区地址
- size:指定接收的消息长度
- msgtype:指定接收的消息类型
- msgflg:标志位 0或IPC_NOWAIT
测试
if(msgrcv(msgid,$buf,LEN,200,0)<0){
perror("msgrcv");
exit(-1);
}
控制消息队列 - msgctl
#include<sys/ipc,h>
#include<sys/msg.h>
int msgct(int msgid,int cmd,struct msqid_ds *buf);
- 成功时返回0,失败时返回EOF
- msgid:消息队列id
- cmd:要执行的操作 IPC_STAT/IPC_SET/IPC_RMID
- buf:存放消息队列属性的地址
测试
实现两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的消息
//client1
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
typedef struct{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define type1 100
#define type2 200
int main(){
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",'q'))==-1){
perror("ftok");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
while(1){
buf.mtype = type2;
printf("input > ");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
if(strcmp(buf.mtext,"quit\n")==0) break;
if(msgrcv(msgid,&buf,LEN,type1,0)<0){
perror("msgrcv");
exit(-1);
}
if(strcmp(buf.mtext,"quit\n")==0){
msgctl(msgid,IPC_RMID,0);
exit(0);
}
printf("recv from client2 : %s",buf.mtext);
}
return 0;
}
//client2
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
typedef struct{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define type1 100
#define type2 200
int main(){
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",'q'))==-1){
perror("ftok");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
while(1){
if(msgrcv(msgid,&buf,LEN,type2,0)<0){
perror("msgrcv");
exit(-1);
}
printf("recv from client1 : %s",buf.mtext);
if(strcmp(buf.mtext,"quit\n")==0){
msgctl(msgid,IPC_RMID,0);
exit(0);
}
buf.mtype = type1;
printf("input > ");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
if(strcmp(buf.mtext,"quit\n")==0) break;
}
return 0;
}
信号灯
- 信号灯也叫信号量,用于进程/线程同步或互斥的机制
- 信号灯的类型
- Posix 无名信号灯
- Posix 有名信号灯
- System V 信号灯
- 信号灯的含义
- 计数信号灯
- System V 信号灯时一个或多个计数信号灯的集合
- 可同时操作集合中的多个信号灯
- 申请多个资源时避免死锁
信号灯的使用
- 打开/创建信号灯 - semget
- 信号灯初始化 - semctl
- P/V操作 - semop
- 删除信号灯 - semctl
打开创建信号灯 - semget
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key,int nsems,int semflg);
- 成功时返回信号灯的id,失败时返回EOF
- key:和消息队列关联的key(IPC_PRIVATE或ftok)
- nsems:集合中包含的计数信号灯个数
- semflg:标志位(IPC_CREAT|0666 IPC_EXCL)
信号灯初始化 - semctl
#include<sys/ipc.h>
#include<sys/sem.h>
int semctl(int semid,int semnum,int cmd,…);
- 成功时返回0,失败时返回EOF
- semid:要操作的信号灯集id
- semnum:要操作的集合中的信号灯编号
- cmd:执行的操作(SETVAL,IPC_RMID)
- union semun:取决于cmd
测试
实现一个初始化为2,一个初始化为0的信号灯集合
union semun myun;
myun.val = 2;
if(semctl(semid,0,SETVAL,myun)<0){
perror("semctl");
exit(-1);
}
myun.val = 0;
if(semctl(semid,1,SETVAL,myun)<0){
perror("semctl");
exit(-1);
}
信号灯-P/V操作 - semop
#include<sys/ipc.h>
#include<sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);
- 成功时返回0,失败时返回-1
- semid:要操作的信号灯集id
- sops:描述对信号灯操作的结构体(数组)
- nsops:要操作的信号灯的个数
sembuf
struct sembuf{
short semnum;
short sem_op;
short sem_flg;
}
- semnum:信号灯编号
- sem_op:-1:P操作;1:V操作
- sem_flg:0/IPC_NOWAIT
测试
实现父子进程通过System V信号灯同步对共享内存的读写
父进程输入,子进程接收去除空格再输出
//信号灯集/共享内存
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#define N 64
#define READ 0
#define WRITE 1
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
void init_sem(int semid,int s[],int n){
int i;
union semun myun;
for(i=0;i<n;i++){
myun.val = s[i];
semctl(semid,i,SETVAL,myun);
}
}
void pv(int semid,int num,int op){
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid,&buf,1);
}
int main(){
int shmid,semid,s[]={0,1};
pid_t pid;
key_t key;
char *shmaddr;
if((key = ftok(".",'s'))==-1){
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,N,IPC_CREAT|0666))<0){
perror("shmget");
exit(-1);
}
if((semid = semget(key,2,IPC_CREAT|0666))<0){
perror("semget");
goto _error1;
}
init_sem(semid,s,2);
if((shmaddr = (char *)shmat(shmid,NULL,0))== (char *)-1){
perror("shmat");
goto _error2;
}
if((pid = fork())<0){
perror("fork");
goto _error2;
}
else if(pid == 0){
char *p,*q;
while(1){
pv(semid,READ,-1);
p = q = shmaddr;
while(*q){
if(*q != ' '){
*p++ = *q;
}
q++;
}
*p = '\0';
printf("%s",shmaddr);
pv(semid,WRITE,1);
}
}
else{
while(1){
pv(semid,WRITE,-1);
printf("input >");
fgets(shmaddr,N,stdin);
if(strcmp(shmaddr,"quit\n") == 0) break;
pv(semid,READ,1);
}
kill(pid,SIGUSR1);
}
_error2:
semctl(semid,0,IPC_RMID);
_error1:
shmctl(semid,IPC_RMID,NULL);
return 0;
}