LV05-05-进程通信-04-共享内存
本文主要是进程通信——共享内存的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、共享内存简介
共享内存就是映射一段能被其它进程所访问的内存,这段共享内存由一个进程创建,但其它的多个进程都可以访问,使得多个进程可以访问同一块内存空间。共享内存是最快的IPC
方式,它是针对其它进程间通信方式运行效率低而专门设计的,它往往与其它通信机制,例如结合信号量来使用,以实现进程间的同步和通信。
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这一个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。
二、内存映射
1. 基本概念
内存映射就是使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read
,write
等函数。
内存映射文件,是由一个文件到一块内存的映射。 共享内存(SharedMemory
)实际就是文件映射的一种特殊情况。 使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O
操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
内存的映射有两种,一种是基于文件的映射(file-backed
),称为文件映射,我看到有些地方也称之为有后备文件的映射,另一种是对没有文件关联的内容进行映射,也就是匿名映射。
文件映射相对read()
和write()
的对比:
- 在大型文件的重复随机访问中,文件映射的优势突出,方便应用,代码简捷。
read()
和write()
操作需要两次传输:一次是在文件和内核高速缓存之间,另一次是内核高速缓存和用户空间缓存之间。使用文件映射就无需第二次传输了。- 如果是顺序的访问文件,执行
read()
和write()
时使用的缓冲区足够大,以至于能够避免执行大量的I/O
系统调用,但是mmap()
带来的系统提升是非常有限,或者可以说没有提升。 - 对于小数据量的
I/O
操作,mmap()
的开销比read()
和write()
还要大。
2. 共享内存创建
2.1 mmap()
在linux
下可以使用man mmap
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数将某个文件映射到物理内存中,也就是创建一个共享内存映射。
【函数参数】
addr
:void *
类型,指定要映射的内存地址,一般设置为NULL
让操作系统自动选择合适的内存地址,访问时可能需要强制类型转换。length
:size_t
类型,必须大于0
(否则就会报【Invalid argument
】),表示映射地址空间的字节数,它从被映射文件开头offset
个字节开始算起。prot
:int
类型,指定共享的内存映射区的访问权限。
点击查看 prot 可取的值
ROT_READ | 映射区域可读 |
PROT_WRITE | 映射区域可写 |
PROT_EXEC | 映射区域可被执行 |
PROT_NONE | 映射区域不可访问 |
可以多选,中间用|
连接。
flag
:int
类型,映射区的特性标志位。
点击查看 flag 常用取值及说明
MAP_FIXED | 如果参数 addr 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正,通常不建议使用此标志。 |
MAP_SHARED | 对应射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。 |
MAP_PRIVATE | 对应射区域的写入操作会产生一个映射文件的复制,即私人的"写入时复制" (copy on write),对此区域作的任何修改都不会被写回原来的文件。 |
MAP_ANONYMOUS | 建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。一般用于血缘关系进程间通信。 |
MAP_DENYWRITE | 只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 |
MAP_LOCKED | 将映射区域锁定住,这表示该区域不会被置换(swap)。 |
【说明】MAP_SHARED
, MAP_PRIVATE
两个必选其一,而 MAP_FIXED
则不推荐使用。
fd
:int
类型,表示代表要映射到内存的文件描述符,如果匿名映射则可以设置为-1
。offset
:off_t
类型,表示映射文件的偏移量,一般设置为0
表示从文件头部开始映射,需要注意的是offset
必须是系统分页大小的整数倍(Linux
系统中系统分页的大小一般是4KB
),如果不是的话,会报【Invalid argument
】。
【返回值】void *
类型,成功返回创建的映射区首地址,失败返回MAP_FAILED
,其实就是(void *) -1
,并设置errno
值。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】
(1)创建映射区的过程中,隐含着一次对映射文件的读操作,会将文件内容读取到映射区。
(2)映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭,在flag
为MAP_SHARED
时,即便是关闭了文件描述符,在修改共享内存数据的时候,部分数据也还是会写回物理磁盘上用于内存映射的文件的。
(3)使用文件映射时,用于映射的文件大小必须大于0
,当映射文件大小为0
时,若指定非0
大小创建映射区,访问映射地址会报【总线错误 (核心已转储)】,指定0
大小创建映射区,会报非法参数错误【Invalid argument
】。
(4)mmap
创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
(5)当flag
为MAP_SHARED
时,要求映射区的权限小于等于文件打开的权限(出于对映射区的保护),如果不满足的话(例如,open
时权限为O_RDONLY
,mmap
时PROT_READ|PROT_WRITE
)将会报非法参数【Invalid argument】错误。当flag
为MAP_PRIVATE
时候,mmap
中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(6)文件偏移量必须为0
或者4K
(系统分页大小)的整数倍(不是会报非法参数IInvalid argument
错误)。
2.2 使用实例
这里写一个测试的实例,用于测试上边几个的注意事项,会先使用后边的共享内存释放函数munmap
。
点击查看实例
1 | /* 头文件 */ |
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | read=99999999999999 |
第一个read
读取的是原来的用于文件映射的文件中的内容,第二个read
是对共享内存写入数据后读取的共享内存的内容,后边的都是对共享内存的读取。
3. 共享内存释放
3.1 munmap()
在linux
下可以使用man munmap
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数用来取消参数addr
所指的映射内存起始地址,也就是说通过该函数可以释放创建的内存映射。
【函数参数】
addr
:void *
类型,指定要释放的内存映射的内存地址,也就是mmap()
函数成功返回的映射区首地址,就是它的第一个参数。length
:size_t
类型,表示内存映射地址空间的字节数,也就是mmap()
函数的第二个参数。
【返回值】int
类型,成功返回0
,失败返回-1
,并设置errno
值。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
3.2 使用实例
暂无。
4. 同步映射区域到文件
4.1 msync()
在linux
下可以使用man msync
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于同步映射区域内数据与文件数据。
【函数参数】
addr
:void *
类型,指定需要同步的映射区域的内存地址。length
:size_t
类型,表示需要同步的映射地址空间的字节数。flag
:int
类型,表示同步的模式。
点击查看 flag 取值
MS_SYNC | 调用会阻塞,直到虚拟内存区域中所有被修改的分页被写入到真实文件为止。调用完后,虚拟内存与磁盘内容同步。 |
MS_ASYNC | 虚拟内存区域与内核高速缓存区同步,并对立即执行read()操作的其他进程可见,但会在之后的某个时刻将修改内容写入磁盘。 |
MS_INVALIDATE | 使映射数据的缓存副本失效,当内存区域中所有被修改过的分页被同步到文件中之后,内存区域中所有与底层文件不一致的分页会被标记为无效。下次引用这些分页时会重新从文件的相应位置复制相应的分页内容。 |
【返回值】int
类型,成功返回0
,失败返回-1
,并设置errno
值。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
4.2 使用实例
暂无。
5. mmap 映射原理与类型
5.1 虚拟内存映射过程
5.1.1 vm_area_struct 结构体
先来了解一个结构体vm_area_struct
,该结构体定义在include/linux/mm_types.h
文件中,可以使用如下命令来查找文件位置:
1 | locate include/linux/mm_types.h |
打开文件,找到这个接结构体,成员如下:
点击查看结构体成员定义
1 | /* |
分配的每个虚拟内存区域都由一个vm_area_struct
数据结构来管理,包括虚拟内存的起始和结束地址,以及内存的访问权限等。vm_area_struct
是在mmap
的时候创建的,vm_area_strcut
代表了一段连续的虚拟地址,这些虚拟地址相应地映射到一个后备文件或者一个匿名文件的虚拟页。一个vm_area_struct
映射到一组连续的页表项。页表项又指向物理内存page
,这样就把一个文件和物理内存页相映射。
5.1.2 虚拟页的三种状态
当拿到一个虚拟地址时,系统会根据已有的vm_area_struct
看这个虚拟地址是否属于某个vm_area_struct
,然后会有以下两种情况:
- 如果没有匹配到,就报段错误,访问了一个没有分配的虚拟地址。
- 如果匹配到了
vm_area_struct
,根据虚拟地址和页表的映射关系,找到对应的页表项PTE
,如果PTE
没有分配,就报一个缺页异常,去加载相应的文件数据到物理内存,如果PTE
分配,就去相应的物理页的偏移位置读取数据。
所以虚拟页会有以下三种状态:
未分配虚拟页 | 指的是没有使用mmap建立vm_area_struct,所以也就没有对应到具体的页表项 |
已分配虚拟页 | 未映射到物理页,指的是已经使用了mmap建立的vm_area_struct,可以映射到对应的页表项,但是页表项没有指向具体的物理页 |
已分配虚拟页 | 已映射到物理页,指的是已经使用了mmap建立的vm_area_struct,可以映射到对应的页表项,并且页表项指向具体的物理页 |
5.1.3 mmap 映射原理
- (一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
(1)进程在用户空间调用库函数mmap
。
(2)在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址。
(3)为此虚拟区分配一个vm_area_struct
结构,接着对这个结构的各个域进行初始化。
(4)将新建的虚拟区结构(vm_area_struct
)插入进程的虚拟地址区域链表或树中。
- (二)调用内核空间的系统调用函数
mmap
(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
(5)为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file
),每个文件结构体维护着和这个已打开文件相关各项信息。
(6) 通过该文件的文件结构体,链接到file_operations
模块,调用内核函数mmap
,其原型为:
1 | int mmap(struct file *filp, struct vm_area_struct *vma);/* 不同于用户空间库函数。 */ |
(7)内核mmap
函数通过虚拟文件系统inode
模块定位到文件磁盘物理地址。
(8)通过remap_pfn_range
函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到内存中。
- (三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
(9)进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
(10)缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
(11)调页过程先在交换缓存空间(swap cache
)中寻找需要访问的内存页,如果没有则调用nopage
函数把所缺的页从磁盘装入到内存中。
(12)之后进程即可对这片内存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
点击查看脏页面的概念
Linux
内核由于存在page cache
, 一般修改的文件数据并不会马上同步到磁盘,会缓存在内存的page cache
中,我们把这种和磁盘数据不一致的页称为脏页,脏页会在合适的时机同步到磁盘。为了回写page cache
中的脏页,需要标记页为脏(dirty
)。
【注意事项】
(1)修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()
来强制同步, 这样所写的内容就能立即保存到文件里了。
(2)前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至物理内存。真正的文件读取是当进程发起读或写操作时。
5.2 mmap 的四种类型
mmap
分为有后备文件的映射和匿名文件的映射,这两种映射又有私有映射和共享映射之分,所以mmap
可以创建4
种类型的映射:
后备文件的共享映射 | 多个进程的vm_area_struct指向同一个物理内存区域,一个进程对文件内容的修改,会被其他进程可见。对文件内容的修改会被写回到后备文件。 |
后备文件的私有映射 | 多个进程的vm_area_struct指向同一个物理内存区域,采用写时拷贝的方式,当一个进程对文件内容做修改,不会被其他进程看到。另外对文件内的修改也不会被写回到后备文件。当内存不够需要进行页回收时,私有映射的页被交换到交换区。一般用在加载共享代码库。 |
匿名文件的共享映射 | 内核创建一个初始都是0的物理内存区域,然后多个进程的vm_area_struct指向这个共享的物理内存区域,对该区域内容的修改对所有进程可见。匿名文件在页回收时被交换到交换区。 |
匿名文件的私有映射 | 内核创建一个初始都是0的物理内存区域,对该区域内容的修改只对创建者进程可见。匿名文件在页回收时被交换到交换区。malloc()底层是用了匿名文件的私有映射来分配大块内存。 |
6. 共享内存基本使用
内存映射可以用于进程的通信,在无血缘关系的进程间,或者是父子进程中,都可以使用内存映射的共享内存实现通信。
6.1 无血缘关系的进程
下边的例子是基于文件映射实现的没有血缘关系的两个进程之间的通信。
点击查看实例
1 | /* 头文件 */ |
1 | /* 头文件 */ |
1 | CC = gcc |
在终端执行以下命令编译程序:
1 | make # 编译程序,将会生成两个可执行程序 |
若之前用于文件映射的文件内容如下:
1 | 123456789 |
那么,两个终端显示的数据将是这样的:
1 | 运行 ./mmap_read 的终端 |
后边都是循环,运行 ./mmap_read
的终端显示的数据中间隔较大,这是因为文件映射到共享内存区后自带换行,这个符号还没有被覆盖,于是每一次都会被读出来,而每次循环打印的时候也会有一个换行,所以就会出现刚开始间隔大,后边间隔小的情况啦。
6.2 父子进程
我们前边学习mmap
函数的时候,它是可以建立匿名映射的,而匿名映射主要就是用于父子进程之间的通信。
点击查看实例
1 | /* 头文件 */ |
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
终端将会显示以下情况:
1 | read father val=1234567890 |
7. 文件映射边界问题
7.1 正常文件范围的内存映射
这种情况就是,我们要映射的区域大小并未超过文件大小,如上图,我们有一个9600 Bytes
的文件,我们现在要映射一个5000 Bytes
的共享内存区域,由于系统是按页来分配内存,在所用的系统中,一页是4KB
,所以实际申请可访问的内存空间会被扩展,创建共享内存时映射语句为:
1 | addr = mmap(NULL, 5000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); |
由于映射长度5000 Bytes
并非系统分页大小的整数倍,所以会按照分页大小,自动向上取整,所以实际映射大小为8092 Bytes
,即文件从起始位置有8092 Bytes
映射到虚拟内存中。
文件中未被映射的区域是无法通过虚拟内存进行访问的,如果强制向虚拟内存末尾后面访问,则会访问到一片未定义的地址,产生SIGSEGV
信号,默认该信号会终止进程,并打印core dump
。【注意事项】这一点其实我挺有疑问的,毕竟我尝试的时候似乎并没有什么特别的现象,代码也在正常跑,后便找到问题所在再补充吧。后来似乎明白了,原因在5.3
一节的图中,当我使用访问4
个页面外的内存时,出现了这个信号。
由于实际文件映射大小为8092 Bytes
,且为共享映射,所以映射后虚拟内存的8092 Bytes
可读可写,且修改的内容会同步到实际文件中。
7.2 超出文件范围的内存映射
这种情况就是,我们要映射的区域大小超过文件大小,如上图,我们有一个2200 Bytes
的文件,我们现在要映射一个6000 Bytes
的共享内存区域,由于系统是按页来分配内存,在所用的系统中,一页是4KB
,所以实际申请可访问的内存空间会被扩展,创建共享内存时映射语句为:
1 | addr = mmap(NULL, 6000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); |
映射长度为6000 Bytes
,自动向上对齐系统分页,所以实际映射大小为8092 Bytes
。但文件的实际大小为2000 Bytes
。所以长度为8092 Bytes
的虚拟内存映射区域被分为三段。第一段,即实际真实文件映射大小2000 Bytes
,该区域是可读、可写,且修改内容会同步到真实文件中;第二段,即第一个分页中除开首部2000 Bytes
的剩余部分,大小为2096 Bytes
,该部分可读可写,但由于没有文件映射与该内存相对应,所以写入内容无法同步到真实文件中;第三段,即第二个分页,该分页无文件与之对应,是为了映射长度的分页对齐而自动扩展的,如果访问该部分内容会产生SIGBUS
信号。
访问未被映射的区域会产生SIGSEGV
信号。【注意事项】这一点其实我挺有疑问的,毕竟我尝试的时候似乎并没有什么特别的现象,代码也在正常跑,后便找到问题所在再补充吧。后来似乎明白了,原因在5.3
一节的图中,当我使用访问4
个页面外的内存时,出现了这个信号。
7.3 有偏移的文件内存映射
当我看到这张图的时候,上边两个疑问似乎都解决了,真正映射到进程的虚拟地址空间的起始是创建共享内存页面数量的2
倍,区域之外的访问才会产生SIGSEGV
信号。
三、System V
共享内存
前边我们已经知道了System V IPC
包含三种通信机制,分别是消息队列、共享内存和信号量,这一部分就刚好是学习共享内存的笔记,就一起写在这里吧。
1. 创建或者打开共享内存
1.1 shmget()
在linux
下可以使用man 2 shmget
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数得到一个共享内存标识符ID
或创建一个共享内存对象。
【函数参数】
key
:key_t
类型,通过ftok
创建的key
(键)值或者是IPC_PRIVATE
(这样的话只能用于具有血缘关系的进程通信)。size
:size_t
类型,指定共享内存的大小,以字节为单位。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386
机器中一页的缺省大小PACE_SIZE=4096
字节)这样,新创建的共享内存的大小实际上是从size
这个参数调整而来的页面大小。即如果size
为1
至4096
,则实际申请到的共享内存大小为4K
(一页);4097
到8192
,则实际申请到的共享内存大小为8K
(两页),依此类推。shmflg
:int
类型,共享内存标志位,使用时需要与IPC
对象存取权限(如0666
)进行|
运算来确定读写权限。一般是需要创建共享内存时为IPC_CREAT|0666
,若不需要创建,直接打开时,使用0666
即可。
点击查看常用可取的值及含义
IPC_CREAT | 如果共享内存不存在,则创建一个共享内存,否则打开操作。 |
IPC_EXCL | 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 |
如果单独使用
IPC_CREAT
,shmget()
函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将
IPC_CREAT
和IPC_EXCL
标志一起使用,shmget()
将返回一个新建的共享内存的标识符;如果该共享内存已存在,就会返回错误。
【返回值】int
类型,成功返回共享内存的标识符,失败返回-1
,并设置errno
。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】共享内存的大小是有限制的,可以使用以下命令查看:
1 | ipcs -l |
1.2 使用实例
暂无。
2. 映射共享内存
2.1 shmat()
在linux
下可以使用man 2 shmat
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数将shmid
标识的共享内存映射到当前进程的虚拟地址空间,随后可像本地空间一样访问。
【函数参数】
shmid
:int
类型,共享内存IPC
对象的ID
。shmaddr
:void *
类型,表示映射的方式,若为NULL
,共享内存会被系统内核自动映射到一个合适的虚拟地址空间,建议使用NULL
;若不为NULL
,我自己没试过,可以参考函数帮助手册。shmflg
:int
类型,一些标志,一般是不指定的,写0
就可以啦,表示可读可写。其他的可以查看帮助手册,若设置为SHM_RDONLY
表示只读模式;不指定的话默认是读写权限;若设置为IPC_REMAP
,表示替换位于shmaddr
处的任意已有映射。
【返回值】void *
类型,成功返回映射的共享内存段的地址,失败返回(void *) -1
,并设置errno
来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】
(1)fork()
后创建的子进程继承已连接的共享内存地址。
(2)exec
执行时会执行其他进程,exec
执行的进程将会与已连接的共享内存地址自动脱离(detach
)。
(3)进程结束后,已连接的共享内存地址会自动脱离(detach
).
2.2 使用实例
暂无。
3.撤销共享内存
3.1 shmdt()
在linux
下可以使用man 2 shmdt
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数是用来撤销共享内存映射,将会禁止本进程访问此片共享内存。
【函数参数】
shmaddr
:void *
类型,表示要撤销的共享内存的首地址。
【返回值】int
类型,成功0
,失败返回-1
,并设置errno
来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】撤销后,内存地址不可再访问,但是共享内存依然存在,共享内存占用的内存空间并未被释放。
3.2 使用实例
暂无。
4. 共享内存控制
4.1 shmctl()
在linux
下可以使用man 2 shmctl
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数是用来控制创建的共享内存,一般是用来删除创建的共享内存。
【函数参数】
shmid
:int
类型,表示共享内存IPC
对象的ID
,由shmget
函数生成,不同的key
值对应不同的ID
值。cmd
:int
类型,表示要执行的操作。
点击查看常见 cmd 取值
IPC_RMID | 删除共享内存(释放内存空间)。公共的IPC选项(ipc.h中) |
IPC_SET | 设置ipc_perm参数,改变共享内存状态。公共的IPC选项(ipc.h中) |
IPC_STAT | 获取ipc_perm参数,获取共享内存状态。>公共的IPC选项(ipc.h中) |
SHM_LOCK | 锁定共享内存段,需要root权限。共享内存自己的选项(shm.h中) |
SHM_UNLOCK | 解锁共享内存段,需要root权限。共享内存自己的选项(shm.h中) |
buf
:struct shmid_ds
类型,保存或设置共享内存属性的地址。
【返回值】int
类型,成功0
,失败返回-1
,并设置errno
来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】none
4.2 使用实例
暂无。
5. 使用实例
5.1 使用实例1
这个例子是使用ftok()
函数创建key
,以实现不同进程的通信。
点击查看实例
1 | /* 头文件 */ |
1 | /* 头文件 */ |
1 | CC = gcc |
在终端执行以下命令编译程序:
1 | make # 生成可执行文件 |
然后,终端会有以下信息显示:
1 | 执行 ./shm_write 的终端 |
5.2 使用实例2
这个例子是,使用IPC_PRIVATE
创建IPC
对象的key
,这样的话只能是具有血缘关系的进程之间通信。
点击查看实例
1 |
|
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | This is child process! |
子进程先运行,父进程1s
后运行,这时候子进程已经运行完毕,当我们输入任意字符,按下enter
按键后,父进程读取共享内存数据。