Linux 共享内存通信:POSIX 共享内存与 mmap

Linux 共享内存通信:POSIX 共享内存与 mmap

课程:尚硅谷《嵌入式 Linux 应用层开发》第 3 章进程间通信

依据:2026-09-28 09:51 录音转写;课程 PDF 的 PDF 第 103---110 页。

范围说明:PDF 第 103 页上半部分是上一节 FIFO 的结尾,共享内存从第 103 页下半部分开始;第 110 页上半部分完成 /dev/shm 验证,下半部分已经进入消息队列,本文到共享内存结束为止。

1. 本节先建立正确模型

共享内存让多个进程把同一个内核共享内存对象映射到各自的虚拟地址空间。映射完成后,双方像访问普通内存一样读写同一批数据。
#mermaid-svg-ntAiSEWLZeEJdnod{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ntAiSEWLZeEJdnod .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ntAiSEWLZeEJdnod .error-icon{fill:#552222;}#mermaid-svg-ntAiSEWLZeEJdnod .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ntAiSEWLZeEJdnod .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ntAiSEWLZeEJdnod .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ntAiSEWLZeEJdnod .marker.cross{stroke:#333333;}#mermaid-svg-ntAiSEWLZeEJdnod svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ntAiSEWLZeEJdnod p{margin:0;}#mermaid-svg-ntAiSEWLZeEJdnod .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ntAiSEWLZeEJdnod .cluster-label text{fill:#333;}#mermaid-svg-ntAiSEWLZeEJdnod .cluster-label span{color:#333;}#mermaid-svg-ntAiSEWLZeEJdnod .cluster-label span p{background-color:transparent;}#mermaid-svg-ntAiSEWLZeEJdnod .label text,#mermaid-svg-ntAiSEWLZeEJdnod span{fill:#333;color:#333;}#mermaid-svg-ntAiSEWLZeEJdnod .node rect,#mermaid-svg-ntAiSEWLZeEJdnod .node circle,#mermaid-svg-ntAiSEWLZeEJdnod .node ellipse,#mermaid-svg-ntAiSEWLZeEJdnod .node polygon,#mermaid-svg-ntAiSEWLZeEJdnod .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ntAiSEWLZeEJdnod .rough-node .label text,#mermaid-svg-ntAiSEWLZeEJdnod .node .label text,#mermaid-svg-ntAiSEWLZeEJdnod .image-shape .label,#mermaid-svg-ntAiSEWLZeEJdnod .icon-shape .label{text-anchor:middle;}#mermaid-svg-ntAiSEWLZeEJdnod .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ntAiSEWLZeEJdnod .rough-node .label,#mermaid-svg-ntAiSEWLZeEJdnod .node .label,#mermaid-svg-ntAiSEWLZeEJdnod .image-shape .label,#mermaid-svg-ntAiSEWLZeEJdnod .icon-shape .label{text-align:center;}#mermaid-svg-ntAiSEWLZeEJdnod .node.clickable{cursor:pointer;}#mermaid-svg-ntAiSEWLZeEJdnod .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ntAiSEWLZeEJdnod .arrowheadPath{fill:#333333;}#mermaid-svg-ntAiSEWLZeEJdnod .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ntAiSEWLZeEJdnod .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ntAiSEWLZeEJdnod .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ntAiSEWLZeEJdnod .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ntAiSEWLZeEJdnod .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ntAiSEWLZeEJdnod .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ntAiSEWLZeEJdnod .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ntAiSEWLZeEJdnod .cluster text{fill:#333;}#mermaid-svg-ntAiSEWLZeEJdnod .cluster span{color:#333;}#mermaid-svg-ntAiSEWLZeEJdnod div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ntAiSEWLZeEJdnod .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ntAiSEWLZeEJdnod rect.text{fill:none;stroke-width:0;}#mermaid-svg-ntAiSEWLZeEJdnod .icon-shape,#mermaid-svg-ntAiSEWLZeEJdnod .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ntAiSEWLZeEJdnod .icon-shape p,#mermaid-svg-ntAiSEWLZeEJdnod .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ntAiSEWLZeEJdnod .icon-shape .label rect,#mermaid-svg-ntAiSEWLZeEJdnod .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ntAiSEWLZeEJdnod .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ntAiSEWLZeEJdnod .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ntAiSEWLZeEJdnod :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程 A 虚拟地址

地址可能是 0x1000...
同一组共享内存页
进程 B 虚拟地址

地址可能是 0x7000...

两个进程得到的虚拟地址通常不同,但这些地址背后可以对应同一组共享页,所以 A 写入后 B 能看到。

共享内存解决的是"数据放在哪里,双方怎样共同访问"。它不自动解决:

  • 谁先写、谁后读;
  • 写到一半时能不能读取;
  • 多个进程同时修改会不会冲突;
  • 一条数据是否已经准备完成。

这些顺序和互斥问题需要 waitpid、信号量、进程共享互斥锁或其他同步机制。

2. 与管道的区别

#mermaid-svg-8tzysDLrccJadJW6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8tzysDLrccJadJW6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8tzysDLrccJadJW6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8tzysDLrccJadJW6 .error-icon{fill:#552222;}#mermaid-svg-8tzysDLrccJadJW6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8tzysDLrccJadJW6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8tzysDLrccJadJW6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8tzysDLrccJadJW6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8tzysDLrccJadJW6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8tzysDLrccJadJW6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8tzysDLrccJadJW6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8tzysDLrccJadJW6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8tzysDLrccJadJW6 .marker.cross{stroke:#333333;}#mermaid-svg-8tzysDLrccJadJW6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8tzysDLrccJadJW6 p{margin:0;}#mermaid-svg-8tzysDLrccJadJW6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8tzysDLrccJadJW6 .cluster-label text{fill:#333;}#mermaid-svg-8tzysDLrccJadJW6 .cluster-label span{color:#333;}#mermaid-svg-8tzysDLrccJadJW6 .cluster-label span p{background-color:transparent;}#mermaid-svg-8tzysDLrccJadJW6 .label text,#mermaid-svg-8tzysDLrccJadJW6 span{fill:#333;color:#333;}#mermaid-svg-8tzysDLrccJadJW6 .node rect,#mermaid-svg-8tzysDLrccJadJW6 .node circle,#mermaid-svg-8tzysDLrccJadJW6 .node ellipse,#mermaid-svg-8tzysDLrccJadJW6 .node polygon,#mermaid-svg-8tzysDLrccJadJW6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8tzysDLrccJadJW6 .rough-node .label text,#mermaid-svg-8tzysDLrccJadJW6 .node .label text,#mermaid-svg-8tzysDLrccJadJW6 .image-shape .label,#mermaid-svg-8tzysDLrccJadJW6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8tzysDLrccJadJW6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8tzysDLrccJadJW6 .rough-node .label,#mermaid-svg-8tzysDLrccJadJW6 .node .label,#mermaid-svg-8tzysDLrccJadJW6 .image-shape .label,#mermaid-svg-8tzysDLrccJadJW6 .icon-shape .label{text-align:center;}#mermaid-svg-8tzysDLrccJadJW6 .node.clickable{cursor:pointer;}#mermaid-svg-8tzysDLrccJadJW6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8tzysDLrccJadJW6 .arrowheadPath{fill:#333333;}#mermaid-svg-8tzysDLrccJadJW6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8tzysDLrccJadJW6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8tzysDLrccJadJW6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8tzysDLrccJadJW6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8tzysDLrccJadJW6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8tzysDLrccJadJW6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8tzysDLrccJadJW6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8tzysDLrccJadJW6 .cluster text{fill:#333;}#mermaid-svg-8tzysDLrccJadJW6 .cluster span{color:#333;}#mermaid-svg-8tzysDLrccJadJW6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8tzysDLrccJadJW6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8tzysDLrccJadJW6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8tzysDLrccJadJW6 .icon-shape,#mermaid-svg-8tzysDLrccJadJW6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8tzysDLrccJadJW6 .icon-shape p,#mermaid-svg-8tzysDLrccJadJW6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8tzysDLrccJadJW6 .icon-shape .label rect,#mermaid-svg-8tzysDLrccJadJW6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8tzysDLrccJadJW6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8tzysDLrccJadJW6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8tzysDLrccJadJW6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 共享内存
直接读写映射地址
直接读写映射地址
进程 A
共享页
进程 B
管道
write
read
写进程
内核管道缓冲区
读进程

对比 管道 共享内存
数据模型 单向字节流 一块共同映射的内存
常用访问方式 read/write 指针、结构体、数组
消费数据 读出后数据离开管道 读取不会自动删除数据
消息边界 没有 没有,数据布局由程序定义
同步 空管道、满管道自带阻塞语义 必须由程序另外设计
适合场景 简单顺序传输 大量数据、频繁共享、低复制访问

共享内存容量仍受系统和 /dev/shm 挂载限制,不能理解为"没有大小限制"。

3. POSIX 共享内存的固定流程

录音反复强调的主线可以记成七步:
#mermaid-svg-EGym2b2WgFUuGoTK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EGym2b2WgFUuGoTK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EGym2b2WgFUuGoTK .error-icon{fill:#552222;}#mermaid-svg-EGym2b2WgFUuGoTK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EGym2b2WgFUuGoTK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EGym2b2WgFUuGoTK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EGym2b2WgFUuGoTK .marker.cross{stroke:#333333;}#mermaid-svg-EGym2b2WgFUuGoTK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EGym2b2WgFUuGoTK p{margin:0;}#mermaid-svg-EGym2b2WgFUuGoTK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EGym2b2WgFUuGoTK .cluster-label text{fill:#333;}#mermaid-svg-EGym2b2WgFUuGoTK .cluster-label span{color:#333;}#mermaid-svg-EGym2b2WgFUuGoTK .cluster-label span p{background-color:transparent;}#mermaid-svg-EGym2b2WgFUuGoTK .label text,#mermaid-svg-EGym2b2WgFUuGoTK span{fill:#333;color:#333;}#mermaid-svg-EGym2b2WgFUuGoTK .node rect,#mermaid-svg-EGym2b2WgFUuGoTK .node circle,#mermaid-svg-EGym2b2WgFUuGoTK .node ellipse,#mermaid-svg-EGym2b2WgFUuGoTK .node polygon,#mermaid-svg-EGym2b2WgFUuGoTK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EGym2b2WgFUuGoTK .rough-node .label text,#mermaid-svg-EGym2b2WgFUuGoTK .node .label text,#mermaid-svg-EGym2b2WgFUuGoTK .image-shape .label,#mermaid-svg-EGym2b2WgFUuGoTK .icon-shape .label{text-anchor:middle;}#mermaid-svg-EGym2b2WgFUuGoTK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EGym2b2WgFUuGoTK .rough-node .label,#mermaid-svg-EGym2b2WgFUuGoTK .node .label,#mermaid-svg-EGym2b2WgFUuGoTK .image-shape .label,#mermaid-svg-EGym2b2WgFUuGoTK .icon-shape .label{text-align:center;}#mermaid-svg-EGym2b2WgFUuGoTK .node.clickable{cursor:pointer;}#mermaid-svg-EGym2b2WgFUuGoTK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EGym2b2WgFUuGoTK .arrowheadPath{fill:#333333;}#mermaid-svg-EGym2b2WgFUuGoTK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EGym2b2WgFUuGoTK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EGym2b2WgFUuGoTK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EGym2b2WgFUuGoTK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EGym2b2WgFUuGoTK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EGym2b2WgFUuGoTK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EGym2b2WgFUuGoTK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EGym2b2WgFUuGoTK .cluster text{fill:#333;}#mermaid-svg-EGym2b2WgFUuGoTK .cluster span{color:#333;}#mermaid-svg-EGym2b2WgFUuGoTK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EGym2b2WgFUuGoTK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EGym2b2WgFUuGoTK rect.text{fill:none;stroke-width:0;}#mermaid-svg-EGym2b2WgFUuGoTK .icon-shape,#mermaid-svg-EGym2b2WgFUuGoTK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EGym2b2WgFUuGoTK .icon-shape p,#mermaid-svg-EGym2b2WgFUuGoTK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EGym2b2WgFUuGoTK .icon-shape .label rect,#mermaid-svg-EGym2b2WgFUuGoTK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EGym2b2WgFUuGoTK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EGym2b2WgFUuGoTK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EGym2b2WgFUuGoTK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1 shm_open

创建或打开对象
2 ftruncate

设置对象大小
3 mmap

映射进地址空间
4 close fd

映射仍有效
5 读写共享区

并进行同步
6 munmap

解除本进程映射
7 shm_unlink

删除名称

简记:

text 复制代码
打开对象 → 定大小 → 做映射 → 用内存 → 解映射 → 删名称

4. 第一步:shm_open 创建或打开对象(PDF 103---104)

c 复制代码
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);

4.1 名称规则

为了可移植,名称使用下面的形式:

text 复制代码
/letter_1234

规则:

  • 以一个 / 开头;
  • 后面至少有一个字符;
  • 后面的字符中不要再出现 /;
  • 它是共享内存对象名称,不是普通文件路径;
  • 两个独立进程只有使用同一个名称,才能打开同一个对象。

/letter_1234 不应写成 /dev/shm/letter_1234 传给 shm_open。Linux 通常会把对象显示在 /dev/shm/letter_1234,但 /dev/shm 是实现时的挂载位置。

4.2 常用打开标志

标志 作用
O_RDONLY 只读打开,只能建立只读映射
O_RDWR 读写打开,可建立读写映射
O_CREAT 对象不存在时创建
O_EXCL 与 O_CREAT 一起使用;对象已存在则失败
O_TRUNC 把已有对象截断为 0;容易破坏其他进程正在使用的数据

创建方建议使用:

c 复制代码
int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);

O_EXCL 可以防止误打开同名旧对象。0600 表示只有对象所有者可读写;最终权限还会受 umask 影响。

使用现有对象的一方通常使用:

c 复制代码
int fd = shm_open(name, O_RDWR, 0);

第三个参数只有创建新对象时才用于设置权限。

4.3 返回值

  • 成功:返回非负文件描述符;
  • 失败:返回 -1 并设置 errno。
c 复制代码
if (fd == -1) {
    perror("shm_open");
}

5. 第二步:ftruncate 设置大小(PDF 104---105)

新建的 POSIX 共享内存对象长度为 0,必须先设置大小:

c 复制代码
#include <sys/types.h>
#include <unistd.h>

int ftruncate(int fd, off_t length);

示例:

c 复制代码
const size_t SHM_SIZE = 1024;

if (ftruncate(fd, (off_t)SHM_SIZE) == -1) {
    perror("ftruncate");
}

扩大对象时,新增加的字节初始化为 0。缩小时,被截去的数据丢失。

5.1 为什么不能漏掉这一步

如果对象还是 0 字节就映射并访问,mmap 即使返回成功,访问超出对象实际范围的页面也可能触发 SIGBUS,导致进程异常终止。

5.2 truncate 与 ftruncate

函数 指定对象的方式 本节用途
truncate(path, size) 普通文件路径 不用于 shm_open 返回的对象名
ftruncate(fd, size) 已打开的文件描述符 设置共享内存对象大小

6. 第三步:mmap 建立映射(PDF 105---106)

c 复制代码
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot,
           int flags, int fd, off_t offset);

int munmap(void *addr, size_t length);

课程中的典型调用:

c 复制代码
char *share = mmap(NULL,
                   SHM_SIZE,
                   PROT_READ | PROT_WRITE,
                   MAP_SHARED,
                   fd,
                   0);

if (share == MAP_FAILED) {
    perror("mmap");
}

6.1 六个参数逐个理解

参数 本节填写 含义
addr NULL 让内核选择合适的虚拟地址
length SHM_SIZE 映射长度,不能为 0
prot `PROT_READ PROT_WRITE`
flags MAP_SHARED 修改对其他映射同一对象的进程可见
fd shm_open 返回值 被映射的共享内存对象
offset 0 从对象开头映射;非零时必须满足页对齐要求

