一、共享内存IPC原理
共享内存进程间通信机制主要用于实现进程间大量数据的传输,共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限、大小和最近访问时间。
数据结构定义如下:
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
两个进程在使用此共享内存空间之前,需要在进程地址空间与共享内存空间之间建立联系,即将共享内存空间挂载到进程中。并且在使用共享内存进行数据的存取时,需要对空间进行同步操作,这个可以使用信号量机制完成。
系统对共享内存做了以下限制:
#define SHMMIN 1 /* min shared seg size (bytes) */
#define SHMMNI 4096 /* max num of segs system wide */
#define SHMMAX (ULONG_MAX - (1UL << 24)) /* max shared seg size (bytes) */
#define SHMALL (ULONG_MAX - (1UL << 24)) /* max shm system wide (pages) */
#define SHMSEG SHMMNI /* max shared segs per process */
二、共享内存的管理
1、创建共享内存
函数原型:
int shmget(key_t key, size_t size, int shmflg)
函数传入值:
key
0(IPC_PRIVATE):会建立新共享内存对象
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值
size
大于0的整数:新建的共享内存大小,以字节为单位
0:只获取共享内存时指定为0
shmflg
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错
函数返回值:
成功:返回共享内存的标识符
出错:-1,错误原因存于error中
2、共享内存控制
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
函数传入值:
shmid
共享内存标识符
cmd
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
buf
共享内存管理结构体。具体说明参见共享内存内核结构定义部分
函数返回值
成功:0
出错:-1,错误原因存于error中
3、映射共享内存对象
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg)
函数传入值:
shmid
共享内存标识符
shmaddr
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg
SHM_RDONLY:为只读模式,其他为读写模式
函数返回值
成功:附加好的共享内存地址
出错:-1,错误原因存于errno中
4、分离共享内存对象
函数原型:
int shmdt(const void *shmaddr)
函数传入值:
shmaddr:连接的共享内存的起始地址
函数返回值:
成功:0
出错:-1,错误原因存于error中
三、使用共享内存进行进程间通信的实例
1、发送端进程
/*
*使用共享内存进行进程间的通信,并运用信号量机制实现同步
*此为信息的发送者
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
int main(int argc , char* argv[])
{
int running = 1 ;
int shid ;
int semid ;
int value ;
void* sharem = NULL ;
struct sembuf sem_b ;
sem_b.sem_num = 0 ;
sem_b.sem_flg = SEM_UNDO ;
if((semid = semget((key_t)123456 , 1 , 0600 | IPC_CREAT)) == -1) //创建信号量(或读ID)
{
perror("semget") ;
exit(EXIT_FAILURE) ;
}
if(semctl(semid , 0 , SETVAL , 0) == -1) //设置初始值为0
{
printf("sem init error\n") ;
if(semctl(semid , 0 , IPC_RMID , 0) != -1) //设置失败则进行删除
{
perror("semctl") ;
exit(EXIT_FAILURE) ;
}
exit(EXIT_FAILURE) ;
}
shid = shmget((key_t)654321 , (size_t)2048 , 0600 | IPC_CREAT) ; //创建共享内存(或读ID)
if(shid == -1)
{
perror("shmget") ;
exit(EXIT_FAILURE) ;
}
sharem = shmat(shid , NULL , 0) ; //挂载共享内存到当前进程
if(sharem == NULL)
{
perror("shmat") ;
exit(EXIT_FAILURE) ;
}
while(running)
{
if(value = semctl(semid , 0 , GETVAL) == 0) //读取值为0则可以写
{
printf("write data operate\n") ;
printf("please input something :") ;
scanf("%s" , sharem) ;
sem_b.sem_op = 1 ;
if(semop(semid , &sem_b , 1) == -1) //实行信号量的自加操作,允许读
{
fprintf(stderr , "semaphore_p failure\n") ;
exit(EXIT_FAILURE) ;
}
}
if(strcmp(sharem , "end") == 0)
{
running-- ;
}
}
shmdt(sharem) ; //解挂
return 0 ;
}
2、接收端进程
/*
*使用共享内存进行进程间的通信,并运用信号量机制实现同步
*此为信息的接收者
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
int main(int argc , char* argv[])
{
int running = 1 ;
int shid ;
int semid ;
int value ;
void* sharem = NULL ;
struct sembuf sem_b ;
sem_b.sem_num = 0 ;
sem_b.sem_flg = SEM_UNDO ;
if((semid = semget((key_t)123456 , 1 , 0600 | IPC_CREAT)) == -1) //创建信号量(或读ID)
{
perror("semget") ;
exit(EXIT_FAILURE) ;
}
shid = shmget((key_t)654321 , (size_t)2048 , 0600 | IPC_CREAT) ; //创建共享内存(或读ID)
if(shid == -1)
{
perror("shmget") ;
exit(EXIT_FAILURE) ;
}
sharem = shmat(shid , NULL , 0) ; //挂载共享内存到当前进程
if(sharem == NULL)
{
perror("shmat") ;
exit(EXIT_FAILURE) ;
}
while(running)
{
if(value = semctl(semid , 0 , GETVAL) == 1) //读取值为1则可以写
{
printf("read data operate\n") ;
sem_b.sem_op = -1 ;
if(semop(semid , &sem_b , 1) == -1) //实行信号量的自减操作,允许写
{
fprintf(stderr , "semaphore_p failure\n") ;
exit(EXIT_FAILURE) ;
}
printf("%s\n" , sharem) ;
}
if(strcmp(sharem , "end") == 0)
{
running-- ;
}
}
shmdt(sharem) ; //解挂
if(shmctl(shid , IPC_RMID , 0) != 0) //删除共享内存
{
perror("shmctl") ;
exit(EXIT_FAILURE) ;
}
if(semctl(semid , IPC_RMID , 0) != 0) //删除信号量
{
perror("semctl") ;
exit(EXIT_FAILURE) ;
}
return 0 ;
}
3、我们可以查看运行结果
发送端:
接收端:
可以看到顺利通信(: