进程间通信的方式及代码讲解

进程间通信


在UNIX伯克利版本system-V(念作系统五,V是罗马数字,是Unix伯克利分支的版本号)中引入的三种通信方式(消息队列、共享内存和信号量组)被称为IPC对象
IPC对象属于系统,而不是进程,因此在没有明确删除操作的情况下,IPC对象不会因为进程的退出而消失。
查看IPC对象# 查看系统当前所有IPC对象
gec@ubuntu:~$ ipcs -a

查看系统当前所有的消息队列MSG对象

gec@ubuntu:~$ ipcs -q

查看系统当前所有共享内存SHM对象

gec@ubuntu:~$ ipcs -m

查看系统当前所有信号量组SEM对象

gec@ubuntu:~$ ipcs -s
删除IPC对象
ipcrm -Q key : 删除指定的消息队列
ipcrm -q id : 删除指定的消息队列

ipcrm -M key : 删除指定的共享内存
ipcrm -m id: 删除指定的共享内存

ipcrm -S key : 删除指定的信号量
ipcrm -s id: 删除指定的信号量

查看系统当前所有IPC对象

gec@ubuntu:~$ ipcs -a
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 393216     gec        600        67108864   2          dest         
0x00000000 557057     gec        600        524288     2          dest         
0x00000000 655362     gec        600        524288     2          dest         
0x00000000 688131     gec        600        524288     2          dest         
0x51010451 8978436    gec        600        1024       1                       
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

查看系统当前所有的消息队列MSG对象

gec@ubuntu:~$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

查看系统当前所有共享内存SHM对象

gec@ubuntu:~$ ipcs -m
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 393216     gec        600        67108864   2          dest         
0x00000000 557057     gec        600        524288     2          dest         
0x00000000 655362     gec        600        524288     2          dest         
0x00000000 688131     gec        600        524288     2          dest         
0x51010451 8978436    gec        600        1024       1                       

查看系统当前所有信号量组SEM对象

gec@ubuntu:~$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems

消息队列

最主要的特征是允许发送的数据携带类型
// 创建(或打开)消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
接口说明
返回值:消息队列MSG对象ID
参数key:键值,全局唯一标识,可由ftok()产生
参数msgflg:操作模式与读写权限,与文件操作函数open类似。

示例代码:

int main()
{
    // 以当前目录和序号1为系数产生一个对应的键值
    key_t key = ftok(".", 1);
    // 创建(若存在则报错)key对应的MSG对象
    int msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);

}
key实质上就是一个整数,但该整数一般应由 ftok() 产生而不应手写,因为key作为键值是 IPC 对象在系统中的唯一标识,万一误撞就会导致错乱。ftok()函数参数中的路径仅仅是产生键值key的参数,与实际文件系统并无关系

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname/*路径*/, int proj_id/*序号*/);msgget() 中的key写成 IPC_PRIVATE,那意味着新建一个私有的IPC对象,该对象只在本进程内部可见,与外部的系统MSG对象不会冲突。

向MSG对象发送消息

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
接口说明:
msgqid:MSG对象的ID,由msgget()获取。
msgp:一个指向等待被发送的消息的指针,由于MSG中的消息最大的特点是必须有一个整数标识,用以区分MSG中的不同的消息,因此MSG的消息会使用一个特别的结构体来表达,具体如下所示:
struct msgbuf
{
    // 消息类型(固定)
    long mtype;
    // 消息正文(可变)
};

因此一般而言,msgp就是一个指向上述结构体的指针。
msgsz:消息正文的长度(单位字节),注意不含类型长度。
msgflg:发送选项,一般有:
0:默认发送模式,在MSG缓冲区已满的情形下阻塞,直到缓冲区变为可用状态。
IPC_NOWAIT:非阻塞发送模式,在MSG缓冲区已满的情形下直接退出函数并设置错误码为EAGAIN.
示例代码:

struct message
{
	long mtype;
	char mtext[80];
};
int main(void)
{
	int msgid;
	msgid = msgget(ftok(".", 1), IPC_CREAT | 0666);
	struct message msg;
	bzero(&msg, sizeof(msg));
	// 消息类型
	msg.mtype = 1;
	// 消息内容
	fgets(msg.text, 80, stdin);
    // 发送消息
	msgsnd(msgid, &msg, strlen(msg.mtext), 0);
 }

