send.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct msgbuf{ //函数要求用户自定义一个结构体
long mtype;
char mtext[128];//默认128个空间
}msg_t;
int main(void) {
key_t key; //为了接收键值以确定ID
msg_t mbuf;
key = ftok(".",21); //获得一个键值
if(key == -1) {
perror("ftok");
return -1;
}
printf("key=%x\n",key);
//从内核获取消息队列
int msqid = msgget(key, IPC_CREAT|0664);
if(msqid == -1) {
perror("MSGGET");
return -1;
}
printf("msqid=0x%x\n",msqid);
//初始化消息
mbuf.mtype = 3;
strcpy(mbuf.mtext,"this is a test\n");
//向消息队列发送消息
int s = msgsnd(msqid, &mbuf, strlen(mbuf.mtext)+1, 0);
if(s == -1) {
perror("msgsnd");
return -1;
}
return 0;
}
命令: tarena@ubuntu:~/day/day33$ a.out
结果: key=15081b76
msqid=0x0
命令: tarena@ubuntu:~/day$ ipcs //察看当前列对中所占的大小和消息数
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 tarena 600 393216 2 dest
0x00000000 32769 tarena 600 393216 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
0x15081b76 0 tarena 664 15 1 //执行一次,写入一次消息,再执行一次会继续追加
recv.c:
#include
#include
#include
#include
#include
typedef struct msgbuf{
long mtype;
char mtext[128];
}msg_t;
int main(void) {
key_t key;
msg_t mbuf;
key = ftok(".",21);
if(key == -1) {
perror("ftok");
return -1;
}
printf("key=%x\n",key);
//从内核获取消息队列
int msqid = msgget(key, IPC_CREAT|0664);
if(msqid == -1) {
perror("MSGGET");
return -1;
}
printf("msqid=0x%x\n",msqid);
//从消息队列获取消息
int s = msgrcv(msqid, &mbuf, 128, 3, IPC_NOWAIT);//0是没有消息阻塞,IPC_NOWAIT是没有消息不阻塞
if(s == -1) {
perror("msgrcv");
return -1;
}
printf("%s\n", mbuf.mtext);
return 0;
}
tarena@ubuntu:~/day/day33$ rec
key=15081b76
msqid=0x0
this is a test //第一次消息列对里有消息
tarena@ubuntu:~/day/day33$ rec
key=15081b76
msqid=0x0
msgrcv: No message of desired type //第二次消息列对里没有消息,提示没有消息。
进程通信的邮箱方式由操作系统提供形如 send()和 receive()的系统调用来支持,本实验要求学生首先查找资料了解所选用操作系统平台上用于进程通信的系统调用具体形式,然后使用该系统调用编写程序进行进程间的通信,要求程序运行结果可以直观地体现在界面上。在此基础上查找所选用操作系统平台上支持信号量机制的系统调用具体形式,运用生产者与消费者模型设计实现一个简单的信箱,该信箱需要有创建、发信、收信、撤销等函数,至少能够支持两个进程互相交换信息,比较自己实现的信箱与操作系统本身提供的信箱,分析两者之间存在的异同。
背景知识
消息队列
什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
Linux中如何使用消息队列
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。
msgget()函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t key, int msgflg);
它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.
msgsnd()函数
该函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
如果调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败时返回-1.
msgrcv()函数
该函数用来从一个消息队列获取消息,它的原型为:
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
msgctl()函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int command, struct msgid_ds *buf);
成功时返回0,失败时返回-1.
信号量
什么是信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
Linux的信号量机制
Linux提供了一组精心设计的信号量接口来对信号量进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
semget()函数
它的作用是创建一个新信号量或取得一个已有信号量,原型为:
int semget(key_t key, int num_sems, int sem_flags);
成功返回一个相应信号标识符(非零),失败返回-1.
semop()函数
它的作用是改变信号量的值,原型为:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
semctl()函数
该函数用来直接控制信号量信息,它的原型为:
int semctl(int sem_id, int sem_num, int command, ...);
共享内存
什么是共享内存
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。
共享内存的使用
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
shmget()函数
该函数用来创建共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);
成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
shmat()函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
shmdt()函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
调用成功时返回0,失败时返回-1.
shmctl()函数
与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);