linux进程间的通信(C): 共享内存
一、共享内存介绍
共享内存是三个IPC(Inter-Process Communication)机制中的一个。
它允许两个不相关的进程访问同一个逻辑内存。
共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。
大多数的共享内存的实现,
都把由不同进程之间共享的内存安排为同一段物理内存。
共享内存是由IPC为进程创建一个特殊的地址范围,
它将出现在该进程的地址空间中。
其他进程可以将同一段共享内存连接它们自己的地址空间中。
所有进程都可以访问共享内存中的地址,
就好像它们是由malloc分配的一样。
如果某个进程向共享内存写入了数据,
所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
二、共享内存的同步
共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。
但是它并未提供同步机制,
所以我们通常需要用其他的机制来同步对共享内存的访问。
我们通常是用共享内存来提供对大块内存区域的有效访问,
同时通过传递小消息来同步对该内存的访问。
在第一个进程结束对共享内存的写操作之前,
并无自动的机制可以阻止第二个进程开始对它进行读取。
对共享内存访问的同步控制必须由程序员来负责。
下图显示了共享内存是如何共存的:
图中的箭头显示了每个进程的逻辑地址空间到可用物理内存的映射关系。
三、共享内存使用的函数
- #include <sys/shm.h>
- int shmget(key_t key, size_t size, int shmflg);
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- int shmdt(const void *shm_addr);
- int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
1. shmget函数
该函数用来创建共享内存:
- int shmget(key_t key, size_t size, int shmflg);
参数:
key : 和信号量一样,程序需要提供一个参数key,
它有效地为共享内存段命名。
有一个特殊的键值IPC_PRIVATE,
它用于创建一个只属于创建进程的共享内存,
通常不会用到。
size: 以字节为单位指定需要共享的内存容量。
shmflag: 包含9个比特的权限标志,
它们的作用与创建文件时使用的mode标志是一样。
由IPC_CREAT定义的一个特殊比特必须和权限标志按位或
才能创建一个新的共享内存段。
NOTE:
权限标志对共享内存非常有用,
因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,
同时其它用户创建的进程只能读取共享内存。
我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,
通过将数据放共享内存并设置它的权限,
就可以避免数据被其他用户修改。
返回值:
创建成功,则返回一个非负整数,即共享内存标识;
如果失败,则返回-1.
2. shmat函数
第一次创建共享内存段时,它不能被任何进程访问。
要想启动对该内存的访问,
必须将其连接到一个进程的地址空间。
这个工作由shmat函数完成:
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数:
shm_id : 由shmget返回的共享内存标识。
shm_add: 指定共享内存连接到当前进程中的地址位置。
它通常是一个空指针,
表示让系统来选择共享内存出现的地址。
shmflg : 是一组标志。
它的两个可能取值是:
SHM_RND, 和shm_add联合使用,
用来控制共享内存连接的地址。
SHM_RDONLY, 它使连接的内存只读
返回值:
如果调用成功, 返回一个指向共享内存第一个字节的指针;
如果失败,返回-1.
共享内存的读写权限由它的属主(共享内存的创建者),
它的访问权限和当前进程的属主决定。
共享内存的访问权限类似于文件的访问权限。
3. shmdt
将共享内存从当前进程中分离。
- int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指针。
成功时,返回0,
失败时,返回-1.
NOTE:
共享内存分离并未删除它,
只是使得该共享内存对当前进程不再可用。
4. shmctl
共享内存的控制函数
- int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds结构至少包含以下成员:
- struct shmid_ds {
- uid_t shm_perm.uid;
- uid_t shm_perm.gid;
- mode_t shm_perm.mode;
- }
参数:
shm_id : 是shmget返回的共享内存标识符。
command: 是要采取的动作,
它可以取3个值:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,
就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
buf : 是一个指针,
包含共享内存模式和访问权限的结构。
返回值:
成功时,返回0,
失败时,返回-1.
四、示例
典型的消费者-生产者程序,
第一个程序(消费者)将创建一个共享内存段,
然后把写到它里面的数据都显示出来。
第二个程序(生产者)将连接一个已有的共享内存段,
并允许我们向其中输入数据。
shm_com.h
- #define TEXT_SZ 2048
- struct shared_use_st {
- int written_by_you;
- char some_text[TEXT_SZ];
- };
当有数据写入这个结构中时,
我们用结构中的written_by_you标志来通知消费者。
需要传输的文本长度2K是随意定的。
shm1.c 消费者程序
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/shm.h>
- #include "shm_com.h"
- int main()
- {
- int running = 1;
- void *shared_memory = (void *)0;
- struct shared_use_st *shared_stuff;
- int shmid;
- srand((unsigned int)getpid());
- shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
- if (shmid == -1) {
- fprintf(stderr, "shmget failed\n");
- exit(EXIT_FAILURE);
- }
现在,让程序可以访问这个共享内存:
- shared_memory = shmat(shmid, (void *)0, 0);
- if (shared_memory == (void *)-1) {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("Memory attached at %X\n", (int)shared_memory);
程序的下一部分将shared_memory分配给shared_stuff,
然后它输出written_by_you中的文本。
循环将一直执行到在written_by_you中找到end字符串为止。
sleep调用强迫消费者程序在临界区域多待一会,
让生产者程序等待:
- shared_stuff = (struct shared_use_st *)shared_memory;
- shared_stuff->written_by_you = 0;
- while(running)
- {
- if (shared_stuff->written_by_you)
- {
- printf("You wrote: %s", shared_stuff->some_text);
- sleep( rand() % 4 ); /* make the other process wait for us ! */
- shared_stuff->written_by_you = 0;
- if (strncmp(shared_stuff->some_text, “end”, 3) == 0) {
- running = 0;
- }
- }
- }
最后,共享内存被分离,然后被删除:
- if (shmdt(shared_memory) == -1)
- {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- if (shmctl(shmid, IPC_RMID, 0) == -1)
- {
- fprintf(stderr, "shmctl(IPC_RMID) failed\n");
- exit(EXIT_FAILURE);
- }
- exit(EXIT_SUCCESS);
- }
shm2.c 生产者程序
通过它向消费者程序输入数据。
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/shm.h>
- #include "shm_com.h"
- int main()
- {
- int running = 1;
- void *shared_memory = (void *)0;
- struct shared_use_st *shared_stuff;
- char buffer[BUFSIZ];
- int shmid;
- shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
- if (shmid == -1)
- {
- fprintf(stderr, "shmget failed\n");
- exit(EXIT_FAILURE);
- }
- shared_memory = shmat(shmid, (void *)0, 0);
- if (shared_memory == (void *)-1)
- {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("Memory attached at %X\n", (int)shared_memory);
- shared_stuff = (struct shared_use_st *)shared_memory;
- while(running)
- {
- while(shared_stuff->written_by_you == 1)
- {
- sleep(1);
- printf("waiting for client...\n");
- }
- printf("Enter some text: ");
- fgets(buffer, BUFSIZ, stdin);
- strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
- shared_stuff->written_by_you = 1;
- if (strncmp(buffer, "end", 3) == 0) {
- running = 0;
- }
- }
- if (shmdt(shared_memory) == -1) {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- exit(EXIT_SUCCESS);
- }
运行程序,
将看到如下所示的样本输出:
- $ ./shm1 &
- [1] 294
- Memory attached at 40017000
- $ ./shm2
- Memory attached at 40017000
- Enter some text: hello
- You wrote: hello
- waiting for client...
- waiting for client...
- Enter some text:
- You wrote:
- waiting for client...
- waiting for client...
- waiting for client...
- Enter some text: end
- You wrote: end
- $
程序解析:
消费者程序
创建共享内存段,
然后将它连接到它自己的地址空间中,
并且,
我们在共享内存的开始处使用了一个结构shared_use_st.
该结构中有个标志written_by_you,
当共享内存中有数据写入时,就设置这个标志。
这个标志被设置时,
程序就从共享内存中读取文本,
将它打印出来,
然后清除这个标志,表示已经读完数据。
我们用一个特殊字符串end来退出循环。
接下来,
程序分离共享内存段并删除它。
生产者程序
使用相同的键值1234来取得并连接同一个共享内存段,
然后提示用户输入一些文本。
如果标志written_by_you被设置,
生产者就知道消费都进程还未读完上一次的数据,
因此就继续等待。
当其它进程清除了这个标志后,
生产者写入新的数据并设置这个标志。
它还使用字符串end来终止并分离共享内存段。
这里提供的同步标志written_by_you,
它是一个非常缺乏效率的忙等待(不停地循环)。
但在实际编程中,
应该使用信号量,
或通过传递消息(使用管道或IPC消息),
或生成信号
的方法来提供读写之间的更有效的同步机制。