1、共享内存介绍
共享内存是三个IPC(Inter-Process Communication)机制中的一个。
它容许两个不相关的进程访问同一个逻辑内存。
共享内存是在两个正在进行的进程之间传递数据的一种很是有效的方式。
大多数的共享内存的实现,
都把由不一样进程之间共享的内存安排为同一段物理内存。
共享内存是由IPC为进程建立一个特殊的地址范围,
它将出如今该进程的地址空间中。
其余进程能够将同一段共享内存链接它们本身的地址空间中。
全部进程均可以访问共享内存中的地址,
就好像它们是由malloc分配的同样。
若是某个进程向共享内存写入了数据,
所作的改动将马上被能够访问同一段共享内存的任何其余进程看到。
2、共享内存的同步
共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。
可是它并未提供同步机制,
因此咱们一般须要用其余的机制来同步对共享内存的访问。
咱们一般是用共享内存来提供对大块内存区域的有效访问,
同时经过传递小消息来同步对该内存的访问。
在第一个进程结束对共享内存的写操做以前,
并没有自动的机制能够阻止第二个进程开始对它进行读取。
对共享内存访问的同步控制必须由程序员来负责。
下图显示了共享内存是如何共存的:
图中的箭头显示了每一个进程的逻辑地址空间到可用物理内存的映射关系。
3、共享内存使用的函数
- #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.
4、示例
典型的消费者-生产者程序,
第一个程序(消费者)将建立一个共享内存段,
而后把写到它里面的数据都显示出来。
第二个程序(生产者)将链接一个已有的共享内存段,
并容许咱们向其中输入数据。
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消息),
或生成信号
的方法来提供读写之间的更有效的同步机制。