从MSG对象接收消息

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
接口说明:
msgqid:MSG对象的ID,由msgget()获取。
msgp:存放消息的内存入口。
msgsz:存放消息的内存大小。
msgtyp:欲接收消息的类型:
0:不区分类型,直接读取MSG中的第一个消息。
大于0:读取类型为指定msgtyp的第一个消息(若msgflg被配置了MSG_EXCEPT则读取除了类型为msgtyp的第一个消息)。
小于0:读取类型小于等于msgtyp绝对值的第一个具有最小类型的消息。例如当MSG对象中有类型为3、1、5类型消息若干条,当msgtyp为-3时,类型为1的第一个消息将被读取。
msgflg:接收选项:
0:默认接收模式,在MSG中无指定类型消息时阻塞。
IPC_NOWAIT:非阻塞接收模式,在MSG中无指定类型消息时直接退出函数并设置错误码为ENOMSG.
MSG_EXCEPT:读取除msgtyp之外的第一个消息。
MSG_NOERROR:如果待读取的消息尺寸比msgsz大,直接切割消息并返回msgsz部分,读不下的部分直接丢弃。若没有设置该项,则函数将出错返回并设置错误码为E2BIG。

示例代码:

struct message
{
	long mtype;
	char mtext[80];
};
int main(void)
{
	int msgid;
	msgid = msgget(ftok(".", 1), IPC_CREAT | 0666);
	struct message msgbuf;
	bzero(&msgbuf, sizeof(msgbuf));
	printf("等待消息...\n");
    int m = msgrcv(msgid, &msgbuf, sizeof(msgbuf)-sizeof(long), 1, 0);
	if(m < 0)
		perror("msgrcv()");
    else
	    printf("%s\n", msgbuf.text);
	msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

对MSG对象其余操作

要想显式地删除掉MSG对象,可以使用如下接口:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

接口说明:
msqid:MSG对象ID
cmd:控制命令字
IPC_STAT:获取该MSG的信息,储存在结构体msqid_ds中
IPC_SET:设置该MSG的信息,储存在结构体msqid_ds
IPC_RMID:立即删除该MSG,并且唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数
应用如下代码删除:
msgctl(id, IPC_RMID, NULL);
共享内存通信
顾名思义,就是通过不同进程共享一段相同的内存来达到通信的目的,SHM一般不能单独使用,而需要配合诸如互斥锁、信号量等协同机制使用

使用共享内存的一般步骤是:

获取共享内存对象的ID
将共享内存映射至本进程虚拟内存空间的某个区域
当不再使用时,解除映射关系
当没有进程再需要这块共享内存时,删除它。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
接口说明:
返回值:SHM对象ID
参数key:SHM对象键值
参数size:共享内存大小
参数shmflg:创建模式和权限
IPC_CREAT:如果key对应的共享内存不存在,则创建SHM对象
IPC_EXCL:如果该key对应的共享内存已存在,则报错
权限与文件创建open类似,用八进制表示

示例代码:

int main(void)
{
	key_t key = ftok(".", 1);
	int shmid;
	// 创建或打开一个大小为1024自己的SHM对象,获取其ID
	shmid = shmget(key, 1024, IPC_CREAT|0666);
	if(shmid < 0)
	{
		perror("创建SHM对象失败");
	}
	// ...
	return 0;

}

映射 / 解除映射SHM对象

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr/*一般为NULL*/, int shmflg);

接口说明:
功能:
将指定的共享内存,映射到本进程内存空间
参数:
shmid:指定的共享内存的ID
shmaddr:指定映射后的地址,因为是虚拟地址,分配的原则要兼顾诸如段对齐、权限分配等问题,因此用户进程是无法指定的,只能由系统自动分配,因此此参数一般为NULL,表示交由系统来自动分配。
shmflg:可选项
0:默认,代表共享内存可读可写。
SHM_RDONLY:代表共享内存只读。
返回值:
共享内存映射后的虚拟地址入口。
正确映射之后,命令ipcs -m查看SHM对象时,可从nattch列中看到已映射进程个数:

aidevelop@aidevelop-vm:~$ ipcs -m
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 688132     aidevelop  600        67108864   2          dest         
0x00000000 688137     aidevelop  600        524288     2          dest                       

使用完SHM对象后,需要将其跟进程解除关联关系,即解除映射,函数接口如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
该函数接口非常简单,参数就是从 shmat() 返回的SHM对象的入口指针。
删除SHM对象等其余操作,接口如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
接口说明:
shmid:指定的共享内存的ID
cmd:一些命令字
IPC_STAT:获取共享内存 的一些信息,放入shmid_ds{ }中
IPC_SET:将 buf 中指定的信息,设置到本共享内存中
IPC_RMID:删除指定的共享内存,此时第三个参数 buf 将被忽略
buf:用来存放共享内存信息的结构体
用的较多的就是删除SHM对象,示例代码如下:
shmctl(shmid, IPC_RMID, NULL);

POSIX信号量通信

信号量SEM全称Semaphore,中文也翻译为信号灯。信号量跟MSG和SHM有极大的不同,SEM不是用来传输数据的,而是作为“旗语”,用来协调各进程或者线程工作的。
信号量的分类
在Unix/Linux系统中常用的信号量有三种:
IPC信号量组
POSIX具名信号量
POSIX匿名信号量
IPC信号量组是一个数组,里面包含N个信号量元素,每个元素相当于一个POSIX信号量。

基本概念

临界资源(critical resources)
多个进程或线程有可能同时访问的资源(变量、链表、文件等等)
临界区(critical zone)
访问这些资源的代码称为临界代码,这些代码区域称为临界区
P操作
程序进入临界区之前必须要对资源进行申请,这个动作被称为P操作,这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样,P操作就是申请资源,如果申请成功,资源数将会减少。如果申请失败,要不在门口等,要不走人。
V操作
程序离开临界区之后必须要释放相应的资源,这个动作被称为V操作,这就像你把车开出停车场之后,要将停车卡归还给保安一样,V操作就是释放资源,释放资源就是让资源数增加。

创建(或打开)SEM

接口如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
接口说明:
key:SEM对象键值
nsems:信号量组内的信号量元素个数
semflg:创建选项
IPC_CREAT:如果key对应的信号量不存在,则创建之
IPC_EXCL:如果该key对应的信号量已存在,则报错
mode:信号量的访问权限(八进制,如0644)
创建信号量时,还受到以下系统信息的影响:
SEMMNI:系统中信号量的总数最大值。
SEMMSL:每个信号量中信号量元素的个数最大值。
SEMMNS:系统中所有信号量中的信号量元素的总数最大值。
Linux中,以上信息在 /proc/sys/kernel/sem 中可查看。
示例代码:

int main()
{
     key_t key = ftok(".", 1);
     // 创建(若已有则打开)一个包含2个元素的信号量组
     int id = semget(key, 2, IPC_CREAT|0666);
}

PV操作

对于信号量而言,最重要的作用就是用来表征对应资源的数量,所谓的P/V操作就是对资源数量进行 +n/-n 操作,既然只是个加减法,那么为什么不使用普通的整型数据呢?原因是:
整型数据的加减操作不具有原子性,即操作可能被中断
普通加减法无法提供阻塞特性,而申请资源不可得时应进入阻塞(等待)
原子性:即这种资源数量的加减法不能有中间过程,不管是成功还是失败都必须一次性完成。加减法看似简单,但在硬件层面上并非一个原子性操作,而是包含了多个寄存器操作步骤,因此不能作为P/V操作的手段。
在SEM对象中,P/V操作的函数接口如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
接口说明:
参数
semid:SEM对象ID
sops:P/V操作结构体sembuf数组
nsops:P/V操作结构体数组元素个数
返回值
成功:0
失败:-1
其中,所谓P/V操作结构体定义如下:
struct sembuf
{
    unsigned short sem_num;  /* 信号量元素序号(数组下标) */
    short		   sem_op;   /* 操作参数 */
    short		   sem_flg;  /* 操作选项 */
};

