OS实验--利用Linux的共享内存通信机制实现两个进程间的通信
0x00 实验思路
简述:
- 创建一个通讯通道,并且用
IPC
键值来标识这个通道,使得两个进程能够使用同一个通道进行通信。 - 创建并初始化三个有名信号量
mutex
receiver_response
sender_write
,对接受和发送过程上锁,避免读者写者冲突。 - 通信双方通过共享内存首地址的值来判断是否已经发送或接受到消息
- 发送方将发送的消息存入共享内存,接收方则不断轮询共享内存,以接受数据。
- 进程结束后,销毁信号量与共享内存。
流程图如下
0x01 知识点学习
ftok函数
函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为
IPC(Inter-Process Communication 进程间通信) 键值,
也称IPC key键值:
进程间通信(IPC)
有两个东西可以标识一个IPC结构:标识符(ID)和键(key)。键值(ID)
ID是IPC结构的内部名,用来确保使用同一个通讯通道(比如说这个通讯通道就是消息队列)。内部即在进程内部使用,这样的标识方法是不能支持进程间通信的。标识符(key)
key就是IPC结构的外部名。当多个进程,针对同一个key调用get函数(msgget等),这些进程得到的ID其实是标识了同一个IPC结构。多个进程间就可以通过这个IPC结构通信。
ftok函数原型及说明如下:
所需头文件
include #include
函数说明
把从pathname导出的信息与id的低序8位组合成一个整数IPC键
函数原型
key_t ftok(const char *pathname, int proj_id)
函数传入值
pathname:指定的文件,此文件必须存在且可存取
proj_id:计划代号(project ID)
函数返回值
成功:返回key_t值(即IPC 键值)
出错:-1,错误原因存于error中
附加说明
key_t一般为32位的int型的重定义
通过ftok返回的是根据文件(pathname)信息和计划编号(proj_id)合成的IPC key键值,从而避免用户使用key值的冲突。proj_id值的意义让一个文件也能生成多个IPC key键值。ftok利用同一文件最多可得到IPC key键值0xff(即256)个,因为ftok只取proj_id值二进制的后8位,即16进制的后两位与文件信息合成IPC key键值
信号量
有名信号量与无名信号量
有名信号量和无名信号量的差异在于创建和销毁的形式上,但是其他工作一样。
无名信号量只能存在于内存中,要求使用信号量的进程必须能访问信号量所在的这一块内存,所以无名信号量只能应用在同一进程内的线程之间(共享进程的内存),或者不同进程中已经映射相同内存内容到它们的地址空间中的线程(即信号量所在内存被通信的进程共享)。意思是说无名信号量只能通过共享内存访问。
相反,有名信号量可以通过名字访问,因此可以被任何知道它们名字的进程中的线程使用。
无论是有名信号量还是无名信号量,都可以通过以下函数进行信号量值操作:
wait
wait 为信号量值减一操作,总共有三个函数,函数原型如下:
#include <semaphore.h>
//若sem小于0则线程阻塞于信号量sem,直到sem大于0,否则信号量值减1
int sem_wait(sem_t *sem);
//作用与第一个相同,只是此函数不阻塞线程,如果sem小于0,直接返回一个错误(错误设置为 EAGAIN )
int sem_trywait(sem_t *sem);
/*
作用也与第一个相同,第二个参数表示阻塞时间,如果 sem 小于 0 ,则会阻塞,参数指定阻塞时间长度。 abs_timeout 指向一个结构体,这个结构体由从 1970-01-01 00:00:00 +0000 (UTC) 开始的秒数和纳秒数构成
结构体如下
struct timespec {
time_t tv_sec;
long tv_nsec;
};
*/
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
post
post 为信号量值加一操作,函数原型如下:
#include <semaphore.h>
int sem_post(sem_t *sem);
有名信号量的创建
有名信号量创建可以调用 sem_open
函数,函数说明如下:
#include <semaphore.h>
//当使用已有的有名信号量时调用该函数,flag参数设为0
sem_t *sem_open(const char *name, int oflag);
//flag 参数应设为 O_CREAT ,如果有名信号量不存在,则会创建一个新的,如果存在,则会被使用并且不会再初始化。
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
有名信号量的销毁
可以使用 sem_unlink
函数销毁一个有名信号量。函数说明如下:
#include <semaphore.h>
//sem_unlink 函数会删除信号量的名字。如果没有打开的信号量引用,则该信号量会被销毁,否则,销毁会推迟到最后一个打开的引用关闭时才进行。
int sem_unlink(const char *name);
共享内存的使用
shmget()函数
int shmget(key_t key, size_t size, int shmflg);
第一个参数,与信号量的
semget
函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()
函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
第二个参数,size以字节为单位指定需要共享的内存容量
第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
shmat()函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。
它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
0x02 实验代码
sender.c
#include <stdio.h>
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <fcntl.h>
#define MSG_MEM_SIZE 1024
#define SEM_MEM_SIZE 64
#define SENDER '1'
#define RECEIVER '0'
#define SENDER_EXIT '3'
int main() {
sem_t *mutex;
sem_t *receiver_response;
sem_t *sender_write;
char *data;
key_t msg_key;
int msg_smd;
char buff[100];
// 通过文件获得key_t 值
msg_key = ftok("./sender.c",10);
if (msg_key < 0) {
printf("ftok error\n");
return 0;
}
// 创建并初始化信号量
// 互斥锁
mutex = sem_open("/mutex",O_CREATO_EXCL,S_IRUSRS_IWUSR,1);
// 等待接受响应
receiver_response = sem_open("/receiver_response",O_CREATO_EXCL,S_IRUSRS_IWUSR,0);
// 等待写者写入
sender_write = sem_open("/sender_write",O_CREATO_EXCL,S_IRUSRS_IWUSR,1);
if(mutex==SEM_FAILEDreceiver_response==SEM_FAILEDsender_write==SEM_FAILED){
mutex = sem_open("/mutex",O_RDWR);
receiver_response = sem_open("/receiver_response",O_RDWR);
sender_write = sem_open("/sender_write",O_RDWR);
if(mutex==SEM_FAILEDreceiver_response==SEM_FAILEDsender_write==SEM_FAILED)
printf("sem_open error\n");
}
// 创建共享内存 并返回该共享内存的标识
msg_smd = shmget(msg_key,MSG_MEM_SIZE,IPC_CREATS_IRUSRS_IWUSR);
if(msg_smd<0){
printf("shmget error\n");
return 0;
}
// 启用该共享内存 并返回该共享内存的首地址
data = (char*)shmat(msg_smd,0,0);
if(data<0){
printf("shmat error\n");
return 0;
}
printf("input 'exit' to exit\n");
while(1){
// 等待信号量
sem_wait(sender_write);
sem_wait(mutex);
printf("\033[36m\033[01mSender input:\033[0m");
// 获取用户输入(待发送的信息)
gets(buff);
if(!strcmp(buff,"exit")){
//
*data=SENDER_EXIT;
printf("message from receiver %s\n",data+1);
printf("\033[31m\033[01mexit!\033[0m\n");
sem_post(mutex);
sem_post(receiver_response);
break;
}
*data = SENDER;
strcpy(data+1,buff);
// 释放信号量
sem_post(mutex);
sem_post(receiver_response);
}
sem_unlink("/mutex");
sem_unlink("/receiver_response");
sem_unlink("/sender_write");
shmdt(data);
shmctl(msg_smd,IPC_RMID,0);
return 0;
}
receiver.c
#include<stdio.h>
#include<semaphore.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#define MSG_MEM_SIZE 1024
#define SEM_MEM_SIZE 64
#define SENDER '1'
#define RECEIVER '0'
#define SENDER_EXIT '3'
int main()
{
sem_t *mutex;
sem_t *receiver_response;
sem_t *sender_write;
char *data;
key_t msg_key;
int msg_smd;
msg_key = ftok("./sender.c",10);
if(msg_key<0){
printf("ftok error\n");
return 0;
}
mutex = sem_open("/mutex",O_CREATO_EXCL,S_IRUSRS_IWUSR,1);
receiver_response = sem_open("/receiver_response",O_CREATO_EXCL,S_IRUSRS_IWUSR,0);
sender_write = sem_open("/sender_write",O_CREATO_EXCL,S_IRUSRS_IWUSR,1);
if(mutex==SEM_FAILEDreceiver_response==SEM_FAILEDsender_write==SEM_FAILED){
mutex = sem_open("/mutex",O_RDWR);
receiver_response = sem_open("/receiver_response",O_RDWR);
sender_write = sem_open("/sender_write",O_RDWR);
if(mutex==SEM_FAILEDreceiver_response==SEM_FAILEDsender_write==SEM_FAILED)
printf("sem_open error\n");
}
msg_smd = shmget(msg_key,MSG_MEM_SIZE,IPC_CREATS_IRUSRS_IWUSR);
if(msg_smd<0){
printf("shmget error\n");
return 0;
}
data = (char*)shmat(msg_smd,0,0);
if(data<0){
printf("shmat error\n");
return 0;
}
while(1){
sem_wait(receiver_response);
sem_wait(mutex);
if(*data==SENDER_EXIT){
printf("\033[31m\033[01msender exit\033[0m\n");
strcpy(data+1,"over");
*data = RECEIVER;
sem_post(mutex);
sem_post(sender_write);
break;
}
// 如果共享内存 首字节为SENDER,则表明已发送,输出接受到的信息
if(*data == SENDER){
printf("\033[36m\033[01mmessage from sender\033[0m: %s\n",data+1);
strcpy(data+1,"over");
*data = RECEIVER;
}
sem_post(mutex);
sem_post(sender_write);
}
//解除信号量引用
sem_unlink("/mutex");
sem_unlink("/receiver_response");
sem_unlink("/sender_write");
//分离并删除共享存储区
shmdt(data);
shmctl(msg_smd,IPC_RMID,0);
return 0;
}