LINUX-并发程序设计System V IPC篇

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;
}

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值