举例说明,假设有一个信号量组SEM对象,包含两个元素,现在要对第0个元素进程P操作(即减操作),对第1个元素进程V操作(即加操作),则代码如下:

int main()
{
     key_t key = ftok(".", 1);
     // 创建(若已有则打开)一个包含2个元素的信号量组
     int id = semget(key, 2, IPC_CREAT|0666);
     // 定义包含两个P/V操作的结构体数组
     struct sembuf op[2];
     op[0].sem_num = 0;  // 信号量元素序号
     op[0].sem_op  = -2; // P操作
     op[0].sem_flg = 0;  // 选项默认0
     
     op[1].sem_num = 1;  // 信号量元素序号
     op[1].sem_op  = +3; // V操作
     op[1].sem_flg = 0;  // 选项默认0
     // 同时对第0、1号信号量元素分别进行P、V操作
     semop(id, op, 2);
}

注意

P操作是申请资源,因此如果资源数不够的话会导致进程阻塞,这正是我们想要的效果,因为资源数不可为负数。P-
V操作是释放资源,永远不会阻塞。V+
SEM对象的一大特色就是可以对多个信号量元素同时进行P/V操作,这也是跟POSIX单个信号量的区别。
等零操作
当操作结构体sembuf中的sem_op为0时,称为等零操作,即阻塞等待直到对应的信号量元素的值为零,示例代码如下:
struct sembuf op[1];
op[0].sem_num = 0;
op[0].sem_op = 0; // 等零操作
op[0].sem_flg = 0;
// 若此时信号量组中的第0个元素的值不为零,则阻塞直到恢复零为止
semop(id, op, 1);
system-V的IPC对象都有类似的操作接口,SEM对象也有control函数,接口如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);

接口说明:
semid:信号量组的ID
semnum:信号量组内的元素序号(从0开始)
cmd:操作命令字:
IPC_STAT:获取信号量组的一些信息,放入semid_ds{ }中
IPC_SET:将 semid_ds{ } 中指定的信息,设置到信号量组中
IPC_RMID:删除指定的信号量组。
GETALL:获取所有的信号量元素的值。
SETALL:设置所有的信号量元素的值。
GETVAL:获取第semnum个信号量元素的值。
SETVAL:设置第semnum个信号量元素的值。
IPC_RMID
其中,当命令字为IPC_RMID时,意为删除SEM对象,这操作与其他两种IPC对象一样,示例代码如下:
// 删除SEM对象
semctl(id, 0/该参数将被忽略/, IPC_RMID);
SETVAL
一般而言,SEM对象在刚被创建出来的时候需要进行初始化,该命令字可以执行初始化的操作,示例代码如下:
// 操作联合体

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 */
};
int main()
{
    // 创建(或打开)SEM对象
    // 注意需要指定 IPC_EXCL 模式,因为要区分是否是新建的SEM对象
    int semid = semget(key, 1, IPC_EXCL|IPC_CREAT|0666);

    // 1,若SEM对象已存在,则重新打开即可
	  if(semid == -1 && errno == EEXIST)
		    semid = semget(key, 1, 0);

    // 2,若SEM对象为当前新建,则需进行初始化
	  else if(semid > 0)
    {
        union semun a;
        a.val = 1; // 假设将信号量元素初始值定为1
        semctl(semid, 0, SETVAL, a);
    }
}

注意:
semun是一个联合体,各成员对应不同的命令字。
该联合体须由调用者自己定义。
IPC_SET / IPC_STAT
这两个命令字用来设置、获取SEM对象的相关属性信息,这些信息用如下结构体表达:

struct semid_ds
{
     struct ipc_perm sem_perm;  /* 所有者、权限*/
     time_t          sem_otime; /* 最后一次PV操作的时间 */
     time_t          sem_ctime; /* 最后一次IPC_SET的时间 */
     unsigned long   sem_nsems; /* 信号量元素个数 */
};

