Linux 块设备驱动完整篇:从gendisk、submit_bio 到blk-mq queue_rq 与排障

磁盘节点在 /dev 里看得见,但 dd 挂死、iostat 的 await 只涨不跌、自写驱动 probe 成功却永远没有 I/O------根因很少在文件系统,而在 bio → request → 硬件队列 这一环没接牢。字符设备用 read/write 直接碰缓冲;块设备必须把请求交给统一块层,由 blk-mq 调度后再进驱动的 queue_rq。少实现完成回调、容量与逻辑块大小不一致、或在 queue_rq 里长时间阻塞,都会表现为「设备在、业务死」。

本文把 gendisk 注册、submit_bio 主路径、blk_mq_ops、队列参数、完成/超时与卸载生命周期合成一篇可对照源码动手验证的闭环。源 chapter(021--023、025--027)为提纲;正文按主线内核真实符号重写,便于嵌入式存储、virtio-blk、简易 RAM disk 与 NVMe/SCSI 类驱动排障。


源码锚点

路径 作用
include/linux/blkdev.h struct gendiskblock_device_operationssubmit_bio*
include/linux/blk-mq.h struct blk_mq_opsblk_mq_tag_setblk_mq_hw_ctx
include/linux/bio.h struct bio、生物向量、bio_endio
block/blk-core.c submit_bio / 通用提交流程入口
block/blk-mq.c mq 分发、构造 request、调用 queue_rq、完成路径
block/genhd.c device_add_disk / del_gendisk、磁盘生命周期
block/blk-mq-tag.c 一带 tag / 队列深度管理
Documentation/block/ 块层与 mq 文档

关键回调(字段以本机头文件为准):

c 复制代码
/* include/linux/blkdev.h */
struct block_device_operations {
	blk_qc_t (*submit_bio)(struct bio *bio); /* 少数驱动直挂 bio */
	int (*open)(struct block_device *, fmode_t);
	void (*release)(struct gendisk *, fmode_t);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	struct module *owner;
	/* ... */
};

/* include/linux/blk-mq.h */
struct blk_mq_ops {
	blk_status_t (*queue_rq)(struct blk_mq_hw_ctx *hctx,
				 const struct blk_mq_queue_data *bd);
	void (*complete)(struct request *rq);
	enum blk_eh_timer_return (*timeout)(struct request *rq);
	/* map_queues / commit_rqs / poll ... 按设备能力选配 */
};

现代驱动注册骨架(API 名随版本演进,以树内 block/ 为准):

c 复制代码
struct blk_mq_tag_set set = {
	.ops = &my_mq_ops,
	.nr_hw_queues = nr_queues,
	.queue_depth = depth,
	.numa_node = NUMA_NO_NODE,
	.flags = BLK_MQ_F_SHOULD_MERGE, /* 按需 */
};
blk_mq_alloc_tag_set(&set);

/* 常见路径:blk_mq_alloc_disk / blk_alloc_disk + 绑定 tag_set */
disk->fops = &my_fops;
disk->private_data = priv;
set_capacity(disk, sectors_512);   /* 或 set_capacity_and_notify */
device_add_disk(parent_dev, disk, NULL);
/* → genhd:sysfs /dev 节点出现,此后才可对 /dev/xxx 下发 I/O */

完成路径必须与 queue_rq 成对:

c 复制代码
/* 驱动在中断/完成线程里 */
blk_mq_complete_request(rq);
/* 或软中断路径:blk_mq_end_request / __blk_mq_end_request 等
   以当前树符号为准------核心是:每个已接受的 request 最终要 end */

调用链

用户 I/O → bio → mq → 驱动(主路径)

