管道、信号、信号量虽然满足了进程之间通讯的需要,但是还有一种没有满足,那就是进程之间需要共享大量的数据。就像一家人一样,他们彼此之间每天都在交流,但是对于家里面的一些共有物品,如电视,餐厅等等都是共享的,而我们所说的进程之间也是一样的,他们虽然互相独立存在,但是还有很多数据都是共享使用的,而进程之间的数据共享就是共享内存。
二、共享内存理论及其分析:
共享内存就是两个进程共同拥有同一片内存区域。这片区域中的内容,二者都可以访问。要使用共享内存进行通讯,一个进程首先创建一片内存空间专门作为通信使用,而其他的进程则将这片内存空间映射到自己的(虚拟)地址空间。这样就通过读写自己地址空间中对应的的共享内存区域时,就是在和其他的进程进行通信了。
通过这里的了解,是不是感觉共享内存和我们前面博客讲的管道很像呢,管道也是在一片内存区域上作为进程间通信的媒介,但是这里的共享内存和管道是不一样的,首先,使用共享内存时两个进程必须在同一台物理机器上,其次,共享内存的访问方式是随机的,而不是只能从一端写,另一端写,因此这也是共享内存的一大优点,所以共享内存传递的信息也比较多,随之而来大量信息的传递也使得传递时比较复杂。
如图所示,共享内存的图解:
那么共享内存与管道,信号量,信号等等通信有啥区别呢?
共享内存是最快的一种IPC通信方式,在各个进程都有指针指向开辟出来的内存区域,访问的时候当做进程中的一个内存控制直接操作,也就是说对于一个内存而言,共享内存就是进程的一部分,直接通过指针操作这片区域,少了内核的陷入过程;进程直接通过指针操作共享内存区域,少了用户态和内核态之间数据的拷贝。
区别图解:
但是共享内存也有它的缺点:由于共享内存是进程中通信最快的的,且信息量较大,也造成了它的管理复杂,且两个进程必须在同一台物理机器上才能进行通讯,另外一个缺点就是,安全性低,当两个进程共享一片内存区域进行通讯时,如果一个进程感染了病毒,很容易会传给(感染)另外一个进程。就像一家人一样,一个人感冒了,有些东西是公用的,到时候用的时候会很容易感染给别人,这是同样的道理。
再就是需要注意的是:使用全局变量在同一个进程的线程间实现通信不叫共享内存,它们是使用进程中的资源,线程只是进程内部的一个执行序列,后期将会进行分析说明。
共享内存使用时,是一个临界资源,所以进程之间使用必须做同步控制,而这里能更好的达到同步控制需要利用我们前面所说的信号量。
三、共享内存的使用函数:
头文件sys/types.h和sys/ipc.h被shm.h自动包含进程序。
shmget函数:
创建共享内存,获得一个共享内存标识符;
int shmget(key_t key, size_t size, int shmflg);
第一个参数是程序提供的一个键值,为共享内存命名,shmget函数返回一个共享内存标识符,后面的其他函数会使用。
第二个参数:size以字节为单位指定需要共享的内存容量。
第三个参数:如果共享内存存在则获取,不存在,使用IPC_CREAT创建,创建可以给设置权限标志 Onnn,这样允许创建共享内存的进程写入数据,设置其他的用户创建的进程只能以读取该共享内存。
返回值:创建成功,返回一个非负整数,即共享内存标识符,失败,则返回-1.
shmat函数:
将共享内存的段连接到进程的地址空间中。
void *shmat(int shm_id, const *shm_addr, int shmflg);
第一个参数shm_id是shmget函数的返回的共享内存标识符。
第二个参数shm_addr是:共享内存将要连接到当前进程中的地址位置,它通常是一个NULL,表示让系统来选择共享内存出现的地址。
第三个参数shmflg:是一组位标志,取值为:SHM_RND与shm_addr联合使用用来控制共享内存区域连续的地址 和SHM_RDONLY(它使得连续的内存只读)。
返回值:如果shmat调用成功,返回一个指向共享内存第一个字节的指针,如果失败,则返回-1.
shmdt函数:
断开连接;此函数只会断开连接,而不会删除共享内存。
int shmdt(void *addr);
addr参数是以前调用shmat函数的时的返回值,如果成功,shmdt将相关shmid_ds结构中的shm_nattch计数器值减1.
返回值:成功返回0,失败返回-1.
shmctl函数:
删除内核对象;
int shmctl(int shm_id, int command, 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是要采取的动作,取以下三个值:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET :如果进程有足够权限,把共享内存的当前关联值设置为shmid_ds结构中的值;
IPC_RMID:删除共享内存段;
第三个参数buf是一个指针,它指向包含共享内存模式和访问权限的结构。
返回值:成功时返回0;失败时返回-1;
基于intel的Linux系统上的存储区布局:
四、共享内存的测试题:
题目:A进程接收用户输入,B进程统计字符个数,遇到end结束!
代码如下:
信号量sem.c中信号量初始值为1;data.val = 1;,其他信号量封装函数见博客目录中的《进程通讯之信号量》
void sem_get()
{
//获取信号量集
semid = semget((key_t)1234,1,0666);
if (semid == -1)
{
//完成创建信号量集
semid = semget((key_t)1234, 1, 0666 | IPC_CREAT);
assert(semid != -1);
if (semid == -1)
{
printf("error\n");
exit(0);
}
//完成初始化
union sembuff data;
data.val = 1;
semctl(semid,0,SETVAL,data);
}
}
文件:shma.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/shm.h>
#include "sem.h"
void main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT | 0666);
assert(shmid != -1);
char *ptr = shmat(shmid,NULL,0);
assert(ptr != NULL);
sem_get();
while(1)
{
sem_p();
printf("please input: ");
fflush(stdout);
fgets(ptr,128,stdin);
if (strncmp(ptr,"end",3) == 0)
{
break;
}
ptr[strlen(ptr)-1] = 0;
sem_v();
}
}
文件:shmb.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/shm.h>
#include "sem.h"
void main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT | 0666);
assert(shmid != -1);
char *ptr = shmat(shmid,NULL,0);
assert(ptr != NULL);
sem_get();
while(1)
{
sem_p();
if (strncmp(ptr,"end",3) == 0)
{
break;
}
sleep(5);
printf("size : %d\n",strlen(ptr));
sem_v();
}
}
Linux终端下编译命令:
shma.c文件:gcc -o shma shma.c sem.c
shmb.c文件:gcc -o shmb shmb.c sem.c
结果:
A进程用户:
B进程计算:
测试目的:为了更好的使用共享内存进行进程间的通讯,以及对于进程间使用共享内存熟练使用,得到加强,并且融入信号量,进程对共享内存访问时,是同步控制的。