6.2 MAP_SHARED 与 MAP_PRIVATE

  • MAP_SHARED:修改对其他共享映射可见,本节必须使用它。
  • MAP_PRIVATE:写时复制,修改进入本进程的私有副本,不适合用来交换修改结果。

6.3 判断失败不能写成 share == NULL

mmap 失败返回 MAP_FAILED:

c 复制代码
if (share == MAP_FAILED) {
    perror("mmap");
}

不能用 NULL 判断失败。

7. 为什么 mmap 后可以立即 close(fd)

c 复制代码
close(fd);

文件描述符用于建立映射。映射成功后,内核已经在进程地址空间中建立映射关系,关闭描述符不会让映射失效。

这三个动作作用不同:

操作 删除什么 映射还能否使用
close(fd) 本进程的文件描述符 可以
munmap(addr, size) 本进程的一段映射关系 不可以再访问该地址
shm_unlink(name) 共享内存对象的名称 已存在的映射仍可继续使用

8. 教材示例的可靠写法(PDF 106---107)

下面保留"子进程写、父进程读"的课堂目标,同时补全每一步的错误检查和清理:

c 复制代码
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define SHM_SIZE 1024

int main(void)
{
    char name[64];
    char *share = MAP_FAILED;
    int fd = -1;
    pid_t pid;

    if (snprintf(name, sizeof(name), "/letter_%ld", (long)getpid())
            >= (int)sizeof(name)) {
        fprintf(stderr, "共享内存名称过长\n");
        return EXIT_FAILURE;
    }

    fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
    if (fd == -1) {
        perror("shm_open");
        return EXIT_FAILURE;
    }

    if (ftruncate(fd, SHM_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        shm_unlink(name);
        return EXIT_FAILURE;
    }

    share = mmap(NULL, SHM_SIZE,
                 PROT_READ | PROT_WRITE,
                 MAP_SHARED, fd, 0);
    if (share == MAP_FAILED) {
        perror("mmap");
        close(fd);
        shm_unlink(name);
        return EXIT_FAILURE;
    }

    if (close(fd) == -1) {
        perror("close");
        munmap(share, SHM_SIZE);
        shm_unlink(name);
        return EXIT_FAILURE;
    }
    fd = -1;

    pid = fork();
    if (pid == -1) {
        perror("fork");
        munmap(share, SHM_SIZE);
        shm_unlink(name);
        return EXIT_FAILURE;
    }

    if (pid == 0) {
        int n = snprintf(share, SHM_SIZE,
                         "子进程 %ld 写入:你是个好人!",
                         (long)getpid());
        if (n < 0 || n >= SHM_SIZE) {
            munmap(share, SHM_SIZE);
            _exit(EXIT_FAILURE);
        }

        if (munmap(share, SHM_SIZE) == -1) {
            _exit(EXIT_FAILURE);
        }
        _exit(EXIT_SUCCESS);
    }

    int status;
    while (waitpid(pid, &status, 0) == -1) {
        if (errno != EINTR) {
            perror("waitpid");
            munmap(share, SHM_SIZE);
            shm_unlink(name);
            return EXIT_FAILURE;
        }
    }

    if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) {
        printf("父进程 %ld 读到:%s\n", (long)getpid(), share);
    } else {
        fprintf(stderr, "子进程未正常写入共享内存\n");
    }

    if (munmap(share, SHM_SIZE) == -1) {
        perror("munmap");
    }

    if (shm_unlink(name) == -1) {
        perror("shm_unlink");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

编译与运行:

bash 复制代码
gcc -Wall -Wextra -O2 shared_memory.c -o shared_memory
./shared_memory

较旧的 Linux 工具链可能需要在链接阶段添加 -lrt:

bash 复制代码
gcc -Wall -Wextra -O2 shared_memory.c -o shared_memory -lrt

8.1 为什么用 waitpid 替换 sleep(1)

教材用 sleep(1) 希望子进程先写完,但系统调度没有保证子进程一定在一秒内完成。waitpid 明确保证父进程在子进程退出后再读取。

这个示例只有一次"子写、父读",所以 waitpid 足够。若双方要反复交换数据,或本来就是两个无亲缘关系的程序,就应使用信号量等同步机制。

9. 真实通信必须设计数据结构和同步

不要只共享一片 char 数组。实际项目通常把结构体放入共享区:

c 复制代码
#define DATA_SIZE 1024

struct shared_block {
    size_t length;
    unsigned int sequence;
    int ready;
    char data[DATA_SIZE];
};

字段含义可以约定为:

  • length:有效数据长度;
  • sequence:数据版本或帧编号;
  • ready:是否已经写完;
  • data:真正的数据区。

但只增加 ready 仍不够。普通 int 不能可靠替代跨进程同步。常见方案:

  • POSIX 命名信号量:两个独立进程通过名称打开;
  • 放在共享内存中的 POSIX 无名信号量,初始化时设置 pshared != 0;
  • 放在共享内存中的互斥锁和条件变量,并把属性设为进程共享;
  • 结合 Socket、管道或 eventfd 发送"数据已准备好"的通知。

volatile 只能限制部分编译器优化,不能提供完整的进程同步、原子性和内存顺序保证。

10. 两个独立程序如何使用同一共享内存

课堂示例在 fork 前建立映射,子进程直接继承映射。对于两个分别启动的程序,流程不同:
使用方 POSIX 共享内存对象 创建方 使用方 POSIX 共享内存对象 创建方 #mermaid-svg-s1QDncn5VORI90oh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s1QDncn5VORI90oh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s1QDncn5VORI90oh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s1QDncn5VORI90oh .error-icon{fill:#552222;}#mermaid-svg-s1QDncn5VORI90oh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s1QDncn5VORI90oh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s1QDncn5VORI90oh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s1QDncn5VORI90oh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s1QDncn5VORI90oh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s1QDncn5VORI90oh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s1QDncn5VORI90oh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s1QDncn5VORI90oh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s1QDncn5VORI90oh .marker.cross{stroke:#333333;}#mermaid-svg-s1QDncn5VORI90oh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s1QDncn5VORI90oh p{margin:0;}#mermaid-svg-s1QDncn5VORI90oh .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1QDncn5VORI90oh text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-s1QDncn5VORI90oh .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s1QDncn5VORI90oh .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-s1QDncn5VORI90oh .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-s1QDncn5VORI90oh .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-s1QDncn5VORI90oh #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-s1QDncn5VORI90oh .sequenceNumber{fill:white;}#mermaid-svg-s1QDncn5VORI90oh #sequencenumber{fill:#333;}#mermaid-svg-s1QDncn5VORI90oh #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-s1QDncn5VORI90oh .messageText{fill:#333;stroke:none;}#mermaid-svg-s1QDncn5VORI90oh .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1QDncn5VORI90oh .labelText,#mermaid-svg-s1QDncn5VORI90oh .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-s1QDncn5VORI90oh .loopText,#mermaid-svg-s1QDncn5VORI90oh .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-s1QDncn5VORI90oh .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s1QDncn5VORI90oh .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-s1QDncn5VORI90oh .noteText,#mermaid-svg-s1QDncn5VORI90oh .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-s1QDncn5VORI90oh .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1QDncn5VORI90oh .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1QDncn5VORI90oh .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1QDncn5VORI90oh .actorPopupMenu{position:absolute;}#mermaid-svg-s1QDncn5VORI90oh .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-s1QDncn5VORI90oh .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1QDncn5VORI90oh .actor-man circle,#mermaid-svg-s1QDncn5VORI90oh line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-s1QDncn5VORI90oh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} shm_open 同名对象,O_CREAT|O_EXCL|O_RDWR ftruncate 设置统一大小 mmap MAP_SHARED shm_open 同一个名称,O_RDWR mmap 相同的数据结构大小 通过信号量等通知已准备 munmap munmap shm_unlink,由约定的所有者清理名称

双方必须事先约定:

  1. 完全相同的共享内存名称;
  2. 完全相同的数据结构和大小;
  3. 谁负责首次创建和初始化;
  4. 谁负责 shm_unlink;
  5. 数据什么时候有效;
  6. 多方同时访问时怎样互斥。

11.1 munmap

c 复制代码
if (munmap(share, SHM_SIZE) == -1) {
    perror("munmap");
}

它解除当前进程的一段映射。成功后不能再通过 share 访问这片区域。

c 复制代码
if (shm_unlink(name) == -1) {
    perror("shm_unlink");
}

它删除共享内存对象的名称,使新进程不能再通过原名称打开对象。已经打开或映射该对象的进程仍可继续使用,直到所有相关引用和映射都释放,内核才真正回收对象。

建议约定一个"所有者"执行一次 shm_unlink。教材代码让父子进程都可能执行它,其中一个成功后,另一个会遇到 ENOENT;这不利于清晰管理生命周期。

12. /dev/shm 与 tmpfs(PDF 108---110)

Linux 通常把 POSIX 共享内存对象放在挂载于 /dev/shm 的专用 tmpfs 中。

查看挂载容量:

bash 复制代码
df -h /dev/shm

查看对象:

bash 复制代码
ls -l /dev/shm

如果程序创建:

c 复制代码
shm_open("/letter_1234", O_CREAT | O_EXCL | O_RDWR, 0600);

在 Linux 上通常可看到:

text 复制代码
/dev/shm/letter_1234

需要正确理解:

  • tmpfs 主要使用内存,也可能在系统配置允许时使用交换空间;
  • /dev/shm 有容量限制,可以用 df -h 查看;
  • 共享内存对象具有内核持久性:进程退出并不会自动等于 shm_unlink;
  • 未清理的名称可能一直存在到手动删除或系统关闭;
  • 不要用无限循环 while (1); 长时间占满一个 CPU 核来等待观察,实验时可使用 pause()。

用于观察的改进写法:

c 复制代码
printf("共享内存名称:%s\n", name);
printf("按 Ctrl+C 结束,然后记得清理该对象。\n");
pause();

若实验中遗留对象,可在确认没有程序使用后清理:

bash 复制代码
rm /dev/shm/letter_1234

程序内部仍应优先使用 shm_unlink("/letter_1234") 管理生命周期。

13. 共享内存为什么快

共享内存建立映射后,进程通过普通内存读写指令访问共享页,不需要像管道那样每一批数据都调用 write 送入通道、再调用 read 取出。

不过,不能简单说"共享内存完全不经过内核":

  • shm_open、ftruncate、mmap、缺页处理和回收都由内核管理;
  • 首次访问页面可能触发缺页分配;
  • 同步原语发生竞争时可能进入内核等待;
  • 真正的性能还取决于数据大小、缓存一致性、锁竞争和访问方式。

共享内存的优势是映射建立后,双方可以反复直接访问共同页面,适合大块或高频数据。

14. 课程示例中的关键校正

  1. 共享内存不是"容量不受限制";它受 /dev/shm、内存、交换空间和系统配置限制。
  2. shm_open 只是创建或打开对象并返回描述符;真正让进程通过指针访问的是 mmap。
  3. 新对象初始长度为 0,应先 ftruncate,再 mmap 和访问。
  4. mmap 失败返回 MAP_FAILED,不是 NULL。
  5. MAP_SHARED 才用于让修改对其他进程可见;MAP_PRIVATE 是私有写时复制映射。
  6. close(fd) 不等于 munmap,munmap 也不等于 shm_unlink。
  7. sleep(1) 不是可靠同步;课堂父子示例应使用 waitpid,独立进程应使用信号量等同步机制。
  8. 共享内存中的数据不能无限使用 strcpy;必须检查目标区域容量。
  9. shm_unlink 接收对象名称,例如 /letter_1234,不接收文件描述符。
  10. /dev/shm 通常是 tmpfs,主要使用 RAM,也可能使用 swap,不能笼统说只存在物理内存。

15. 常见故障排查

现象 常见原因 检查方法
shm_open: File exists 使用 O_EXCL 时存在旧对象 ls -l /dev/shm,确认后清理旧对象
shm_open: No such file or directory 使用方名称错误,或创建方尚未创建 核对双方完整名称和启动顺序
mmap 返回 MAP_FAILED 长度、权限、描述符或资源有问题 检查 errno,核对 ftruncate 和打开模式
访问时出现 Bus error 对象尺寸不足、映射后被错误截短 检查 ftruncate 返回值和双方尺寸
读到空数据或旧数据 缺少同步,读方运行过早 使用信号量、条件变量或可靠通知
数据偶尔损坏 多进程并发写,缺少互斥和协议 增加进程共享锁、长度和状态字段
/dev/shm 越来越满 忘记 shm_unlink 约定所有者并统一清理

16. 与测试 A 和后续学习的关系

测试 A 的 TCP Server 不要求使用共享内存。学习本节的价值在于理解:

  • 多进程为什么不能直接共享普通全局变量;
  • 多进程模型的服务器如何共享状态或大块数据;
  • 共享数据与同步是两个独立问题;
  • 后续信号量为什么经常和共享内存一起出现;
  • 共享内存适合本机数据共享,Socket 适合有明确通信协议的本机或网络通信。

现阶段不需要背所有 mmap 标志。先熟练主流程、错误返回值和生命周期即可。

17. 本节最低掌握标准

学完后应能回答:

  1. 为什么普通全局变量不能让两个独立进程共享,而共享内存可以?
  2. POSIX 共享内存的七步流程是什么?
  3. 为什么 shm_open 后必须调用 ftruncate?
  4. MAP_SHARED 与 MAP_PRIVATE 有什么区别?
  5. 为什么 mmap 失败要与 MAP_FAILED 比较?
  6. 为什么 mmap 成功后可以关闭文件描述符?
  7. close、munmap、shm_unlink 各自清理什么?
  8. 共享内存为什么必须配合同步机制?
  9. /letter_1234 与 /dev/shm/letter_1234 是什么关系?

最低实践要求:

  • 独立完成一次"子进程写、父进程读"的共享内存实验;
  • 能在 /dev/shm 中观察对象,并在结束后正确清理;
  • 删除 waitpid 改用短暂 sleep,观察这种方式为什么不可靠,再恢复正确同步。

18. PDF 页码与录音时间索引

主题 PDF 页码 录音时间
与管道对比、共享内存整体流程 103---106 00:04---05:18
shm_open、名称和打开标志 103---104 00:45---01:52、05:48---07:59
ftruncate 设置大小 104---105 01:53---02:26、08:00---08:26
mmap 参数与 MAP_FAILED 105---106 02:27---05:18、08:27---09:30
关闭 fd、解除映射和删除对象 104、106---107 09:31---10:50、12:43---14:31
父子进程读写示例 106---107 10:51---14:31
tmpfs 与 /dev/shm 108---110 14:59---18:33
消息队列开头 110 下半页 不属于本节录音总结

进一步核对资料:

相关推荐
贵州山魈羡民1 小时前
【玩转手机】通过 Podroid 上的 Alpine 部署 1Panel,把微型服务器装进口袋
运维·服务器
小白的码BUG之路1 小时前
Docker -- 构建nacos镜像
运维·docker·容器
谷哥的小弟2 小时前
CentOS Stream浴火重生
linux·运维·centos
程序员-Benothing3 小时前
Shell输入输出详解:read、echo、printf、颜色输出
linux·运维·服务器
和裕3 小时前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
FPGA小徐4 小时前
从 NEMU 到 AbstractMachine:程序、运行时环境与 AM 完全解析
linux·运维·服务器
呉師傅5 小时前
得力P2500、M2500系列打印机硒鼓加粉及清零方法【纯享版】
运维·网络·windows·电脑
Sayai5 小时前
Elasticsearch 日志检索 DSL 实战:时间范围查询、字段去重、分钟级统计与最新日志获取
大数据·运维·elasticsearch·搜索引擎·日志分析
qetfw5 小时前
Debian Chrony 时间同步、RAID5 与 LUKS 磁盘加密配置
linux·debian