【4】共享内存
(1)概述
两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新,反之,进程B也可以即时看到进程A对共享内存中数据的更新。
共享内存是存在于内核级别的一种资源
在系统内核为一个进程分配内存地址时,通过分页机制可以让一个进程的物理地址不连续,同时也可以让一段内存同时分配给不同的进程。共享内存机制就是通过该原理来实现的,共享内存机制只是提供数据的传送,如何控制服务器端和客户端的读和写操作互斥,这就需要一些其他的辅助工具,例如信号量的概念。
共享内存可以说是Linux下最快速、最有效的进程间通信方式
- 因为进程可以直接读写内存,而不需要任何数据的拷贝
- 管道和消息队列等通信方式,需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件
- 进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的
- 共享内存的不足之处:由于多个进程对同一块内存区域具有访问的权限,各个进程间的同步问题尤为重要。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则将造成数据的混乱。同步控制问题可以通过下一节介绍的信号量来解决。
(2)共享内存相关操作
用于Linux进程通信共享内存。共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。
shmat函数原型
shmat(把共享内存区对象映射到调用进程的地址空间)
-
所需头文件
#include <sys/types.h> #include <sys/shm.h>
-
函数说明
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
-
函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg)
-
函数传入值
shmid 共享内存标识符 shmaddr 指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置 shmflg SHM_RDONLY:为只读模式,其他为读写模式
-
函数返回值
成功:附加好的共享内存地址 出错:-1,错误原因存于errno中
shmget()
-
创建或打开共享内存的函数
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key,int size,int flag);
返回值:成功返回共享内存ID,出错返回-1
key:创建或打开的共享内存的键值
size:共享内存区域大小,只在创建一个新的共享内存时生效
flag:调用函数的操作类型,也可用于设置共享内存的访问权限,两者通过逻辑或表示
shmdt函数原型
shmdt(断开共享内存连接)
-
所需头文件
#include <sys/types.h> #include <sys/shm.h>
-
函数说明
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
-
函数原型
int shmdt(const void *shmaddr)
-
函数传入值
shmaddr:连接的共享内存的起始地址
-
函数返回值
成功:0 出错:-1,错误原因存于error中
(3)使用shmget函数创建共享内存的例子,create_shm.c:
程序中在调用shmget函数时指定key参数值为IPC_PRIVATE,这个参数的意义是创建一个新的共享内存区,当创建成功后使用shell命令ipcs来显示目前系统下共享内存的状态。命令参数-m为只显示共享内存的状态。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFSZ 4096
int main(void)
{
int shm_id;/*共享内存标识符*/
shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);
/*创建共享内存*/
if(shm_id < 0)
{
printf("shmget failed!\n");
exit(1);/*shmget出错退出*/
}
printf("create a shared memory segment successfully:%d\n",shm_id);
system("ipcs -m");/*调用ipcs命令查看IPC*/
exit(0);
}
运行结果:
hyx@hyx-virtual-machine:~/test$ ./create_shm
create a shared memory segment successfully:2949133
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 65536 hyx 600 524288 2 目标
0x00000000 1114113 hyx 600 524288 2 目标
0x00000000 196610 hyx 600 524288 2 目标
0x00000000 393219 hyx 600 524288 2 目标
0x00000000 950276 hyx 600 524288 2 目标
0x00000000 589829 hyx 600 524288 2 目标
0x00000000 622598 hyx 600 16777216 2
0x00000000 655367 hyx 600 16777216 2 目标
0x00000000 753672 hyx 600 524288 2 目标
0x00000000 819209 hyx 600 2097152 2 目标
0x00000000 1146890 hyx 600 1048576 2 目标
0x00000000 1245195 hyx 600 524288 2 目标
0x00000000 1933324 hyx 600 67108864 2 目标
0x00000000 2949133 hyx 666 4096 0
执行ipcs,打印共享内存,信号量和消息队列的信息:
hyx@hyx-virtual-machine:~/test$ ipcs
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 65536 hyx 600 524288 2 目标
0x00000000 1114113 hyx 600 524288 2 目标
0x00000000 196610 hyx 600 524288 2 目标
0x00000000 393219 hyx 600 524288 2 目标
0x00000000 950276 hyx 600 524288 2 目标
0x00000000 589829 hyx 600 524288 2 目标
0x00000000 622598 hyx 600 16777216 2
0x00000000 655367 hyx 600 16777216 2 目标
0x00000000 753672 hyx 600 524288 2 目标
0x00000000 819209 hyx 600 2097152 2 目标
0x00000000 1146890 hyx 600 1048576 2 目标
0x00000000 1245195 hyx 600 524288 2 目标
0x00000000 1933324 hyx 600 67108864 2 目标
0x00000000 2949133 hyx 666 4096 0
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
执行ipcs -m,只打印共享内存段信息:
hyx@hyx-virtual-machine:~/test$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 65536 hyx 600 524288 2 目标
0x00000000 1114113 hyx 600 524288 2 目标
0x00000000 196610 hyx 600 524288 2 目标
0x00000000 393219 hyx 600 524288 2 目标
0x00000000 950276 hyx 600 524288 2 目标
0x00000000 589829 hyx 600 524288 2 目标
0x00000000 622598 hyx 600 16777216 2
0x00000000 655367 hyx 600 16777216 2 目标
0x00000000 753672 hyx 600 524288 2 目标
0x00000000 819209 hyx 600 2097152 2 目标
0x00000000 1146890 hyx 600 1048576 2 目标
0x00000000 1245195 hyx 600 524288 2 目标
0x00000000 1933324 hyx 600 67108864 2 目标
0x00000000 2949133 hyx 666 4096 0
hyx@hyx-virtual-machine:~/test$
(4)使用共享内存进行通信的实例
共享内存ID以命令行参数的形式传递给进程。
write_shm.c是写共享内存,即分10次向共享内存中写入people结构体的成员数据,(姓名和年龄)数据的值由for循环自动生成。程序的最后使用shmdt将共享内存段从当前的进程空间中脱离掉。
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc,char** argv)
{
int shm_id,i;
char temp;
people *p_map;
if(argc != 2)/*命令行参数错误*/
{
printf("USAGE:atshm < d=identifier>");/*打印帮助消息*/
exit(1);
}
shm_id = atoi(argv[1]);/*得到要引入的共享内存段,atoi将字符转换成整型*/
p_map = (people *)shmat(shm_id,NULL,0);/*shmat:把共享内存区对象映射到调用进程的地址空间*/
temp = 'a';
for(i = 0;i < 10;i++)
{
temp+=1;
memcpy((*(p_map+i)).name,&temp,1);
(*(p_map+i)).age = 20+i;
/*memcpy:c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标des所指的内存地址的起始位置中,void *memcpy(void *dest, const void *src, size_t n);*/
}
if(shmdt(p_map)==-1)
{
perror("detach error!\n");
}
return 0;
}
read_shm.c是读共享内存,即分10次从共享内存中读出people结构体的成员数据。程序的最后同样使用shmdt将共享内存段从当前的进程空间中脱离掉。
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc,char** argv)
{
int shm_id,i;
people *p_map;
if(argc != 2)
{
printf("USAGE:atshm <idenfifier>");
exit(1);
}
shm_id = atoi(argv[1]);/*得到要引入的共享内存段,atoi将字符转换成整型*/
p_map = (people*)shmat(shm_id,NULL,0);/*shmat:把共享内存区对象映射到调用进程的地址空间*/
for(i=0;i<10;i++)
{
printf("name:%s ",(*(p_map+i)).name);
printf("age %d\n",(*(p_map+i)).age);
}
if(shmdt(p_map)==-1)
{
perror("detach error!\n");
}
return 0;
}
运行结果:
hyx@hyx-virtual-machine:~/test$ ./write_shm 2949133
hyx@hyx-virtual-machine:~/test$ ./read_shm 2949133
name:b age 20
name:c age 21
name:d age 22
name:e age 23
name:f age 24
name:g age 25
name:h age 26
name:i age 27
name:j age 28
name:k age 29
hyx@hyx-virtual-machine:~/test$
【5】信号量
信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他的通信资源(如文件、外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,其只是一种外部资源的标识。
信号量,有时也被称为信号灯,是在多进程环境下使用的一种设施,它负责协调各个进程,以保证它们能够正确、合理的使用公共资源。信号量分为单值和多值两种,前者只能被一个进程获得,后者可以被若干个进程获得。
以停车场为例:假设停车场只有三个车位,一开始三个车位都为空。这时如果同时来了第五辆车,看门人允许其中三辆直接进入,然后放下车栏,剩下的车则必须在入口等待,在此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车栏,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
- 车位:公共资源
- 每辆车:一个进程
- 看门人:信号量的作用
- 抽象来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的进程/线程(车辆)都会将该整数减1(通过它是为了使用公共资源),当该整数值为零时,所有试图通过它的进程都处于等待状态。在信号量上我们定义两种操作:wait(等待)和releas(释放)。当一个进程调用wait操作时,它要么得到资源探后将信号量减1,要么一直等下去(指放入阻塞队列),直到信号量大于等于1时。release实际上是在信号量上执行加1操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了信号量守护的资源。