#mermaid-svg-Se5CSeXGEYlCrQpU{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-Se5CSeXGEYlCrQpU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Se5CSeXGEYlCrQpU .error-icon{fill:#552222;}#mermaid-svg-Se5CSeXGEYlCrQpU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Se5CSeXGEYlCrQpU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Se5CSeXGEYlCrQpU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Se5CSeXGEYlCrQpU .marker.cross{stroke:#333333;}#mermaid-svg-Se5CSeXGEYlCrQpU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Se5CSeXGEYlCrQpU p{margin:0;}#mermaid-svg-Se5CSeXGEYlCrQpU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster-label text{fill:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster-label span{color:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster-label span p{background-color:transparent;}#mermaid-svg-Se5CSeXGEYlCrQpU .label text,#mermaid-svg-Se5CSeXGEYlCrQpU span{fill:#333;color:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU .node rect,#mermaid-svg-Se5CSeXGEYlCrQpU .node circle,#mermaid-svg-Se5CSeXGEYlCrQpU .node ellipse,#mermaid-svg-Se5CSeXGEYlCrQpU .node polygon,#mermaid-svg-Se5CSeXGEYlCrQpU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Se5CSeXGEYlCrQpU .rough-node .label text,#mermaid-svg-Se5CSeXGEYlCrQpU .node .label text,#mermaid-svg-Se5CSeXGEYlCrQpU .image-shape .label,#mermaid-svg-Se5CSeXGEYlCrQpU .icon-shape .label{text-anchor:middle;}#mermaid-svg-Se5CSeXGEYlCrQpU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Se5CSeXGEYlCrQpU .rough-node .label,#mermaid-svg-Se5CSeXGEYlCrQpU .node .label,#mermaid-svg-Se5CSeXGEYlCrQpU .image-shape .label,#mermaid-svg-Se5CSeXGEYlCrQpU .icon-shape .label{text-align:center;}#mermaid-svg-Se5CSeXGEYlCrQpU .node.clickable{cursor:pointer;}#mermaid-svg-Se5CSeXGEYlCrQpU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Se5CSeXGEYlCrQpU .arrowheadPath{fill:#333333;}#mermaid-svg-Se5CSeXGEYlCrQpU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Se5CSeXGEYlCrQpU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Se5CSeXGEYlCrQpU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Se5CSeXGEYlCrQpU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Se5CSeXGEYlCrQpU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Se5CSeXGEYlCrQpU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster text{fill:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU .cluster span{color:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU 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-Se5CSeXGEYlCrQpU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Se5CSeXGEYlCrQpU rect.text{fill:none;stroke-width:0;}#mermaid-svg-Se5CSeXGEYlCrQpU .icon-shape,#mermaid-svg-Se5CSeXGEYlCrQpU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Se5CSeXGEYlCrQpU .icon-shape p,#mermaid-svg-Se5CSeXGEYlCrQpU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Se5CSeXGEYlCrQpU .icon-shape .label rect,#mermaid-svg-Se5CSeXGEYlCrQpU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Se5CSeXGEYlCrQpU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Se5CSeXGEYlCrQpU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Se5CSeXGEYlCrQpU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
少见直挂
BLK_STS_OK
资源不足等
用户态 read/write / O_DIRECT / 页缓存回写
FS / iomap / 块层生成 bio
submit_bio(bio)
disk 使用 blk-mq?
blk_mq_submit_bio
disk->fops->submit_bio
合并/调度 / 取 tag
构造 struct request
ops->queue_rq(hctx, bd)
硬件或仿真后端执行
BLK_STS_* 返回 / 重试策略
ops->complete / blk_mq_complete_request
bio_endio → 唤醒等待者

模块分层与数据流

#mermaid-svg-mjdExZHrqhRF4wvl{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-mjdExZHrqhRF4wvl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mjdExZHrqhRF4wvl .error-icon{fill:#552222;}#mermaid-svg-mjdExZHrqhRF4wvl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mjdExZHrqhRF4wvl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mjdExZHrqhRF4wvl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mjdExZHrqhRF4wvl .marker.cross{stroke:#333333;}#mermaid-svg-mjdExZHrqhRF4wvl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mjdExZHrqhRF4wvl p{margin:0;}#mermaid-svg-mjdExZHrqhRF4wvl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mjdExZHrqhRF4wvl .cluster-label text{fill:#333;}#mermaid-svg-mjdExZHrqhRF4wvl .cluster-label span{color:#333;}#mermaid-svg-mjdExZHrqhRF4wvl .cluster-label span p{background-color:transparent;}#mermaid-svg-mjdExZHrqhRF4wvl .label text,#mermaid-svg-mjdExZHrqhRF4wvl span{fill:#333;color:#333;}#mermaid-svg-mjdExZHrqhRF4wvl .node rect,#mermaid-svg-mjdExZHrqhRF4wvl .node circle,#mermaid-svg-mjdExZHrqhRF4wvl .node ellipse,#mermaid-svg-mjdExZHrqhRF4wvl .node polygon,#mermaid-svg-mjdExZHrqhRF4wvl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mjdExZHrqhRF4wvl .rough-node .label text,#mermaid-svg-mjdExZHrqhRF4wvl .node .label text,#mermaid-svg-mjdExZHrqhRF4wvl .image-shape .label,#mermaid-svg-mjdExZHrqhRF4wvl .icon-shape .label{text-anchor:middle;}#mermaid-svg-mjdExZHrqhRF4wvl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mjdExZHrqhRF4wvl .rough-node .label,#mermaid-svg-mjdExZHrqhRF4wvl .node .label,#mermaid-svg-mjdExZHrqhRF4wvl .image-shape .label,#mermaid-svg-mjdExZHrqhRF4wvl .icon-shape .label{text-align:center;}#mermaid-svg-mjdExZHrqhRF4wvl .node.clickable{cursor:pointer;}#mermaid-svg-mjdExZHrqhRF4wvl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mjdExZHrqhRF4wvl .arrowheadPath{fill:#333333;}#mermaid-svg-mjdExZHrqhRF4wvl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mjdExZHrqhRF4wvl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mjdExZHrqhRF4wvl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mjdExZHrqhRF4wvl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mjdExZHrqhRF4wvl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mjdExZHrqhRF4wvl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mjdExZHrqhRF4wvl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mjdExZHrqhRF4wvl .cluster text{fill:#333;}#mermaid-svg-mjdExZHrqhRF4wvl .cluster span{color:#333;}#mermaid-svg-mjdExZHrqhRF4wvl 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-mjdExZHrqhRF4wvl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mjdExZHrqhRF4wvl rect.text{fill:none;stroke-width:0;}#mermaid-svg-mjdExZHrqhRF4wvl .icon-shape,#mermaid-svg-mjdExZHrqhRF4wvl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mjdExZHrqhRF4wvl .icon-shape p,#mermaid-svg-mjdExZHrqhRF4wvl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mjdExZHrqhRF4wvl .icon-shape .label rect,#mermaid-svg-mjdExZHrqhRF4wvl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mjdExZHrqhRF4wvl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mjdExZHrqhRF4wvl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mjdExZHrqhRF4wvl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 观测
驱动
块层
用户与FS
进程 / dd / 数据库
文件系统 / page cache
struct bio
submit_bio
blk-mq: tag + hctx
struct request
queue_rq
DMA / 仿真后端
complete / timeout
/sys/block/*/queue
blktrace / iostat

打开与容量侧:

text 复制代码
open("/dev/sdX") / 分区扫描
  → bdev open → disk->fops->open(若实现)
  → /sys/block/<disk>/size、logical_block_size、nr_requests
     决定可寻址范围与并发深度

卸载必须先停 I/O:

text 复制代码
冻结/排水队列 → del_gendisk → 释放 tag_set / 私有数据
  否则在飞 request 回调会 UAF

重点知识

1. bio 是通用货币,request 是队列侧封装

文件系统和页缓存不直接碰 NVMe/SCSI/virtio 寄存器;它们提交 bio(扇区范围 + 生物向量)。块层把 bio 映射成 request,再交给 blk_mq_ops.queue_rq。自写「伪块设备」若只实现了字符式 read 而没有挂上 mq/队列,/dev 节点即便存在也不会按块语义工作。

设计意图:用统一块层屏蔽后端差异,并让 elevator/io scheduler、合并、限流、cgroup 在一层完成。

2. queue_rq 要快,完成路径要成对

queue_rq 返回 BLK_STS_OK 表示「已接受」------通常意味着描述符已交给硬件或内部队列,而不是「数据已落盘」。真正结束靠中断/轮询线程里的 complete。忘记 blk_mq_complete_request,表现就是 await 永久上涨、进程 D 状态;重复 complete 则可能破坏 request 生命周期。

超时走 ops->timeout:硬件挂死后,内核靠它决定复位或错误结束,而不是让上层无限等。

踩坑 :在 queue_rq 里做大块 memcpy、拿可能睡眠的锁、或同步等硬件完成------会拖垮整条 mq 路径。重活放完成路径的线程上下文或独立 worker。

3. 容量、逻辑块、队列深度必须自洽

bash 复制代码
lsblk -t
cat /sys/block/<dev>/queue/logical_block_size
cat /sys/block/<dev>/queue/physical_block_size
cat /sys/block/<dev>/queue/max_sectors_kb
cat /sys/block/<dev>/queue/nr_requests
cat /sys/block/<dev>/size          # 历史语义:512B 扇区数
# 直接验证驱动本身(绕过 page cache)
dd if=/dev/zero of=/dev/<dev> bs=4k count=100 oflag=direct
# 追踪
sudo blktrace -d /dev/<dev> -o - | blkparse -i -
iostat -x 1

O_DIRECT 对齐失败、错误的 max_hw_sectors、逻辑块写成 512 而硬件是 4K,都会导致 EIO 或吞吐断崖。分区表/revalidate 时机不对时,用户态会读到旧 size。

Kconfig 侧确认 CONFIG_BLOCKCONFIG_BLK_MQ(现代默认)以及具体后端(如 CONFIG_BLK_DEV_LOOPCONFIG_VIRTIO_BLK)已开。

4. 注册与生命周期

device_add_disk 之后才会出现 /dev/sys/block。模块卸载顺序建议:

  1. 停止接受新 I/O(冻队列 / 置失效标志)
  2. del_gendisk
  3. 等待在飞请求结束
  4. blk_mq_free_tag_set(或等价释放)
  5. 释放私有 DMA/缓冲

踩坑 :只 module_exitkfree 私有结构,未摘盘------热路径回调仍可能进入。

5. 排障对照表

现象 优先检查
有节点,读写挂死 queue_rq 是否被调用;完成回调是否执行;/proc D 状态栈
await 高、吞吐低 队列深度、合并是否关闭、是否在 queue_rq 阻塞、中断亲和
EIO / 对齐错误 logical_block_sizeO_DIRECT 对齐、max_sectors
容量不对 set_capacity、分区重扫、size sysfs
卸载 oops 是否先 del_gendisk 再释放;是否有在飞 request
只缓冲 I/O「正常」 oflag=direct / fio direct 验证驱动本身

6. 性能要点(来自队列侧,而非盲目加大 bio)

  • 合理 nr_hw_queues(常贴近 CPU/硬件队列数),避免单队列锁争用
  • 允许合并(除非设备语义禁止),减少 doorbell
  • 完成路径尽量 blk_mq_complete_request 批处理友好,避免在硬中断里做重活
  • blktrace 看 Q/D/C 事件间隔,区分「提交慢」与「完成慢」

Checklist

  • 能指出 submit_bioblk_mq_submit_bioqueue_rqcomplete 的文件级路径
  • ls /sys/block/<dev>/ 存在,size 与驱动上报容量一致
  • queue_rq 与完成/超时回调成对;刺激 I/O 后无永久 D 状态读进程
  • logical_block_size / max_sectors 与硬件或仿真后端匹配
  • iostat / blktrace 能看到请求下发与完成,而非只在 FS 层空转
  • dd ... oflag=direct 或 fio direct 验证过驱动路径
  • 卸载路径先停 I/O 再 del_gendisk,无 UAF
  • 未在 queue_rq 中长时间阻塞或睡眠

小结

块驱动的核心不是「会写寄存器」,而是 把统一块层的 bio/request 契约接完整 :注册出正确容量的 gendisk,在 queue_rq 快速提交,在完成路径成对结束,并用 sysfs/blktrace 证明请求真的进出硬件。抓住这条链,存储类挂死与 await 飙高的问题大多能在驱动侧几分钟内定位。

相关推荐
不吃鱼的羊1 小时前
Davinci Developer的IRV
服务器
德迅云安全杨德俊1 小时前
裸金属服务器的运用与适用场景
运维·服务器
chinesegf1 小时前
现代互联网服务的经典架构
服务器·架构·llama
Rsingstarzengjx1 小时前
第二十六章 Linux 蜂鸣器实验笔记
linux·驱动开发·笔记·stm32·嵌入式硬件·stm32mp157
wdfk_prog2 小时前
Docker 29 与 containerd image store:镜像为什么会保存两种形态
运维·docker·容器
Java小学生丶2 小时前
[开源自荐] ZTShell,一个使用 Tauri 2 + Rust 全程由 AI 开发的跨平台桌面 SSH 工具
linux·网络·centos7·云服务器·开发技巧·资源分享·个人私货
广州灵眸科技有限公司2 小时前
灵眸科技EAI3572-Core-L核心板即将发布!八核+4TOPS NPU,面向工业与边缘AI
linux·运维·服务器·数据库·yolo
流浪0012 小时前
Linux系统篇28——通信(五):信号量专篇:从电影院订票到内核一张大表,把信号量一次讲透
linux·运维·服务器
码农小韩2 小时前
Linux应用开发(三)——Linux的文件属性与目录
linux·嵌入式软件开发·linux操作系统·嵌入式操作系统·linux应用开发