一、共享内存的含义
共享内存支持不同进程访问同一个逻辑内存,是不同进程间共享传递数据的一种有效方式。不同进程共享的内存一般具有相同的物理内存。进程可将这段共享的物理内存连接到自己的虚拟地址空间,通过操作虚拟地址从而操作这段共享物理内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
注意:共享内存只是共享内存,并未提供同步机制。在{BANNED}中国第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。因此,实际应用中通常需要配合同步机制对共享内存进行访问。
二、共享内存的操作函数
Linux提供了一组函数接口用于操作共享内存,他们声明在sys/shm.h中。
1、shmget()函数
该函数用来创建和获取共享内存,原型定义如下:
-
#include <sys/ipc.h>
-
#include <sys/shm.h>
-
-
int shmget(key_t key, size_t size, int shmflg);
{BANNED}中国第一个参数key,是一个非零正数,一般采取ftok()函数生成。
第二个参数size,以字节为单位指定了需要共享的内存大小。
第三个参数shmflg,指定了调用者操作共享内存的权限。权限描述方式与open函数的mode参数一样。若需要在共享内存不存在时,仍然能创建共享内存标识符,则需对shmflg或上IPC_CREAT标识,此时如果共享内存标识符已存在,则直接引用该共享内存标识符对象。若或上IPC_EXCL,标识符对象已存在时则返回失败。
返回值:成功返回一个有效的共享内存标识符,失败返回-1,errno会被设置成对应的错误。
与共享内存相关的数据结构如下:
-
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 */
-
...
-
};
其中ipc_perm的定义如下:
-
struct ipc_perm {
-
key_t __key; /* Key supplied to shmget(2) */
-
uid_t uid; /* Effective UID of owner */
-
gid_t gid; /* Effective GID of owner */
-
uid_t cuid; /* Effective UID of creator */
-
gid_t cgid; /* Effective GID of creator */
-
unsigned short mode; /* Permissions + SHM_DEST and
-
SHM_LOCKED flags */
-
unsigned short __seq; /* Sequence number */
-
};
当一个共享内存段被创建时,它的内存会被初始化为0。以上共享内存相关的数据结构中的域段会按照如下进行初始化:
1)设置shm_perm.cuid和shm_perm.uid为调用进程的有效user ID;
2)设置shm_perm.cgid和shm_perm.gid为调用进程的有效group ID;
3)设置shm_perm.mode为shmflg的地9bit的值,若共享内存段已存在,则权限会被修改。;
4)设置shm_segsz为size值;
5)设置shm_lpid、shm_nattch、shm_atime和shm_dtime为0;
6)设置shm_ctime为当前的时间。
2、shmat()函数
创建完共享内存后得到的共享内存标识符,还不能被任何进程访问。使用shmat()函数,则可将共享内存attach到调用进程的进程地址空间。原型如下:
-
#include <sys/types.h>
-
#include <sys/shm.h>
-
-
void *shmat(int shmid, const void *shmaddr, int shmflg);
{BANNED}中国第一个参数shm_id,是由shmget()函数返回的共享内存标识符。
第二个参数shm_addr,指定共享内存连接到当前进程中的地址,通常为空,表示让系统来选择共享内存地址。shm_addr不为空且shmflg指定了SHM_RND,这个attach发生在shmaddr的向下舍入SHMLBA的{BANNED}最佳近倍数的位置。若只是shmaddr不为空,则shmaddr必须时一个页对齐的地址。
第三个参数shmflag,表示一组flag,除了上述的SHM_RND外,还可以为以下三个值:
SHM_EXEC:共享内存段的内存允许被执行,调用者必须对共享内存段有执行权限。
SHM_RDONLY:attach的内存段是只读的,进程对共享内存段必须有可读权限。若该标识为指定,则调用进程对共享内存段必须有读写权限。
SHM_REMAP:此标志指定段的映射应该替换从shmadr开始并继续到段大小范围内的任何现有映射。
返回值:成功返回指向共享内存的{BANNED}中国第一个字节地址,失败返回-1。
2、shmdt()函数
该函数用于将共享内存从当前进程中分离。该函数不会删除共享内存段,而只是使得当前进程不可在访问该共享内存。原型如下:
点击(此处)折叠或打开
-
int shmdt(const void *shmaddr);
参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.
3、shmctl()函数
该函数与信号量的semctl()函数作用类似,用来控制共享内存,原型定义如下:
点击(此处)折叠或打开
-
#include <sys/ipc.h>
-
#include <sys/shm.h>
-
-
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
{BANNED}中国第一个参数shm_id,是shmget()函数返回的共享内存标识符;
第二个参数cmd,是表示待作用的操作,可取以下三个值:
-
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
-
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
第三个参数shmid_ds结构的buf指针,它执行共享内相关的数据结构。
三、共享内存实操
两个进程的共享内存结构如下:
-
#define BUF_SIZE 256
-
typedef struct {
-
int has_data;
-
char text[BUF_SIZE];
-
} share_data;
其中的has_data标记是否有数据,1表示有数据,0表示数据被读取。
写数据的进程代码
shm_write.c如下:
-
#include <sys/ipc.h>
-
#include <sys/shm.h>
-
#include <string.h>
-
#include <stdio.h>
-
#include <unistd.h>
-
-
#define BUF_SIZE 256
-
typedef struct {
-
int has_data;
-
char text[BUF_SIZE];
-
} share_data;
-
-
int main(int agrc, char **agrv)
-
{
-
char buf[BUF_SIZE];
-
-
key_t key = ftok("/dev/shm/myshm2", 0);
-
int shm_id = shmget(key, sizeof(share_data), IPC_CREAT | 0666);
-
share_data *shm = (share_data *)shmat(shm_id, NULL, 0);
-
if (shm == (void *)-1) {
-
fprintf(stderr, "shmat failed\n");
-
return -1;
-
}
-
-
printf("shm_addr=0x%p\n", shm);
-
while (1) {
-
while (shm->has_data == 1) {
-
sleep(1);
-
printf("wait for reading text...\n");
-
}
-
-
printf("write data to share memory:");
-
fgets(buf, BUF_SIZE, stdin);
-
strcpy(shm->text, buf);
-
shm->has_data = 1;
-
if (strncmp(shm->text, "end", 3) == 0)
-
break;
-
}
-
-
int rc = shmdt(shm);
-
if (rc == -1) {
-
fprintf(stderr, "shmdt failed\n");
-
return -1;
-
}
-
-
return 0;
-
}
读取共享数据的进程代码
shm_read.c如下:
-
#include <sys/ipc.h>
-
#include <sys/shm.h>
-
#include <string.h>
-
#include <stdio.h>
-
#include <unistd.h>
-
-
#define BUF_SIZE 256
-
typedef struct {
-
int has_data;
-
char text[BUF_SIZE];
-
} share_data;
-
-
int main(int agrc, char **agrv)
-
{
-
char buf[BUF_SIZE];
-
-
key_t key = ftok("/dev/shm/myshm2", 0);
-
int shm_id = shmget(key, sizeof(share_data), IPC_CREAT | 0666);
-
share_data *shm = (share_data *)shmat(shm_id, NULL, 0);
-
if (shm == (void *)-1) {
-
fprintf(stderr, "shmat failed\n");
-
return -1;
-
}
-
-
printf("shm_addr=0x%p\n", shm);
-
while (1) {
-
// 有数据待读取
-
if (shm->has_data == 1) {
-
printf("Received: %s", shm->text);
-
sleep(1);
-
-
// 读取完数据,请标记has_data
-
shm->has_data = 0;
-
-
// 输入了end,退出循环
-
if (strncmp(shm->text, "end", 3) == 0) {
-
break;
-
}
-
} else {
-
sleep(1);
-
}
-
}
-
-
int rc = shmdt(shm);
-
if (rc == -1) {
-
fprintf(stderr, "shmdt failed\n");
-
return -1;
-
}
-
-
// 删除共享内存
-
if (shmctl(shm_id, IPC_RMID, 0) == -1)
-
{
-
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
-
return -1;
-
}
-
-
return 0;
-
}
编译两个进程,如下:
gcc shm_read.c -o sr
gcc shm_write.c -o sw
分别运行写共享内存进程和读共享内存进程。写进程显示等待输入数据,数据还未被读取时,则等待读取完毕;读共享内存数据的进程,感知到有数据可读时,则获取数据内容,并清空数据标记,无数据可读时则一直等待数据写入。
测试结果如下:
注意:在实际应用时,has_data的修改必须保证原子性且需加上barrier操作确保读的数据的准确性。
阅读(2014) | 评论(0) | 转发(0) |