您的位置:首页 > 运维架构 > Linux

【Linux】进程间通信之共享内存

2016-07-28 21:04 399 查看

为什么进程间需要通信?

1、数据传输:一个进程需要将它的数据发送给另一个进程。
2、资源共享:多个进程之间享受同样的资源
3、通知事件:一个进程需要向另一个或另一组进程发送消息,通知它们发生了某种事件。
4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够即使知道它的状态改变。

Linux进程间通信(IPC)由以下几部分发展而来:
1、UNIX进程间通信
2、基于System V进程间通信
3、POSIX进程间通信

IPC(Inter-Process Communication,进程间通信)对象的介绍
System V 的IPC对象有共享内存、消息队列、信号灯。



注意:在IPC的通信模式下,不管是使用消息队列还是共享内存,甚至是信号灯,每个IPC的对象都有唯一的名字,称为"键"(key)。通过"键",进程能够识别所用的对象。"键"与IPC对象的关系就如同文件名称于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够公用一个文件。而在
IPC的通讯模式下,通过"键"的使用也使得一个IPC对象能为多个进程所共用。

1、共享内存

共享内存就是允许两个不相关的进程访问同一逻辑内存(物理内存)。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存的地址,就好像它们用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。但共享内存并未提供同步机制。也就是说,在第一个进程对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所有我们通常需要用其他的机制来同步对共享内存的访问。
特点:
1)共享内存是进程间通信最快的方式。
2)共享内存没有保护机制,需要信号量控制。
3)共享内存的基本单位是页,即大小最小是4K,且是向上取整数页的。
4)共享内存的生命周期是随内核的。
每个进程都有地址空间,地址空间到物理内存利用:mmu+页表;物理内存中共享一段缓冲区,通过各自的页表+mmu映射到各自的虚拟地址,如下图所示:



2、共享内存函数及使用

两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

共享内存与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,它们的头文件为sys/shm.h中。
下面对此进行简单说明。
1)shmget函数

该函数用来创建共享内存,它的原型为:



-------key与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名。
-------size以字节为单位指定需要共享的内存容量
-------shmflg是权限标志。它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以
与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存 被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
调用成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。

2)shmat函数

第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:



-------shm_id是由shmget函数返回的共享内存标识。
-------shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
-------shmflg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

3)shmdt函数

该函数用于将共享内存从当前进程中分离。
注意:将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:



参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

4)shmctl函数

与信号量的semctl函数一样,用来控制共享内存,它的原型如下:



-------shm_id是shmget函数返回的共享内存标识符。
-------command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID:删除共享内存段
-------buf是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构包括以下成员:



shmdt和shmctl的区别:
IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。

shmdt(addr)使进程中的shmaddr指针无效化,不可以使用,但是保留空间。

shmctl(shmid,IPC_RMID,0) 删除共享内存,彻底不可用,释放空间。

创建两个进程,在A进程中创建一个共享内存,并向其写入数据,通过B进程从共享内存中读取数据。此程序是子进程进行写入,父进程进行了读取

shm.h如下:



shm.c如下:



test_shm.c如下:



运行结果如下:



删除共享内存



3、安全性

这个程序是不安全的,当有多个程序同时向共享内存中读写数据时,问题就会出现。可能你会认为,可以改变一下writer的使用方式,例如,只有当writer为0时进程才可以向共享内存写入数据,而当一个进程只有在writer不为0时才能对其进行读取,同时把writer进行加1操作,读取完后进行减1操作。这就有点像文件锁中的读写锁的功能。它似乎能行得通,但是这都不是原子操作,所以这种做法是行不能的。

4、共享内存的优缺点

1)优点:
a、函数的接口简单;
b、进程间通信中速度最快的。数据的共享使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。
C、无局限性,可用于任意两个进程间的通信。它也不像匿名管道那样要求通信的进程有一定的父子关系。
2)缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

4、mmap和shm共享内存的区别和联系

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。

mmap和shm的机制
mmap的机制:就是在磁盘上建立一个文件,每个进程存储器里面,单独开辟一个空间来进行映射。如果多进程的话,那么不会对实际的物理存储器(主存)消耗太大。

shm的机制:每个进程的共享内存都直接映射到实际物理存储器里面。
区别:

1、mmap保存到实际硬盘,实际存储并没有反映到主存上。
优点:储存量可以很大(多于主存);缺点:进程间读取和写入速度要比主存的要慢。

2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。
优点:进程间访问速度(读写)比磁盘要快;缺点:储存量不能非常大(多于主存)
在使用时,如果分配的存储量不大,那么使用shm;如果存储量大,那么使用mmap。

mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

mmap并不分配空间, 只是将文件映射到调用进程的地址空间里, 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后用msync()同步一下, 你所写的内容就保存到文件里了. 不过这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了。简单说就是把一个文件的内容在内存里面做一个映像,内存比磁盘快些。基本上它是把一个档案对应到你的virtual
memory 中的一段,并传回一个指针。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: