一.共享内存实现进程间通信的原理
共享内存实际是操作系统在实际物理内存中开辟的一段内存。
共享内存实现进程间通信,是操作系统在实际物理内存开辟一块空间,一个进程在自己的页表中,将该空间和进程地址空间上的共享区的一块地址空间形成映射关系。另外一进程在页表上,将同一块物理空间和该进程地址空间上的共享区的一块地址空间形成映射关系。
当一个进程往该空间写入内容时,另外一进程访问该空间,会得到写入的值,即实现了进程间的通信。
注意:要实现进程间通信就必须要两个进程之间看到相同的物理空间,系统开辟的共享内存就是两进程看到的同一块物理空间。
共享内存是进程间通信中最快的。
二,管理共享内存的数据结构
共享内存不仅仅可以实现两个进程之间的通信,还可以实现多进程之间的通信。要实现多进程之间的通信,就必须让操作系统将这些进程管理起来。
通过先描述在组织将通信进程之间的信息管理起来。
查看内核代码描述共享内存的数据结构如下:
/* Obsolete, used only for backwards compatibility and libc5 compiles */
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 */
};
描述共享内存的数据结构里保存了一个ipc_perm结构体,这个结构体保存了IPC(进程将通信)的关键信息。
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
__kernel_key_t key;//共享内存的唯一标识符
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode; //权限
unsigned short seq;
};
key是共享内存的唯一标识符。
三,实现共享内存所需要的系统调用函数
ftok函数:
作用:以路径名称和项目id为参数,运用算法算出一个key值,如果算出的key值与其他值冲突,就更改路径名称或者项目id。
返回值:ftok如果成功返回一个key值,如果失败返回-1。
shmget函数:
作用:创建一个共享内存
参数:
key:为共享内存的名字,一般是ftok的返回值。
size:共享内存的大小,以page为单位,大小为4096的整数倍。
shmflg:权限标志,常用两个IPC_CREAT和IPC_EXCL,一般后面还加一个权限,相当于文件的权限。
IPC_CREAT:创建一个共享内存返回,已存在打开返回
IPC_EXCL:配合着IPC_CREAT使用,共享内存已存在出错返回。
使用:IPC_CREAT | IPC_EXCL | 0666
返回值:
成功返回一个非负整数,即共享内存的标识码,失败返回-1。
为什么已经有一个key来标识共享内存,还需要一个返回值来标识共享内存?因为key是内核级别的,供内核标识,shmget返回值是用户级别的,供用户使用的。
我们发现当进程创建了一个共享内存,没有释放,进程结束后,共享内存还在,所以第二次执行程序,会报错(报错是因为IPC_EXCL)。
这里得出一个结论:IPC(进程将通信)资源生命周期不随进程,而是随内核的,不释放会一直占用,除非重启。所以,shmget创建的共享内存要释放掉,不然会内存泄漏。
可以用命令行来释放共享内存:ipcrm -m shmid(shmget返回值)
我们不能每次进行完共享内存通信之后再手动的将共享内存释放,可以用下面的函数来释放共享内存。
shmctl函数:
作用:用于控制共享内存
参数: shmid:共享内存的标识
cmd:以什么方式来控制共享内存。IPC_RMID是释放共享内存
buf:指向一个共享内存的数据结构 。struct shmid_ds
返回值:成功返回0,失败返回-1。
shmat函数:
作用:使创建的共享内存与调用该函数进程的进程地址空间参数关联。
参数:
shmid:共享内存的标识,shmget的返回值。
shmaddr:指定进程地址空间连接的地址。如果设置为null,默认让系统定要关联的地址。
shmflg: 权限,常见有两个SHM_RDONLY(只读)和SHM_REMAP(重新映射一个进程地址空间没这样shmaddr不能为空)。设为0,系统默认。
返回值:
返回映射到进程地址空间共享区的开始地址。
shmdt函数:
作用:删除共享内存与进程地址空间的映射关系,将页表映射关系删除,释放进程地址空间。
参数:
shmaddr:共享内存映射到进程地址空间的地址。shmat返回值。
返回值:
成功返回0,失败返回-1
四,进程间通信的代码实现
comm.h
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
comm.c
static int commShm(int size, int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if(_key < 0){
perror("ftok");
return -1;
}
int shmid = 0;
if( (shmid = shmget(_key, size, flags)) < 0){
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, NULL) < 0){
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
server.c
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i++<26){
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i<26){
addr[i] = 'A'+i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}