1、共享内存的数据结构
共享内存就是分配一块能被其他进程访问的内存。每个共享内存段在内核中维护着一个内部结构:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm shm_perm; 操作许可,里面包含共享内存的用户ID、组ID
size_t shm_segsz;共享内存段的大小,以单位为字节
time_t shm_atime; 最后一个进程访问共享内存的时间
time_t shm_dtime;最后一个进程离开共享内存的时间
time_t shm_ctime; 最后一次修改共享内存的时间
pid_t shm_cpid; 创建共享内存的进程ID
pid_t shm_lpid; 最后操作共享内存的进程ID
shmatt_t shm_nattch; 当前使用该共享内存段的进程数量
2、共享内存的创建
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
int shmget(key_t key, size_t size, int shmflg);
key:此值来源于ftok返回的IPC键值
size:大于0的整数:新建的共享内存大小,以字节为单位;只获取共享内存时指定为0
shmflg:
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
3、共享内存的操作
在使用共享内存前,必须通过shmat函数将其附加到进程的地址空间。进程与共享内存就建立了连接。
shmat调用成功就会返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了。
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存标识符,为shmget的返回值
shmflg:SHM_RDONLY:为只读模式,其他为读写模式
shmaddr:指定共享内存出现在进程内存地址的什么位置。
如果为空,则由内核选择一个空闲的内存区,如果非空,返回地址取决于调用者是否给shmflg参数指定了SHM_RND值,如果没有指定,
则共享内存区附加到shmaddr指定的地址;否则附加地址为shmaddr向下舍入一个共享内存低端边界地址后的地址(SHMLBA,一个常址)
通常将参数shmaddr设置为NULL
-----------------------------------------------------------------------
当进程结束使用共享内存区时,要通过函数shmdt断开与共享内存区的连接。
int shmdt(const void *shmaddr);
参数shmaddr为shmat函数的返回值。该函数调用成功后,返回0,否则返回-1
进程脱离共享内存区后,数据结构shmid_ds 中的shm_nattch就会减1。但是共享内存依然存在,只有
shm_nattch为0后,即没有任何进程再使用该共享内存,共享内存才会在内核中被删除。
一般来说,当一个进程终止时,它所附加的共享内存都会自动脱离。
fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)
4、共享内存的控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid:共享内去标识符
buf:为指向shmid_ds结构体的指针。
cmd:操作标志位。
IPC_STAT:读取共享内存的shmid_ds结构,并将其存储到buf指定的地址中
IPC_SET:设置共享内存的shmid_ds结构
IPC_RMID:从系统中删除由shmid标识的共享内存
5、共享内存的应用实例
通过读写者问题(不考虑优先级)来演示共享内存和信号量如何配合使用。这里的读者写者问题要求一个进程读共享内存的时候,其他进程不能写内存;当一个进程写共享内存的时候,其他进程不能读
内存。
程序首先定义了一个包含共用函数的头文件 sharemem.h
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
//
int createsem(const char *pathname, int proj_id, int members, int init_val)
{
key_t key;
int semid;
union semun sem_opts;
int index;
if ((key=ftok(pathname, proj_id)) == -1)
{
perror("ftok error:");
return -1;
}
if ((semid=semget(key, members, IPC_CREAT|0666)) == -1)
{
perror("semget error:");
return -1;
}
//初始化信号量
sem_opts.val = init_val;
for(index=0;index<members;index++)
{
semctl(semid, index, SETVAL, sem_opts);
}
return semid;
}
//
int opensem(const char *pathname, int proj_id)
{
key_t key;
int semid;
if ((key=ftok(pathname, proj_id)) == -1)
{
perror("ftok error:");
return -1;
}
if ((semid=semget(key, 0, IPC_CREAT|0660)) == -1)
{
perror("semget error:");
return -1;
}
return semid;
}
//
int sem_p(int semid, int index)
{
struct sembuf buf = {0, -1, IPC_NOWAIT};
buf.sem_num = index;
if (semop(semid, &buf, 1) == -1)
{
perror("semop error:");
return -1;
}
return 0;
}
//
int sem_v(int semid, int index)
{
struct sembuf buf = {0, 1, IPC_NOWAIT};
buf.sem_num = index;
if (semop(semid, &buf, 1) == -1)
{
perror("semop error:");
return -1;
}
return 0;
}
//
int sem_delete(int semid)
{
semctl(semid, 0, IPC_RMID);
}
int sem_wait(int semid, int index)
{
while(semctl(semid, index, GETVAL) <= 0)
{
sleep(1);
}
return 1;
}
int createshm(const char *pathname, int proj_id, size_t size)
{
key_t key;
int semid;
if ((key=ftok(pathname, proj_id)) == -1)
{
perror("ftok error:");
return -1;
}
if ((semid=shmget(key, size, IPC_CREAT|0660)) == -1)
{
perror("shmget error:");
return -1;
}
return semid;
}
writer和reader程序,两个程序在进入共享内存之前,首先都检查信号量的值是否为1(相当于是否能进入共享内存区),如果不为1,调用sleep进入休眠状态,直到信号量的值变为1.
write程序:
#include <sharemem.h>
#include <string.h>
#define SHM_SIZE 256
int main()
{
int shmid;
int semid;
char *shmaddr;
char write_str[SHM_SIZE];
shmid = createshm(".", 'm', SHM_SIZE);
shmaddr = (char*)shmat(shmid, (char*)0, 0);
semid = createsem(".", 's', 1, 1);
while(1)
{
sem_wait(semid, 0);
sem_p(semid, 0);
printf("writer: ");
fgets(write_str, SHM_SIZE, stdin);
int len = strlen(write_str) - 1;
write_str[len] = '\0';
strcpy(shmaddr, write_str);
sleep(10);
sem_v(semid, 0);
sleep(10);
}
}
reader程序:
#include <sharemem.h>
#define SHM_SIZE 256
int main()
{
int shmid;
int semid;
char *shmaddr;
char write_str[SHM_SIZE];
shmid = createshm(".", 'm', SHM_SIZE);
shmaddr = (char*)shmat(shmid, (char*)0, 0);
semid = opensem(".", 's');
while(1)
{
printf("reader: ");
sem_wait(semid, 0);
sem_p(semid, 0);
printf("%s\n", shmaddr);
sleep(10);
sem_v(semid, 0);
sleep(10);
}
}