问:老师,这三种IPC对象基本语法我都懂了,函数接口也都练习了一遍了,但我还是不知道具体在什么场合下用什么对象,我是在钻牛角尖吗?
答:不,你没在钻牛角尖,相反你提的问题很好。具体是这样的,MSG对象可以看做是一种增强型具名管道,它可以对每个数据标注类型,因此可以用在多对一通信的场合,而普通的管道由于对数据不加区分,因此只能用于一对一通信。同时还应理解到,不管是普通管道还是MSG对象,通信数据都是被发往内核,由内核进行管理,这样一来自然就省却了很多数据管理的烦恼,比如无需担心会数据越界,也无需担心读到重复的数据,但代价是降低了性能,因为数据转手了两次。SHM对象与此相对,不再由内核管理,而是直接给用户进程自己管理,因此在SHM中存取数据要比其他的IPC对象效率高得多,但也正因如此,SHM一般无法单独使用,因为我们必须使用诸如信号量、互斥锁等机制去协同各个进程或线程。至于SEM对象,它纯粹就是一种进程线程间的旗语,它不是用来传输数据的,它的作用就是红绿灯,在实际应用项目中,凡是需要协同进程线程、控制它们的执行进度,一般都离不开信号量。

(共享内存、信号)

【1】编写两个独立的程序:sender和receiver。利用共享内存和信号,实现数据从sender到receiver的妥善传输。
要求:
不能重复读取内容,就像管道一样,读过的内容不能再读取。
不能覆盖未读取的内容,就像管道一样,未读取的内容不会被覆盖。
采用信号来协调两个程序。
解答:
共享内存的使用一般要配合信号量(注意不是信号),本题强调用信号来协调读写程序,目的仅仅是练习。需要注意的另一点是,信号的发送需要双方的进程PID,因此还要使用管道来互换双方的进程号。
示例代码如下:
// 数据发送者 sender.c

int main(void)
{
	// 创建SHM对象
	int shmid = shmget(ftok(".", 1), 100, 0644|IPC_CREAT);
	// 映射SHM对象
	char *shm_addr = shmat(shmid, NULL, 0);
	// 等待对方的PID
	pid_t pid;
	mkfifo("/tmp/fifo", 0666);
	int fd = open("/tmp/fifo", O_RDONLY);
	read(fd, &pid, sizeof(pid));
	while(1)
	{
		// 往SHM对象写入数据,并发送信号通知对方读取
		fgets(shm_addr, 100, stdin);
		kill(pid, SIGUSR1);  //kill pid  SIGUSR1
	}
	
	return 0;
}

// 数据接收者 receiver.c

static int shmid;
static char *shm_addr;
void readMsg(int sig)
{
	printf("收到数据: %s", shm_addr);
}

void cleanup(int sig)
{
	// 删除管道
	unlink("/tmp/fifo");

	// 解除SHM映射,并删除SHM对象
	shmdt(shm_addr);
	shmctl(shmid, IPC_RMID, NULL);
	exit(0);
}
int main(void)
{
	// 准备好要处理的信号
	signal(SIGUSR1, readMsg); // 读取SHM对象数据
	signal(SIGINT , cleanup); // 清场退出

	// 创建SHM对象
	shmid = shmget(ftok(".", 1), 100, 0644|IPC_CREAT);

	// 映射SHM对象
	shm_addr = shmat(shmid, NULL, 0);
	// 给对方发PID
	pid_t pid = getpid();
	mkfifo("/tmp/fifo", 0666);
	int fd = open("/tmp/fifo", O_WRONLY);
	write(fd, &pid, sizeof(pid));
	while(1)
		pause();
	
	return 0;
}

(消息队列)

【2】编写两个独立的程序:Jack和Rose。利用消息队列,实现他们之间的对话。
要求:
可以以任意次序启动 Jack 和 Rose。
Jack 和 Rose 可以随时互相发送和接收消息。
可以任意启动和关闭程序,只要都启动了,就可以通信。
Jack和Rose

解答:
对题干中的几个要求的解读:可以任意次序启动Jack和Rose,这意味着双方均要默认MSG对象不存在,启动时要创建它。Jack和Rose可随时互相发消息,意味着双方均是多进程或多线程程序,一个进程发一个进程收。可以任意关闭进程,启动了就能通信,这意味着进程在关闭时可保留MSG对象不删除,或需要判定双方均已退出后方可删除MSG对象。
示例代码如下:
// Jack.c

#define JACK2ROSE 1
#define ROSE2JACK 2
struct msgbuf
{
	long mtype;
	char mtext[1024];
};

int main(void)
{
	// 创建或打开MSG对象
	int msgid = msgget(ftok(".",1), IPC_CREAT|0666);
	struct msgbuf msg;
	// 子进程负责发消息
	if(fork() == 0)
	{
		while(1)
		{
			bzero(&msg, sizeof(msg));
			msg.mtype = JACK2ROSE;
			fgets(msg.mtext, 100, stdin);

			msgsnd(msgid, &msg, strlen(msg.mtext), 0);
		}
	}

	// 父进程负责收消息
	else
	{
		while(1)
		{
			bzero(&msg, sizeof(msg));
			msgrcv(msgid, &msg, 100, ROSE2JACK, 0);
			printf("对方:%s", msg.mtext);
		}
	}
}

// Rose.c

#define JACK2ROSE 1
#define ROSE2JACK 2
struct msgbuf
{
	long mtype;
	char mtext[1024];
};

int main(void)
{
	// 创建或打开MSG对象
	int msgid = msgget(ftok(".",1), IPC_CREAT|0666);
	struct msgbuf msg;
	// 子进程负责发消息
	if(fork() == 0)
	{
		while(1)
		{
			bzero(&msg, sizeof(msg));
			msg.mtype = ROSE2JACK;
			fgets(msg.mtext, 100, stdin);
			msgsnd(msgid, &msg, strlen(msg.mtext), 0);
		}
	}
	// 父进程负责收消息
	else
	{
		while(1)
		{
			bzero(&msg, sizeof(msg));
			msgrcv(msgid, &msg, 100, JACK2ROSE, 0);
			printf("对方:%s", msg.mtext);
		}
	}
}

(共享内存、信号量)

【3】使用system-V信号量组,改进第1题。
解答:
信号量与共享内存是最常见的搭配,在本题中实际上使用POSIX单个信号即可,但由于本节课学习的是信号量组,因此只需要创建一个只包含一个信号量元素的信号量组即可。
示例代码如下:

// 信号量组操作封装函数 sem.c
union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};

void init_sem(int semid, int val)
{
	union semun a;
	a.val = val;
	semctl(semid, 0, SETVAL, a);
}
void sem_p(int semid)
{
	struct sembuf a;
	a.sem_num = 0;
	a.sem_op = -1;
	a.sem_flg = 0;
	semop(semid, &a, 1);
}
void sem_v(int semid)
{
	struct sembuf a;
	a.sem_num = 0;
	a.sem_op = 1;
	a.sem_flg = 0;
	semop(semid, &a, 1);
}

// 数据发送者 sender.c

int main(void)
{
	// 产生两个IPC对象的键值key
	key_t key1 = ftok(".", 1);
	key_t key2 = ftok(".", 2);

	// 创建并映射SHM对象
	int shmid = shmget(key1, 1024, IPC_CREAT|0666);
	char *p = shmat(shmid, NULL, 0);

	// 创建或打开SEM对象
	int semid = semget(key2, 1, IPC_EXCL|IPC_CREAT|0666);
	if(semid == -1 && errno == EEXIST)
		semid = semget(key2, 1, 0);
	else if(semid > 0)
		init_sem(semid, 1);
	while(1)
	{
		fgets(p, 1024, stdin);
		sem_v(semid); //通知  发送通知
	}
	return 0;
}

// 数据接收者 receiver.c

int main(void)
{
	// 产生两个IPC对象的键值key
	key_t key1 = ftok(".", 1);
	key_t key2 = ftok(".", 2);
	// 创建并映射SHM对象
	int shmid = shmget(key1, 1024, IPC_CREAT|0666);
	char *p = shmat(shmid, NULL, 0);

	// 创建或打开SEM对象
	int semid = semget(key2, 1, IPC_EXCL|IPC_CREAT|0666);
	if(semid == -1 && errno == EEXIST)
		semid = semget(key2, 1, 0);
	else if(semid > 0)
		init_sem(semid, 1);
	while(1)
	{
		printf("收到:%s", p);
		sem_p(semid);  //接收  申请接受
	}
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qt历险记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值