刚接触linux 不久,很多东西都是边学便用,最近再做一个项目用到共享内存,期间遇到很多问题,也顺便熟悉了Linux进程间通信、同步机制。
把他们都记录下来主要是方便后续查阅吧,免得边学边忘,呵呵
在使用过程中在查阅了很多资料、加上自己的理解,引用了很多朋友的文章,在此表示感谢~,其中可能也有些错误的地方欢迎大家帮助指出
1 进程间通信概述
进程通信的目的:数据传输、共享数据、资源共享、进程控制
Linux 进程间通信机制基本上是从Unix平台上的进程通信手段继承,加上后续的扩展而来,主要包括:UNIX IPC、System IPC、Posix IPC、Socket IPC
最初的UNIX IPC包括:管道、FIFO、信号。
System V IPC包括:System V消息队列、System V信号灯、System V共享内存、
POSIX IPC包括:posix消息队列、posix信号灯、posix共享内存。
由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准,所以看到很多人都推荐尽量使用Posix的
2 System V IPCs
System V进程间通信(IPC)包括3种机制:消息队列、信号量、共享内存。
消息队列和信号量均是内核空间的系统对象,经由它们的数据需要在内核和用户空间进行额外的数据拷贝。而共享内存和访问它的所有应用程序均同处于用户空间,共享内存是内核为了多个进程间交换数据专门预留出的一块内存区域,应用进程可以通过地址映射的方式直接读写内存,这样进程间的数据传递就不涉及内核,是最快的IPC方式。
2.1 System V共享内存实现原理
System V是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm(/dev/shm)中的一个文件(这是通过shmid_kernel结构联系起来的)。
共享内存是系统在内核预留的一块内存区,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。System V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构的同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。系统不会随时写数据到shm文件,只用当shmdt的时候才会一次性写入,由于shmfs是基于内存文件系统tmpfs的,所以数据一直存放在内存中,当系统重启后数据会全部丢失。
而 Posix 共享内存则是通过mmap映射普通文件系统中的文件实现的。
2.2 System V共享内存系统调用
int shmget(key_t key,int size,int shmflg);
功能:创建一个共享内存区,或者访问一个已存在的共享内存区
key:共享内存标识符,可以是自定义的整数,或者用字符串通过ftok() 生成(但每次生成的值可能不一样)
Size:size是要建立共享内存的长度,内核是以页为单位分配内存,当size参数的值不是系统内存页长的整数倍时,系统会分配给进程最小的可以满足size长的页数,但是最后一页 的剩余部分内存是不可用的。
intshmflg:shmflg主要和一些标志及权限有关
IPC_CREATE: 调用 shmget 时,系统将此值与其他共享内存区的 key 进行比较,如果存在相同的 key ,说明共享内存区已存在,此时返回该共享内存区的标识符,否则新建一个共享内存区并返回其标识符。
IPC_EXCL: 该宏必须和 IPC_CREATE 一起使用,否则没意义。当 shmflg 取 IPC_CREATE | IPC_EXCL 时,表示如果发现内存区已经存在则返回 -1,错误代码为 EEXIST 。
如果只是想获得已经创建的共享内存,可以将Size和shmflg同时设置为0。
返回值
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。
EINVAL 参数size小于SHMMIN或大于SHMMAX。
EEXIST 预建立key所致的共享内存,但已经存在。
EIDRM 参数key所致的共享内存已经删除。
ENOSPC 超过了系统允许建立的共享内存的最大值(SHMALL )。
ENOENT 参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。
EACCES 没有权限。
ENOMEM 核心内存不足。
void *shmat(int shm_id,void *shm_addr,int shmflg)
功能:将共享内存映射到当前进程的虚拟地址空间
shmid:共享存储的id
addr:一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整
flag:如前所述,一般为0
返回值:如果成功,返回共享存储段地址,出错返回-1
注:32位系统下最多允许11个进程同时映射同一个共享内存
int shmdt(void *shmaddr);
功能:取消共享内存映射
addr:共享存储段的地址,以前调用shmat时的返回值
shmdt 将使相关shmid_ds结构中的shm_nattch计数器值减
在使用中尽量在进程不再需要的时候再去dettach,否则需要每次attach会很耗时,并且会导致不断地写数据到文件,影响性能
int shmctl(int shm_id,int command,struct shmid_ds *buf)
功能:对共享存储段执行多种操作
shmid:共享存储段的id
cmd:一些命令,有:
IPC_STAT,
IPC_RMID,删除shm_id所指向的共享内存段,只有当shmid_ds结构的shm_nattch域为零时,才会真正执行删除命令,否则不会删除
SHM_LOCK,锁定共享内存段在内存,禁止交换的swap分区
SHM_UNLOCK,对共享内存段解锁
2.3 共享内存删除
System V 共享内存,当进程退出时会断开已经attach的共享内存段,但是不会主动删除,需要user删除,shmdt将使shmid_ds结构中的shm_nattch计数器值减1,Shmctl删除shm_id所指向的共享内存段时,只有当shmid_ds结构的shm_nattch域为零时,才会真正执行删除命令,否则只是被设置为IPC_PRIVATE状态,并被标记为”已被删除”,当shm_nattch为零时才会真正的删除,但这期间不允许新的进程attach。
2.4共享内存限制
Posix & System V 共享内存没有提供数据读写同步方式,需要user自己同步,通常是结合Semphore一起使用,但我测试发现 Semphore V 操作很耗时,大概要18微妙左右,有点太慢了~
2.5 共享内存参数配置
SHMMAX,单个共享内存段的最大值,linux系统默认为32M,最好设置的比SGA_MAX_SIZE大,否则会降低系统的效率
SHMMIN,单个共享内存段的最小值
SHMMNI,整个系统中共享内存段的个数,linux默认为4096个
SHMALL,系统任意时刻可以分配的所有共享内存段的总和的最大值(以页为单位),其值应不小于shmmax/page_size.缺省值就是2097152(8G)
查看:# more /proc/sys/kernel/shmmax
33554432
# more/proc/sys/kernel/shmall
# more /proc/sys/kernel/shmmni
修改:# echo 1073741824> /proc/sys/kernel/shmmax
系统Share Memory 查看:
# ipcs -m
删除:
# ipcrm –mshmid
如果当前没有进程attach则删除改共享内存段,否则key清为0x00
测试结果:
1. 共享内存映射到进程地址空间后,memory copy的时候1KB的数据大概需要2、3微妙,但是速度不是很稳定,copy 3次之后,就会有一次跳到20微秒,而一般memory copy很稳定在2、3微秒,不知道在做什么操作?
写数据加上进程同步平均需要22微秒,偶尔接近40微秒,UDP 发送大概需要40多微秒
2. 进程间通过信号量同步时,V操作太耗时影响性能,大概要18微秒左右,试了下Posix的貌似还要慢一点~