Linux 全链路多媒体数据流完整分析:设备发现 → 设计模式 → 同步机制 → 异常处理
·
第一部分 设备发现与信息打开
-
打开所有设备相关信息 → 设备节点发现、Media Controller 拓扑、V4L2/ALSA 设备枚举
-
不同场景使用相应软件设计模式 → 策略模式、管道模式、观察者模式、工厂模式
-
使用技术同步数据,解决拥塞、花屏、丢失 → Ring Buffer、时间戳同步、丢帧策略、FEC
-
多种输入源 → UVC、AHD、网络视频流,统一设备号管理
一、设备发现与信息打开:完整的设备枚举框架
1.1 Linux 多媒体设备节点体系
Linux 多媒体设备节点树 ├── Video 设备 (/dev/video*) │ ├── /dev/video0 ──┬── UVC 摄像头 (USB) │ │ └── 对应 /sys/class/video4linux/video0/ │ ├── /dev/video1 ──┬── AHD 采集卡 (通过 V4L2) │ │ └── 对应 /sys/class/video4linux/video1/ │ ├── /dev/video2 ──┬── 内置 ISP (RK 平台) │ │ └── 对应 /sys/class/video4linux/video2/ │ └── /dev/video3 ──┬── 视频解码器输出 │ ├── Audio 设备 (/dev/snd/) │ ├── /dev/snd/pcmC0D0c ──┬── USB 麦克风 (UAC) │ │ └── card0: 设备0,capture方向 │ ├── /dev/snd/pcmC0D0p ──┬── 扬声器输出 │ ├── /dev/snd/pcmC1D0c ──┬── HDMI 音频输入 │ └── /dev/snd/controlC0 ──┬── 混音器控制 │ ├── Media Controller 设备 (/dev/media*) │ ├── /dev/media0 ──┬── 完整的媒体拓扑(ISP + CSI + 传感器) │ └── /dev/media1 ──┬── 另一个媒体设备 │ └── DMA-BUF 共享句柄 (通过 fd 传递,无固定节点)
1.2 V4L2 设备枚举核心代码
// ========== 用户态设备发现 ==========
// 程序:v4l2-ctl --list-devices
// 实现原理:遍历 /dev/video* 并调用 VIDIOC_QUERYCAP
#include <dirent.h>
#include <linux/videodev2.h>
#include <fcntl.h>
// 枚举所有 V4L2 设备
void enumerate_v4l2_devices(void) {
DIR *dir = opendir("/dev");
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "video", 5) == 0) {
char path[256];
snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
int fd = open(path, O_RDWR);
if (fd < 0) continue;
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
printf("Device: %s\n", path);
printf(" Driver: %s\n", cap.driver);
printf(" Card: %s\n", cap.card);
printf(" Bus info: %s\n", cap.bus_info);
// 判断设备类型
if (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)
printf(" Type: Video Capture\n");
if (cap.device_caps & V4L2_CAP_VIDEO_OUTPUT)
printf(" Type: Video Output\n");
if (cap.device_caps & V4L2_CAP_M2M)
printf(" Type: Memory-to-Memory\n");
}
close(fd);
}
}
closedir(dir);
}
1.3 Media Controller:发现设备内部拓扑
Media Controller 是 Linux 内核提供的框架,用于发现和配置多媒体设备的内部拓扑结构。对于复杂的 SoC 平台(如 RK 的 ISP 管道),这是必须使用的接口。
Media Controller 拓扑模型 ┌─────────────────────────────────────────────────────────────────────────────┐ │ 实体 (Entity) │ │ 每个硬件功能块是一个实体:传感器、ISP、缩放器、编码器 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Sensor │ ──→ │ ISP │ ──→ │ Scaler │ ──→ │ DMA │ │ │ │ Entity │ │ Entity │ │ Entity │ │ Entity │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ Pad (Sink) Pad (Src/Sink) Pad (Src) Pad (Src) │ │ │ │ 链接 (Link):连接两个 Pad,数据从 Source Pad 流向 Sink Pad │ └─────────────────────────────────────────────────────────────────────────────┘ 实体类型: - MEDIA_ENT_F_V4L2_SUBDEV_SENSOR: 图像传感器 - MEDIA_ENT_F_PROC_VIDEO_ISP: 图像信号处理器 - MEDIA_ENT_F_PROC_VIDEO_SCALER: 缩放器 - MEDIA_ENT_F_IO_V4L: V4L2 输入/输出节点
1.4 使用 Media Controller 发现拓扑
// ========== 内核态:驱动注册 Media Controller 实体 ==========
// drivers/media/platform/rockchip/rkisp1/rkisp1.c
// RK ISP 驱动注册 Media Controller 实体的简化示例
#include <media/media-device.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
// 1. 定义实体和 Pad
struct media_entity entities[] = {
{
.name = "rkisp1_mainpath",
.function = MEDIA_ENT_F_IO_V4L,
.pads = {
{
.flags = MEDIA_PAD_FL_SINK, // DMA 引擎的 sink pad
},
{
.flags = MEDIA_PAD_FL_SOURCE, // 输出 pad
},
},
},
{
.name = "rkisp1_isp",
.function = MEDIA_ENT_F_PROC_VIDEO_ISP,
.pads = {
{ .flags = MEDIA_PAD_FL_SINK }, // 从传感器接收
{ .flags = MEDIA_PAD_FL_SOURCE }, // 输出到 DMA
},
},
};
// 2. 注册实体到 Media Device
struct media_device *mdev = &isp_dev->media_dev;
for (int i = 0; i < num_entities; i++) {
media_device_register_entity(mdev, &entities[i]);
}
// 3. 创建 Pad 之间的链接
media_create_pad_link(&sensor_entity, 0, &isp_entity, 0, 0);
media_create_pad_link(&isp_entity, 1, &dma_entity, 0, MEDIA_LNK_FL_ENABLED);
1.5 用户态:通过 Media Controller 获取拓扑
// ========== 用户态:使用 libmediactl 获取拓扑 ==========
// 程序:media-ctl -p -d /dev/media0
#include <mediactl/mediactl.h>
#include <mediactl/v4l2subdev.h>
void explore_media_topology(const char *media_devnode) {
struct media_device *media;
struct media_entity *entity;
// 1. 打开 Media Device
media = media_device_new(media_devnode);
media_device_enumerate(media); // 获取拓扑信息
// 2. 遍历所有实体
media_device_for_each_entity(entity, media) {
printf("Entity: %s\n", media_entity_get_name(entity));
printf(" Function: 0x%x\n", media_entity_get_function(entity));
printf(" Pad count: %d\n", media_entity_get_links_count(entity));
// 3. 遍历每个 Pad 的链接
for (int i = 0; i < media_entity_get_links_count(entity); i++) {
const struct media_link *link = media_entity_get_link(entity, i);
printf(" Link: %s:%d -> %s:%d\n",
media_entity_get_name(link->source->entity),
link->source->index,
media_entity_get_name(link->sink->entity),
link->sink->index);
}
}
media_device_unref(media);
}
// 使用示例:RK3588 平台的 ISP 拓扑
// media-ctl -d /dev/media0 -p
// 输出:
// Entity: m00_b_ov13850 1-0010 (Sensor)
// Pad 0: Source [fmt:SBGGR10/4224x3136]
// Entity: rkisp0_isp (ISP)
// Pad 0: Sink <- m00_b_ov13850:0 [ENABLED]
// Pad 1: Source -> rkisp0_mainpath:0 [ENABLED]
// Entity: rkisp0_mainpath (DMA)
// Pad 0: Sink <- rkisp0_isp:1 [ENABLED]
1.6 AHD 摄像头的设备发现
AHD (Analog High Definition) 摄像头通过模拟高清采集卡接入系统。
AHD 摄像头接入架构 ┌─────────────┐ 75-5 同轴线缆 ┌─────────────────────────┐ │ AHD 摄像头 │ ──────────────────→ │ TV800E 采集卡 │ │ 1080P/30fps │ 最长 300 米 │ (PCIe接口) │ └─────────────┘ │ - FPGA 解码 AHD/CVI/TVI │ │ - 输出 YUV/RGB 格式 │ │ - V4L2 标准驱动 │ └────────────┬────────────┘ │ PCIe ▼ ┌─────────────────────────┐ │ /dev/video0 (V4L2) │ │ 标准 V4L2 接口 │ └─────────────────────────┘ AHD 采集卡特征: - 支持 8 路 1080P AHD 信号同时采集 - DMA 传输带宽高达 2000MB/s,超低延迟 - 硬件去隔行、色彩空间转换、图像缩放 - 提供 Linux V4L2 驱动,兼容 VLC、OBS、GStreamer
// AHD 采集卡的设备发现和使用
// 与 UVC 使用相同的 V4L2 API,但内部驱动不同
int setup_ahd_capture(const char *devnode) {
int fd = open(devnode, O_RDWR); // 例如 /dev/video0
// 1. 查询能力
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
// cap.driver 可能是 "ahd_capture" 或 "tw5864"
// 2. 设置输入源(AHD 卡可能有多个 BNC 输入)
struct v4l2_input input;
memset(&input, 0, sizeof(input));
input.index = 0; // 选择第 0 路输入
ioctl(fd, VIDIOC_ENUMINPUT, &input);
ioctl(fd, VIDIOC_S_INPUT, &input.index);
// 3. 设置格式(AHD 采集卡支持 YUYV 或 RGB24)
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
ioctl(fd, VIDIOC_S_FMT, &fmt);
// 4. 后续使用与 UVC 完全相同(请求缓冲区、队列、DQBUF)
return fd;
}
二、软件设计模式:应对不同场景的架构
2.1 设计模式在多媒体系统中的运用
流媒体播放器需要应对不断变化的媒体格式、编解码器、传输协议,设计模式是实现高可扩展性的关键。
多媒体系统设计模式全景图 │ ├── 1. 策略模式 (Strategy Pattern) │ ├── 场景:不同编码格式(H.264/H.265/VP9/AV1) │ ├── 场景:不同传输协议(RTMP/HLS/WebRTC/SRT) │ └── 实现:运行时动态切换解码器/协议处理器 │ ├── 2. 管道模式 (Pipeline Pattern) │ ├── 场景:GStreamer 的核心架构 │ ├── 数据流:Source → Demux → Decode → Convert → Sink │ └── 实现:每个 Element 独立处理,通过 Buffer 传递 │ ├── 3. 工厂模式 (Factory Pattern) │ ├── 场景:根据文件扩展名/MIME 类型创建对应解析器 │ ├── 场景:根据设备节点类型创建采集器 │ └── 实现:MediaFactory::create(const std::string& uri) │ ├── 4. 观察者模式 (Observer Pattern) │ ├── 场景:播放器状态变化通知 UI │ ├── 场景:缓冲进度通知、播放完成通知 │ └── 实现:回调函数队列 / 信号槽机制 │ ├── 5. 责任链模式 (Chain of Responsibility) │ ├── 场景:V4L2 设备节点的 Probe 机制 │ ├── 流程:逐个驱动尝试绑定设备 │ └── 实现:Linux 内核的 driver_probe_device() │ └── 6. 对象池模式 (Object Pool) ├── 场景:V4L2 的 VB2 缓冲区池 ├── 场景:编码器的帧缓冲池 └── 实现:预分配固定数量缓冲区,循环使用
2.2 GStreamer 管道模式:多媒体处理的标准架构
GStreamer 是 Linux 多媒体处理的标准框架,其核心就是管道模式。
GStreamer 管道模式架构 ┌─────────────────────────────────────────────────────────────────────────────┐ │ 管道 (Pipeline) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Source │ → │ Demux │ → │ Decode │ → │ Convert │ → │ Sink │ │ │ │ Element │ │ Element │ │ Element │ │ Element │ │ Element │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ │ filesrc qtdemux avdec_h264 videoconvert autovideosink│ │ or v4l2src │ │ │ │ 数据流:每个 Element 处理 Buffer,通过 Pad 传递给下一个 Element │ └─────────────────────────────────────────────────────────────────────────────┘ GStreamer 管道示例:USB 摄像头采集 → 编码 → 网络推流 gst-launch-1.0 v4l2src device=/dev/video0 ! \ videoconvert ! \ v4l2h264enc ! \ # 使用硬件编码器 rtph264pay config-interval=1 ! \ udpsink host=192.168.1.100 port=5000
2.3 策略模式:解码器的运行时选择
// C++ 策略模式实现:根据编码类型选择解码器
// 策略接口
class DecoderStrategy {
public:
virtual ~DecoderStrategy() = default;
virtual int decode(const uint8_t* input, size_t len,
uint8_t* output, size_t* out_len) = 0;
virtual const char* name() const = 0;
};
// 具体策略:H.264 解码
class H264DecoderStrategy : public DecoderStrategy {
public:
int decode(const uint8_t* input, size_t len,
uint8_t* output, size_t* out_len) override {
// 调用 libavcodec 或 V4L2 M2M 设备
return h264_decode_frame(input, len, output, out_len);
}
const char* name() const override { return "H.264/AVC"; }
};
// 具体策略:H.265 解码
class H265DecoderStrategy : public DecoderStrategy {
public:
int decode(const uint8_t* input, size_t len,
uint8_t* output, size_t* out_len) override {
return h265_decode_frame(input, len, output, out_len);
}
const char* name() const override { return "H.265/HEVC"; }
};
// 策略工厂:根据编码类型创建对应策略
class DecoderFactory {
public:
static std::unique_ptr<DecoderStrategy> create(const std::string& codec) {
if (codec == "h264") return std::make_unique<H264DecoderStrategy>();
if (codec == "h265") return std::make_unique<H265DecoderStrategy>();
// 支持更多编码...
return nullptr;
}
};
三、数据同步与拥塞控制:解决花屏、丢失、延迟
3.1 音视频同步架构
音视频同步架构 ┌─────────────────────────────────────────────────────────────────────────────┐ │ 时间戳同步机制 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 视频流 │ ───→ │ 解码器 │ ───→ │ 视频帧 │ ───→ │ 渲染器 │ │ │ │ PTS=1000│ │ │ │ PTS=1000│ │ 等待 │ │ │ └─────────┘ └─────────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ 同步控制器 │ │ │ │ 比较 PTS,决定等待/丢帧/重复 │ │ │ └─────────────────────────────────┘ │ │ ▲ ▲ │ │ ┌─────────┐ ┌─────────┐ ┌────┴────┘ ┌────┴────┐ │ │ │ 音频流 │ ───→ │ 解码器 │ ───→ │ 音频帧 │ ───→ │ 渲染器 │ │ │ │ PTS=1000│ │ │ │ PTS=1000│ │ 立即播放│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ 同步策略:以音频时钟为主时钟,视频跟随音频 │ │ - 视频 PTS < 音频 PTS - 阈值 → 丢帧 │ │ - 视频 PTS > 音频 PTS + 阈值 → 等待 │ └─────────────────────────────────────────────────────────────────────────────┘
3.2 环形缓冲区 (Ring Buffer):解决数据拥塞
环形缓冲区是解决生产-消费速度不匹配的核心数据结构。
// ========== 多生产者-多消费者环形缓冲区 ==========
// 用于:采集线程 → 编码线程 → 网络发送线程 之间的数据传递
typedef struct {
uint8_t** buffer; // 缓冲区指针数组
size_t* sizes; // 每个缓冲区的大小
size_t capacity; // 缓冲区数量
atomic_int write_index; // 写索引
atomic_int read_index; // 读索引
atomic_int count; // 当前可用数据量
pthread_mutex_t lock;
pthread_cond_t not_empty;
pthread_cond_t not_full;
} RingBuffer;
// 生产者:写入数据
int ring_buffer_push(RingBuffer* rb, const uint8_t* data, size_t size) {
pthread_mutex_lock(&rb->lock);
// 等待有空间
while (rb->count >= rb->capacity) {
// 缓冲区满,根据策略决定:
// 策略1: 阻塞等待
// 策略2: 丢弃新数据
// 策略3: 丢弃最老的数据
pthread_cond_wait(&rb->not_full, &rb->lock);
}
int write_idx = rb->write_index % rb->capacity;
memcpy(rb->buffer[write_idx], data, size);
rb->sizes[write_idx] = size;
rb->write_index++;
rb->count++;
pthread_cond_signal(&rb->not_empty);
pthread_mutex_unlock(&rb->lock);
return 0;
}
// 消费者:读取数据
int ring_buffer_pop(RingBuffer* rb, uint8_t* out, size_t* out_size) {
pthread_mutex_lock(&rb->lock);
while (rb->count == 0) {
pthread_cond_wait(&rb->not_empty, &rb->lock);
}
int read_idx = rb->read_index % rb->capacity;
memcpy(out, rb->buffer[read_idx], rb->sizes[read_idx]);
*out_size = rb->sizes[read_idx];
rb->read_index++;
rb->count--;
pthread_cond_signal(&rb->not_full);
pthread_mutex_unlock(&rb->lock);
return 0;
}
3.3 拥塞控制策略
拥塞控制与丢帧策略 │ ├── 1. 网络拥塞控制(WebRTC 场景) │ ├── GCC (Google Congestion Control) │ │ ├── 基于延迟的拥塞检测 │ │ ├── 基于丢包的拥塞检测 │ │ └── 码率自适应调整 │ ├── 参数调优: │ │ ├── start_bitrate=3000kbps │ │ ├── max_bitrate=5000kbps │ │ └── min_bitrate=1000kbps │ └── 注意:WebRTC 默认码率算法不适合桌面共享场景 │ ├── 2. 采集端丢帧策略 │ ├── 策略A:丢弃新帧(保持队列深度) │ │ └── 适用:实时性要求高的直播场景 │ ├── 策略B:丢弃旧帧(总是处理最新帧) │ │ └── 适用:视频预览、监控场景 │ └── 策略C:智能丢帧(根据帧类型) │ ├── 优先丢弃 B 帧 │ ├── 其次丢弃 P 帧 │ └── 保留 I 帧(关键帧) │ ├── 3. 解码端丢帧策略 │ ├── 参考帧依赖检查 │ │ ├── 如果丢失 P 帧,依赖的后续帧也无法解码 │ │ └── 需要等待下一个 IDR 帧 │ └── 解码器刷新策略 │ ├── 丢帧后请求关键帧 │ └── 解码器状态重置 │ └── 4. 花屏处理 ├── 原因1:丢包导致参考帧缺失 │ └── 解决:FEC (Forward Error Correction) / 重传 / 关键帧请求 ├── 原因2:编码器参数不当导致 QP 过高 │ └── 解决:降低 QP、提高码率、使用较慢编码预设 └── 原因3:硬件编码器超时 └── 解决:降低分辨率/帧率、使用软件编码
3.4 WebRTC 桌面共享优化案例
根据 ZLMediaKit 的 WebRTC 优化实践,桌面共享场景需要特殊配置:
# ZLMediaKit config.ini - WebRTC 优化配置 [rtc] # 初始码率(kbps),0表示自动 start_bitrate=3000 # 最大码率(kbps) max_bitrate=5000 # 最小码率(kbps) min_bitrate=1000 # 桌面共享内容特征: # - 包含大量文字和图形,需要更高的质量 # - 运动具有局部性和突发性,码率需要快速响应 # - 推荐 1080p 至少 6Mbps 码率
四、网络视频流:播放网络视频的完整链路
4.1 RTP 视频流接收与播放
使用 GStreamer 接收 RTP 视频流并播放:
# 接收端:GStreamer 接收 RTP H.264 流并播放 gst-launch-1.0 udpsrc port=5000 caps="application/x-rtp, \ media=(string)video, clock-rate=(int)90000, \ encoding-name=(string)H264, payload=(int)96" ! \ rtph264depay ! \ decodebin ! \ videoconvert ! \ autovideosink sync=false # 发送端:USB 摄像头采集 → H.264 编码 → RTP 打包 → UDP 发送 gst-launch-1.0 v4l2src device=/dev/video0 ! \ videoconvert ! \ v4l2h264enc ! \ rtph264pay config-interval=1 pt=96 ! \ udpsink host=192.168.1.100 port=5000
4.2 网络视频播放器的状态机
网络视频播放器状态机 ┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ ┌─────────┐ 打开URL ┌─────────┐ 预加载 ┌─────────┐ │ │ │ Stopped │ ───────────→ │ Opening │ ───────────→ │ Buffering│ │ │ └─────────┘ └─────────┘ └────┬────┘ │ │ ↑ │ │ │ │ │ │ 失败 │ 缓冲足够 │ │ │ 停止 ▼ ▼ │ │ │ ┌─────────┐ ┌─────────┐ │ │ └──────────────────── │ Error │ │ Playing │ │ │ └─────────┘ └────┬────┘ │ │ │ │ │ │ 缓冲不足 │ │ ▼ │ │ ┌─────────┐ │ │ │Rebuffering│ │ │ └─────────┘ │ │ │ │ 缓冲策略: │ │ - 初始缓冲:预加载 3-10 秒数据后才开始播放 │ │ - 动态缓冲:网络波动时增加缓冲,播放稳定后减小缓冲 │ │ - 智能缓冲:根据带宽估计动态调整目标缓冲长度 │ └─────────────────────────────────────────────────────────────────────────────┘
五、完整案例:边录边播 + 多路输入
5.1 系统架构
边录边播完整架构 ┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 输入层 (Input Layer) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ UVC 摄像头│ │ AHD 采集卡│ │ RTMP 流 │ │ 本地文件 │ │ │ │ │ │/dev/video0│ │/dev/video1│ │rtmp://.. │ │test.mp4 │ │ │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ └───────┼─────────────┼─────────────┼─────────────┼───────────────────┘ │ │ │ │ │ │ │ │ └─────────────┴─────────────┴─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 解复用与解码层 (Demux & Decode) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 策略工厂 (DecoderFactory) │ │ │ │ │ │ H.264解码器 H.265解码器 AAC解码器 MP3解码器 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 处理层 (Processing Layer) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ 色彩空间转换 │ │ 图像缩放 │ │ 音视频同步 │ │ │ │ │ │ YUV→RGB │ │ 1080P→720P │ │ PTS对齐 │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ 本地录制 │ │ 网络推流 │ │ 本地预览 │ │ │ │ (Record Path) │ │ (Stream Path) │ │ (Preview Path) │ │ │ │ │ │ │ │ │ │ │ │ 编码器(H.264) │ │ 编码器(H.264) │ │ 直接渲染 │ │ │ │ ↓ │ │ ↓ │ │ │ │ │ │ 封装器(MP4) │ │ RTP打包 │ │ │ │ │ │ ↓ │ │ ↓ │ │ │ │ │ │ 文件写入 │ │ UDP发送 │ │ │ │ │ │ (ext4/f2fs) │ │ │ │ │ │ │ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
5.2 统一设备管理:设备号与驱动映射
// ========== 统一设备管理 ==========
// 支持多种输入源,统一抽象为 "MediaSource"
typedef enum {
SOURCE_TYPE_UVC, // USB 摄像头
SOURCE_TYPE_AHD, // AHD 采集卡
SOURCE_TYPE_NETWORK, // 网络流 (RTMP/RTSP/HLS)
SOURCE_TYPE_FILE, // 本地文件
SOURCE_TYPE_SCREEN // 屏幕录制
} SourceType;
typedef struct {
SourceType type;
char devnode[64]; // 设备节点,如 /dev/video0
char url[512]; // 网络地址,如 rtmp://live.example.com/stream
char filepath[512]; // 文件路径
// 运行时信息
int fd; // 打开的 fd(UVC/AHD 使用)
void* handle; // 网络流句柄(libcurl/ffmpeg)
// 状态
int is_streaming;
uint64_t frame_count;
} MediaSource;
// 根据源类型自动发现设备节点
void discover_all_sources(void) {
// 1. 枚举 UVC 设备
DIR* dir = opendir("/dev");
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "video", 5) == 0) {
char path[256];
snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
int fd = open(path, O_RDWR);
if (fd >= 0) {
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (strstr(cap.driver, "uvcvideo") != NULL) {
printf("Found UVC camera: %s\n", path);
// 添加到设备列表
}
close(fd);
}
}
}
closedir(dir);
// 2. 枚举 AHD 采集卡
// 通过 PCI 设备或特定驱动名称识别
// 3. 枚举音频设备
// 遍历 /dev/snd/pcm*C*D*c
// 4. 枚举 Media Controller 设备
// 遍历 /dev/media*,获取完整拓扑
}
六、全链路数据流总结
┌─────────────────────────────────────────────────────────────────────────────┐ │ 全链路数据流总结 │ │ │ │ 设备发现层 │ │ ├── /dev/video* (V4L2) ──┬── UVC 摄像头 │ │ │ └── AHD 采集卡 │ │ ├── /dev/snd/pcm* (ALSA) ──┬── USB 麦克风 │ │ │ └── HDMI 音频输入 │ │ ├── /dev/media* (Media Controller) ── 完整拓扑发现 │ │ └── 网络流 (RTMP/RTSP/RTP) ── URL 解析 │ │ │ │ 设计模式层 │ │ ├── 策略模式:动态切换编码器/解码器 │ │ ├── 管道模式:GStreamer 数据处理流 │ │ ├── 工厂模式:根据源类型创建采集器 │ │ └── 观察者模式:状态变化通知 │ │ │ │ 同步与拥塞控制层 │ │ ├── 环形缓冲区:生产-消费解耦 │ │ ├── 时间戳同步:PTS/DTS 对齐 │ │ ├── 丢帧策略:智能丢帧(B帧 > P帧 > I帧) │ │ └── 拥塞控制:GCC / 码率自适应 │ │ │ │ 输出层 │ │ ├── 本地录制:ext4/f2fs 文件系统 + 页缓存 │ │ ├── 网络推流:RTP/RTMP/WebRTC + UDP/TCP │ │ └── 本地预览:DRM/GPU 渲染 + 零拷贝 │ │ │ │ 与 MM/FS 子系统的关联 │ │ ├── V4L2 VB2 缓冲区:通过 dma_alloc_coherent() 分配 │ │ ├── 页缓存:录制文件时通过 write() 写入 │ │ └── 零拷贝:DMA-BUF 共享,避免 CPU 拷贝 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
第二部分 MM/FS 子系统的关联知识体系与问题解决框架
"问题 → 分析框架 → 解决方案" 的逻辑,形成一套可以应对任何 FS 问题的宏大叙事。
一、完整知识体系全景图
Linux FS 子系统完整知识体系 │ ├── 第一层:FS 基础架构 │ ├── VFS 层(虚拟文件系统) │ ├── DCACHE(目录项缓存) │ ├── ICACHE(inode 缓存) │ └── Page Cache(页缓存) │ ├── 第二层:MM-FS 交互机制 │ ├── address_space 桥梁 │ ├── 读路径:read() → 页缓存 → 磁盘 │ ├── 写路径:write() → 页缓存 → 回写 │ └── mmap 路径:缺页异常 → 文件映射 │ ├── 第三层:性能优化机制 │ ├── NEON/SIMD 指令加速 │ ├── 预读(readahead) │ ├── 脏页回写(writeback) │ └── LRU 回收 │ ├── 第四层:具体文件系统实现 │ ├── ext4(日志型,通用) │ ├── f2fs(Flash 友好) │ ├── exfat(跨平台) │ └── tmpfs(内存文件系统) │ └── 第五层:调试与问题解决 ├── 性能分析工具 ├── 内存问题定位 ├── I/O 问题定位 └── 缓存问题定位
二、核心问题与解决思路框架
2.1 问题分类矩阵
┌─────────────────────────────────────────────────────────────────────────┐ │ FS 问题分类与诊断框架 │ ├─────────────┬─────────────────────────────┬─────────────────────────────┤ │ 问题类型 │ 典型现象 │ 诊断路径 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 路径查找慢 │ open() 调用耗时 > 1ms │ DCACHE 命中率 → 目录深度 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 读文件慢 │ read() 吞吐量低 │ Page Cache 命中率 → I/O 调度 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 写文件慢 │ write() 阻塞或延迟高 │ 脏页比例 → 回写线程状态 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 内存压力大 │ kswapd CPU 高,系统卡顿 │ LRU 回收 → slab 使用情况 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 文件系统满 │ ENOSPC 错误 │ inode 使用 → 块使用 → 预留 │ ├─────────────┼─────────────────────────────┼─────────────────────────────┤ │ 数据损坏 │ fsck 报错,文件不可读 │ 日志回放 → 坏块检测 │ └─────────────┴─────────────────────────────┴─────────────────────────────┘
三、源码目录完整树形分析
3.1 FS 子系统完整目录树
linux-4.19/fs/ │ ├── 1. VFS 核心层(所有 FS 的抽象层) │ ├── open.c # ⭐ open() 系统调用 │ ├── read_write.c # ⭐ read()/write() 系统调用 │ ├── namei.c # ⭐ 路径查找核心(DCACHE 消费者) │ ├── dcache.c # ⭐ 目录项缓存 │ ├── inode.c # ⭐ inode 缓存 │ ├── file_table.c # 文件表管理 │ ├── super.c # 超级块管理 │ ├── filesystems.c # 文件系统注册 │ ├── namespace.c # 挂载命名空间 │ ├── exec.c # 可执行文件加载 │ ├── fcntl.c # fcntl 系统调用 │ ├── ioctl.c # ioctl 系统调用 │ ├── select.c / eventpoll.c # I/O 多路复用 │ └── splice.c # 零拷贝 │ ├── 2. 页缓存与 I/O 层(MM-FS 握手点) │ ├── buffer.c # ⭐ 块缓冲区(传统路径) │ ├── direct-io.c # 直接 I/O(绕过页缓存) │ └── iomap.c # ⭐ iomap(现代 FS 的新路径) │ ├── 3. 磁盘文件系统(Disk FS) │ ├── ext4/ # ⭐ Ext4(Linux 默认) │ │ ├── super.c # 超级块操作 │ │ ├── inode.c # inode 读写 │ │ ├── file.c # 文件操作 │ │ ├── namei.c # 目录操作 │ │ ├── extents.c # ⭐ Extent 树(大文件支持) │ │ ├── mballoc.c # 多块分配器 │ │ ├── balloc.c # 块分配 │ │ ├── dir.c # 目录项处理 │ │ ├── xattr.c / acl.c # 扩展属性 │ │ └── journal.c / jbd2.c # 日志 │ ├── xfs/ # XFS(企业级) │ ├── f2fs/ # F2FS(Flash 友好) │ └── exfat/ / fat/ # FAT/exFAT(嵌入式) │ ├── 4. 伪文件系统(Pseudo FS) │ ├── proc/ # /proc │ │ ├── base.c │ │ ├── task_mmu.c # /proc/pid/maps │ │ └── vmcore.c │ ├── sysfs/ # /sys │ ├── debugfs/ # 调试文件系统 │ └── devpts/ # 伪终端 │ ├── 5. 内存文件系统(Memory FS) │ ├── ramfs/ # 简单内存 FS │ ├── shmem.c # ⭐ tmpfs(MM 管理) │ └── hugetlbfs/ # 大页 FS │ └── 6. 网络与特殊 FS ├── nfs/ # 网络文件系统 ├── fuse/ # 用户态 FS └── overlayfs/ # 联合 FS
四、MM-FS 交互关键函数树形分析
4.1 读路径函数调用链
[用户空间] read(fd, buf, count)
│
▼
[VFS 层] fs/read_write.c
SYSCALL_DEFINE3(read)
└── ksys_read()
├── fdget(fd) # 获取 struct fd
├── file->f_op->read_iter() # 多态调用
│ └── generic_file_read_iter() # 通用实现
└── fdput(fd)
│
▼
[MM 层] mm/filemap.c
generic_file_read_iter()
└── filemap_read()
├── while (有数据) {
│ ├── find_get_page(mapping, index) # 查找页缓存
│ │ ├── radix_tree_lookup() # O(log n) 查找
│ │ └── 命中 → 直接使用
│ │
│ ├── if (未命中) {
│ │ ├── page_cache_sync_readahead() # 预读
│ │ ├── page_cache_alloc() # 分配新页
│ │ ├── add_to_page_cache_lru() # 加入 LRU
│ │ └── mapping->a_ops->readpage() # 调用 FS
│ │}
│ │
│ ├── wait_on_page_locked() # 等待 I/O 完成
│ ├── copy_page_to_iter() # 拷贝到用户空间
│ └── pos += copied
│}
└── return total
│
▼
[FS 层] fs/ext4/inode.c
ext4_readpage()
└── ext4_mpage_readpages()
├── ext4_map_blocks() # 计算磁盘块号
│ ├── ext4_find_extent() # 查询 Extent 树
│ └── 返回物理块号
├── bio_alloc() # 分配 BIO
├── bio_add_page() # 添加页
└── submit_bio() # 提交到块层
│
▼
[块层] block/blk-core.c
submit_bio()
└── generic_make_request()
└── 块设备驱动 → 硬件
4.2 写路径函数调用链
[用户空间] write(fd, buf, count)
│
▼
[VFS 层] fs/read_write.c
SYSCALL_DEFINE3(write)
└── ksys_write()
├── fdget(fd)
├── file->f_op->write_iter()
│ └── generic_file_write_iter()
└── fdput(fd)
│
▼
[MM 层] mm/filemap.c
generic_file_write_iter()
└── generic_perform_write()
├── while (有数据) {
│ ├── offset = pos & ~PAGE_MASK
│ ├── bytes = min(PAGE_SIZE - offset, count)
│ │
│ ├── page = grab_cache_page_write_begin()
│ │ ├── find_lock_page() # 查找并锁定
│ │ └── page_cache_alloc() # 分配新页
│ │
│ ├── mapping->a_ops->write_begin() # FS 准备
│ │ └── ext4_write_begin()
│ │ ├── 检查是否需要"读-改-写"
│ │ └── 如需读取旧数据 → readpage()
│ │
│ ├── copy_page_from_iter() # 拷贝用户数据
│ │
│ ├── mapping->a_ops->write_end() # FS 完成
│ │ └── ext4_write_end()
│ │ ├── SetPageDirty() # 标记脏页
│ │ ├── mark_inode_dirty() # 标记 inode 脏
│ │ └── 更新 i_size
│ │
│ ├── balance_dirty_pages() # ⭐ 脏页限速
│ └── cond_resched() # 让出 CPU
│}
└── return written
│
▼
[后台回写] mm/page-writeback.c
wb_workfn() # 回写线程
└── wb_writeback()
├── writeback_sb_inodes()
└── __writeback_single_inode()
└── do_writepages()
└── mapping->a_ops->writepages()
└── ext4_writepages()
├── ext4_map_blocks()
├── 构造 BIO
└── submit_bio()
4.3 DCACHE 路径查找函数调用链
[用户空间] open("/data/video.mp4")
│
▼
[VFS 层] fs/namei.c
path_lookupat()
└── link_path_walk()
└── walk_component()
├── lookup_fast() # ⭐ DCACHE 快速路径
│ └── d_lookup() # 哈希表查找
│ ├── 计算哈希桶
│ ├── 遍历链表匹配
│ └── 命中 → dget() 增加引用
│
└── lookup_slow() # 慢速路径(磁盘读取)
└── dir->i_op->lookup()
└── ext4_lookup()
├── ext4_find_entry() # 读目录页
└── d_splice_alias() # 创建 dentry
│
▼
[DCACHE 层] fs/dcache.c
d_alloc() # 分配新 dentry
d_instantiate() # 绑定 inode
dget() / dput() # 引用计数管理
五、关键数据结构树形分析
5.1 核心数据结构关系图
┌─────────────────────────────────────────────────────────────────────────┐ │ 数据结构关系图 │ │ │ │ struct super_block (超级块) │ │ ├── s_root ──────────────────────────┐ │ │ ├── s_inodes (inode 链表) │ │ │ └── s_dentry_lru (dentry LRU) │ │ │ │ │ │ ▼ │ │ struct dentry (目录项) │ │ │ ├── d_inode ─────────────────────────┼──────────────┐ │ │ ├── d_parent (父目录) │ │ │ │ ├── d_subdirs (子目录链表) │ │ │ │ ├── d_hash (哈希表节点) │ │ │ │ └── d_lru (LRU 链表) │ │ │ │ │ │ │ │ ▼ │ │ │ struct inode (索引节点) │ │ │ │ ├── i_mapping ───────────────────────┼──────────────┼──────┐ │ │ ├── i_fop (文件操作) │ │ │ │ │ ├── i_op (inode 操作) │ │ │ │ │ └── i_size (文件大小) │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ struct address_space (地址空间) │ │ │ │ │ ├── host ─────────────────────────────┘ │ │ │ │ ├── page_tree (radix tree) │ │ │ │ ├── a_ops (FS 操作表) │ │ │ │ │ └── nrpages (页缓存页数) │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ struct page (页) │ │ │ │ │ ├── mapping ──────────────────────────┘ │ │ │ │ ├── index (文件内偏移) │ │ │ │ ├── lru (LRU 链表) │ │ │ │ └── flags (PG_dirty, PG_locked, etc.) │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ struct file (打开的文件) │ │ │ │ ├── f_inode ─────────────────────────┘ │ │ │ │ ├── f_mapping ─────────────────────────┘ │ │ │ │ ├── f_pos (文件位置) │ │ │ │ └── f_op (文件操作) │ │ │ └─────────────────────────────────────────────────────────────────────────┘
5.2 核心结构体定义
// ========== 1. struct dentry - 目录项缓存 ==========
// include/linux/dcache.h
struct dentry {
unsigned int d_count; // 引用计数
spinlock_t d_lock; // 保护锁
struct inode *d_inode; // ⭐ 关联的 inode
struct dentry *d_parent; // 父目录
struct list_head d_child; // 父目录的子节点
struct list_head d_subdirs; // 子目录链表头
struct hlist_node d_hash; // ⭐ 哈希表节点
struct list_head d_lru; // ⭐ LRU 回收链表
unsigned char d_iname[16]; // 短名字(内联)
const struct dentry_operations *d_op;
};
// ========== 2. struct inode - 索引节点 ==========
// include/linux/fs.h
struct inode {
umode_t i_mode; // 文件类型和权限
unsigned long i_ino; // inode 号
loff_t i_size; // ⭐ 文件大小
struct timespec i_atime; // 访问时间
struct timespec i_mtime; // 修改时间
struct timespec i_ctime; // 状态改变时间
struct address_space *i_mapping; // ⭐ 指向 address_space
struct address_space i_data; // ⭐ 内嵌 address_space
const struct file_operations *i_fop; // 文件操作
const struct inode_operations *i_op; // inode 操作
};
// ========== 3. struct address_space - MM-FS 桥梁 ==========
// include/linux/fs.h
struct address_space {
struct inode *host; // ⭐ 所属 inode
struct radix_tree_root page_tree; // ⭐ 页缓存基树
spinlock_t tree_lock; // 树锁
unsigned long nrpages; // 页缓存中的页数
const struct address_space_operations *a_ops; // ⭐ FS 操作表
struct backing_dev_info *backing_dev_info;
};
// ========== 4. struct page - 物理页 ==========
// include/linux/page-flags.h
struct page {
unsigned long flags; // PG_locked, PG_dirty, PG_uptodate
struct list_head lru; // ⭐ LRU 链表节点
struct address_space *mapping; // ⭐ 所属 address_space
pgoff_t index; // ⭐ 文件内偏移
unsigned long private; // FS 私有数据
};
// ========== 5. address_space_operations - FS 必须实现的回调 ==========
// include/linux/fs.h
struct address_space_operations {
int (*readpage)(struct file *, struct page *); // ⭐ 读一页
int (*writepage)(struct page *, struct writeback_control *); // ⭐ 写一页
int (*write_begin)(struct file *, struct address_space *,
loff_t, unsigned, struct page **, void **);
int (*write_end)(struct file *, struct address_space *,
loff_t, unsigned, unsigned, struct page *, void *);
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *);
};
六、DCACHE 与 ICACHE 的"主仆"关系
┌─────────────────────────────────────────────────────────────────────────┐
│ DCACHE 是 ICACHE 的主人(关键设计原则) │
│ │
│ 内核注释原文(fs/dcache.c): │
│ "The dcache is a master of the icache - whenever a dcache entry │
│ exists, the inode will always exist." │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 为什么这样设计? │ │
│ │ │ │
│ │ 1. 简化引用计数管理 │ │
│ │ ├── dentry 和 inode 共享生命周期 │ │
│ │ ├── 只需管理 dentry 的 d_count │ │
│ │ └── inode 通过 d_inode 指针访问,无需单独计数 │ │
│ │ │ │
│ │ 2. 保证路径查找的安全性 │ │
│ │ ├── 路径查找过程中,dentry 被 dget() 锁定 │ │
│ │ ├── 保证 inode 不会在查找过程中被释放 │ │
│ │ └── 避免 use-after-free 问题 │ │
│ │ │ │
│ │ 3. 支持负向 dentry(Negative Dentry) │ │
│ │ ├── 缓存"文件不存在"的结果 │ │
│ │ ├── d_inode == NULL 表示负向 dentry │ │
│ │ └── 避免重复磁盘访问 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 代码示例: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ struct dentry *dentry = d_lookup(parent, &name); │ │
│ │ if (!dentry) { │ │
│ │ // 分配 dentry │ │
│ │ dentry = d_alloc(parent, &name); │ │
│ │ // 读取 inode │ │
│ │ inode = sb->s_op->iget(sb, ino); │ │
│ │ // ⭐ dentry 接管 inode │ │
│ │ d_instantiate(dentry, inode); │ │
│ │ } │ │
│ │ // 此时 inode 保证存在 │ │
│ │ struct inode *inode = dentry->d_inode; // 一定非 NULL │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
七、问题解决框架
7.1 路径查找慢的问题解决框架
问题:open() 调用耗时 > 1ms │ ├── 诊断步骤 │ ├── 1. 查看 DCACHE 统计 │ │ └── cat /proc/sys/fs/dentry-state │ │ │ ├── 2. 分析目录深度 │ │ └── 嵌套层级 > 10 层时,即使 DCACHE 命中也要遍历 │ │ │ └── 3. 检查是否频繁创建/删除文件 │ └── DCACHE 回收频繁 → 增加 vfs_cache_pressure │ ├── 解决方案 │ ├── 方案1:增加 vfs_cache_pressure(减少回收) │ │ └── echo 50 > /proc/sys/vm/vfs_cache_pressure │ │ │ ├── 方案2:使用目录项缓存预加载 │ │ └── 提前 open 常用目录 │ │ │ └── 方案3:重新组织目录结构 │ └── 减少目录嵌套深度 │ └── 验证方法 └── strace -T -e trace=open ./program
7.2 读写性能低的问题解决框架
问题:read()/write() 吞吐量低于预期 │ ├── 诊断步骤 │ ├── 1. 检查页缓存命中率 │ │ └── cat /proc/meminfo | grep -E "Cached|Dirty" │ │ │ ├── 2. 检查脏页比例 │ │ └── cat /proc/meminfo | grep Dirty │ │ │ ├── 3. 检查 I/O 调度器 │ │ └── cat /sys/block/mmcblk0/queue/scheduler │ │ │ └── 4. 检查预读设置 │ └── cat /sys/block/mmcblk0/queue/read_ahead_kb │ ├── 解决方案 │ ├── 方案1:调整脏页参数 │ │ ├── sysctl -w vm.dirty_ratio=20 │ │ └── sysctl -w vm.dirty_background_ratio=10 │ │ │ ├── 方案2:调整预读大小 │ │ └── echo 2048 > /sys/block/mmcblk0/queue/read_ahead_kb │ │ │ ├── 方案3:使用 direct I/O(数据库场景) │ │ └── open(fd, O_DIRECT) │ │ │ └── 方案4:使用异步 I/O(io_uring) │ └── 验证方法 ├── dd if=/dev/zero of=test bs=1M count=1024 conv=fsync └── iostat -x 1
7.3 内存压力大的问题解决框架
问题:kswapd CPU 高,系统卡顿 │ ├── 诊断步骤 │ ├── 1. 查看内存使用 │ │ └── free -m │ │ │ ├── 2. 查看 slab 使用 │ │ └── slabtop -o │ │ │ ├── 3. 查看页缓存占比 │ │ └── cat /proc/meminfo | grep Cached │ │ │ └── 4. 查看回收压力 │ └── cat /proc/vmstat | grep -E "pgscan|pgsteal" │ ├── 解决方案 │ ├── 方案1:调整 vfs_cache_pressure │ │ └── echo 200 > /proc/sys/vm/vfs_cache_pressure │ │ │ ├── 方案2:减少 swap 使用 │ │ └── sysctl -w vm.swappiness=10 │ │ │ ├── 方案3:手动清空缓存(临时) │ │ └── echo 3 > /proc/sys/vm/drop_caches │ │ │ └── 方案4:增加内存 │ └── 验证方法 ├── vmstat 1 └── cat /proc/meminfo
八、调试命令速查表
| 调试目标 | 命令 | 关键指标 |
|---|---|---|
| DCACHE 状态 | cat /proc/sys/fs/dentry-state |
nr_dentry, nr_unused |
| inode 状态 | cat /proc/slabinfo \| grep inode |
活跃 inode 数 |
| 页缓存状态 | cat /proc/meminfo \| grep -E "Cached\|Dirty" |
缓存大小,脏页大小 |
| 内存回收 | cat /proc/vmstat \| grep -E "pgscan\|pgsteal" |
扫描/回收页数 |
| I/O 统计 | iostat -x 1 |
%util, r/s, w/s |
| 进程跟踪 | strace -e trace=file,open,read,write |
系统调用耗时 |
| 内核追踪 | trace-cmd record -e filemap -e writeback |
页缓存事件 |
| 性能分析 | perf record -e page-faults -ag |
缺页异常统计 |
九、关键宏与函数速查表
| 名称 | 文件位置 | 作用 |
|---|---|---|
d_lookup() |
fs/dcache.c |
DCACHE 哈希查找 |
d_alloc() |
fs/dcache.c |
分配 dentry |
d_instantiate() |
fs/dcache.c |
dentry 绑定 inode |
find_get_page() |
mm/filemap.c |
页缓存查找 |
add_to_page_cache_lru() |
mm/filemap.c |
添加页到缓存 |
set_page_dirty() |
mm/page-writeback.c |
标记脏页 |
balance_dirty_pages() |
mm/page-writeback.c |
脏页限速 |
shrink_page_list() |
mm/vmscan.c |
页回收 |
generic_file_read_iter() |
mm/filemap.c |
通用文件读 |
generic_perform_write() |
mm/filemap.c |
通用文件写 |
PAGE_SIZE |
asm/page.h |
页大小(4096) |
PAGE_MASK |
asm/page.h |
页掩码(~4095) |
更多推荐



所有评论(0)