Linux 驱动基础(二):驱动自动加载的设计与实现

日期 :2026-09-12 · 参考源码版本:Linux 5.x
版权声明 本文为原创技术文章,著作权归作者所有。未经作者书面授权,严禁任何形式的转载、摘编、复制、建立镜像、商业使用

写在前面

一块嵌入式开发板配置了以模块形式(.ko)发布的时钟驱动(obj-m,而非编进内核镜像的 obj-y)。内核启动时解析设备树,枚举出一个 compatible 为 rockchip,rk3399-cru 的时钟控制器节点;此刻驱动代码不在内核镜像中,模块的查找与加载由用户态完成:内核就新设备向用户态发出 uevent,用户态的 udev 守护进程收到后自动调用 modprobe,modprobe 从 /lib/modules/<内核版本>/ 选中 clk-rk3399.ko,经 finit_module 系统调用将其载入内核。问题由此产生:modprobe 依据什么把一个设备对应到一个 .ko 文件? 设备的标识(compatible 字符串)与模块文件名之间没有任何天然联系,为此系统维护了一张"设备 → 模块"的对照表。

这张对照表不由任何人集中维护,而是由每个驱动自己申报------全部机制浓缩在一行宏里:

c 复制代码
static const struct of_device_id clk_rk3399_match_table[] = {
    { .compatible = "rockchip,rk3399-cru" },
    { .compatible = "rockchip,rk3399-pmucru" },
    { }                      /* 终止条目 */
};
MODULE_DEVICE_TABLE(of, clk_rk3399_match_table);

本文剖析 MODULE_DEVICE_TABLE 的完整链路:宏如何为静态数组生成可供检索的符号,构建工具 modpost/file2alias 如何把设备表翻译成 modalias 字符串,运行时内核与 udev/modprobe 如何接力完成自动加载。

阅读本文你将了解:

  • 一张设备 ID 表如何同时服务内核匹配与用户态自动加载,且数据始终只有一份
  • __mod_<type>__<name>_device_table 命名约定如何让构建工具检索到任意命名的静态数组
  • alias 属性为何能零成本地导出静态数据
  • modalias 字符串格式为何在两处代码中独立实现,又为何必须逐字符一致
  • 设备表为什么必须以全零条目结尾

一、问题:硬件身份与模块名之间的翻译

设备标识的形式由总线的枚举方式决定。PCI、USB 这类支持动态枚举的总线,在设备接入后从硬件寄存器中读出标识:PCI 设备是厂商号与设备号(如 xxxx:yyyy),USB 设备是厂商号与产品号(如 aaaa:bbbb)。SPI、I2C 等总线上的设备不具备自描述能力,由设备树静态描述,标识是源码中写明的 compatible 字符串(如 rockchip,rk3399-cru)。模块的标识是文件名(如 clk-rk3399.ko)。设备标识与模块文件名分属两套互不相关的命名体系,从任何一侧都无法推知另一侧,对照表因此必不可少。这张表的维护方式有几种可能:

  • 内核或发行版集中维护一张全局表:每支持一款新硬件都要修改公共文件,数万驱动的规模下不可行,且必然与驱动演进脱节
  • 每个驱动自行申报:驱动作者最清楚自己支持哪些设备,数据随驱动源码一起演进------问题是,驱动以什么形式"申报"?

关键在于:驱动本来就维护着一份现成的数据 。任何驱动要让总线完成设备与驱动的匹配,都必须提供一张设备 ID 表(如 clk_rk3399_match_table),逐条声明"我支持这些设备"。这份为内核匹配而存在的数据,恰好就是自动加载所需映射的来源。MODULE_DEVICE_TABLE 的全部设计,就是围绕"如何把这份现成数据交给用户态工具"展开,如图 1 所示。

设备树同样描述 PCI 总线(drivers/pci/controller/ 下的各主机驱动即按 compatible 匹配的 platform 驱动),但 PCI 总线上枚举出的设备仍以厂商号与设备号标识,匹配不经过设备树。

图 1:设备身份与模块名之间的翻译

graph LR subgraph HW[硬件身份] A[PCI xxxx:yyyy] B[USB aaaa:bbbb] C[设备树 compatible 字符串] end subgraph MD[模块名] D[e1000e.ko] E[usb-skeleton.ko] F[clk-rk3399.ko] end T[驱动内的设备 ID 表] A --> T B --> T C --> T T --> D T --> E T --> F

对照关系的唯一数据源是驱动自己的设备表:新增一款硬件支持,作者只改动这一处,内核匹配与自动加载同时受益。

二、核心设计思想

2.1 单一数据源:同一张设备表的两类读取方

设备 ID 表在生命周期中被读取两次:

  • 内核侧 :驱动注册时把表交给总线(如 device_driver.of_match_table 字段),总线匹配循环逐条比对,决定设备与驱动是否绑定
  • 用户态侧:构建期由 file2alias 读同一张表,为每条 ID 生成 modalias 字符串,供 modprobe 检索

单一数据源意味着映射永远不会与匹配规则脱节------这是整个机制最重要的不变量。

2.2 以命名约定实现符号检索

设备表是驱动内部的 static 数组,名字由作者随意起,外部工具无从知晓。宏要解决的第一个问题就是让工具能找到它 。做法不是引入注册 API,也不是复制数据,而是生成一个符合固定命名模式的别名符号__mod_<type>__<name>_device_table。工具扫描 ELF 符号表、按模式匹配即可,驱动侧没有任何额外动作。

2.3 modalias:内核与用户态之间的中间语言

构建期工具与运行时内核面对的数据形态不同:前者处理的是 .ko 文件与结构体数组,后者处理的是总线枚举出的设备对象。两者之间以一串文本交会------modalias,如 of:N*T*Crockchip,rk3399-cru。它是一种纯文本的约定格式,且在两处代码中独立实现(file2alias 生成、总线 uevent 回调生成),一致性完全依靠约定维系。

三、宏的实现:生成设备表的别名符号

c 复制代码
/* include/linux/module.h */
#ifdef MODULE
/* Creates an alias so file2alias.c can find device table. */
#define MODULE_DEVICE_TABLE(type, name)                    \
extern typeof(name) __mod_##type##__##name##_device_table        \
  __attribute__ ((unused, alias(__stringify(name))))
#else  /* !MODULE */
#define MODULE_DEVICE_TABLE(type, name)
#endif

MODULE_DEVICE_TABLE(of, clk_rk3399_match_table),宏声明了一个外部符号 __mod_of__clk_rk3399_match_table_device_table------它不分配任何存储,仅是指向既有设备表的别名。逐项看这个声明:

  • alias(__stringify(name)) :GCC 的别名属性,令新符号成为 clk_rk3399_match_table 的别名。别名不复制任何数据,只是在符号表中多出一个全局符号(STT_OBJECT),地址与大小均与原数组一致。GCC 要求别名目标定义在同一编译单元------这正是设备表与宏调用必须写在同一源文件中的原因
  • extern typeof(name):别名声明沿用设备表的完整类型。若表被误定义为其他类型,此处直接编译报错,宏对数据类型做了一道静态检查
  • unused:该符号没有任何代码引用,属性用于抑制编译器的未使用告警
  • 符号名中的 type :这是给下游工具的"解释说明"。每种总线的设备表结构体不同(pci_device_idusb_device_idof_device_id......),工具必须知道按哪个结构体解释字节流,总线类型因此被编码进符号名

图 2:别名符号与设备表共享同一份数据

graph LR SRC[驱动源文件 clk-rk3399.c] TBL[static 数组 clk_rk3399_match_table] AL[全局别名符号 __mod_of__clk_rk3399_match_table_device_table] SYM[.ko 符号表] SRC --> TBL TBL -.->|同一地址与大小| AL AL --> SYM SYM -.->|附带 st_size| TOOL[构建期工具按符号定位数据]

别名符号在符号表中携带 st_value(节内偏移)与 st_size(字节数),下游工具仅凭符号表条目即可定位并读取整张表------无需任何运行期数据。

四、构建期:modpost 与 file2alias

4.1 符号表扫描与设备表定位

模块链接为 .ko 后,Kbuild 调用 modpost 做后处理。modpost 遍历符号表的每个符号,交给 handle_moddevtablescripts/mod/file2alias.c):

c 复制代码
/* scripts/mod/file2alias.c */
/* All our symbols are of form __mod_<name>__<identifier>_device_table. */
if (strncmp(symname, "__mod_", strlen("__mod_")))
    return;

它按 __mod_ 前缀、_device_table 后缀、中间双下划线分隔的约定解析出总线类型与表名;要求符号是节内对象(STT_OBJECT),再用 st_valuest_size 定位数据。随后按总线类型查 devtable[] 分发表:

c 复制代码
/* scripts/mod/file2alias.c */
static const struct devtable devtable[] = {
    {"hid", SIZE_hid_device_id, do_hid_entry},
    {"pci", SIZE_pci_device_id, do_pci_entry},
    {"usb", SIZE_usb_device_id, do_usb_entry},     /* 特例,单独处理 */
    {"of",  SIZE_of_device_id,  do_of_entry},      /* 特例,单独处理 */
    {"i2c", SIZE_i2c_device_id, do_i2c_entry},
    {"spi", SIZE_spi_device_id, do_spi_entry},
    {"platform", SIZE_platform_device_id, do_platform_entry},
    /* ... 共四十余种总线 ... */
};

每行登记一种总线:结构体尺寸(SIZE_xxx,用于按条目切分字节流)与解释函数(do_xxx_entry,从结构体字段生成 modalias)。usb 与 of 因语义复杂(匹配标志位、多条 compatible)在 handle_moddevtable 中走特例分支,不经此表。

这里存在一份结构体布局契约 :file2alias 以用户态程序的身份直接包含 include/linux/mod_devicetable.h(源码注释称其为"don't include kernel headers into userspace"规则的重大例外),字段偏移则由构建期生成的 devicetable-offsets.h 提供。设备表结构体的布局因此是内核与构建工具之间的 ABI,改动布局必须两端同步。

4.2 全零终止条目:双向依赖的约定

do_table 逐条解释设备表时,有一条固定动作:

c 复制代码
/* scripts/mod/file2alias.c */
/* Leave last one: it's the terminator. */
size -= id_size;

无条件丢弃最后一条 ,不检查其内容是否为零。这不是疏忽,而是与内核侧约定的一致行为:总线匹配循环同样以全零条目为终点------PCI 匹配循环推进到 id->vendor == 0 停止,USB 匹配循环推进到 id->match_flags == 0 停止。file2alias 另有 device_id_check 辅助校验:表大小必须是结构体尺寸的整数倍(违反则构建失败),末条是否全零则仅告警。

⚠️ 终止条目 { }{ 0, } 不是可选项。缺了它,内核匹配循环将越界读取;多了空行无碍,但工具永远假定"最后一条是终止符"。

4.3 生成 MODULE_ALIAS

设备表的每条非终止条目,都由解释函数从结构体字段拼出一个 modalias 字符串,随后 file2alias 为其生成一条 MODULE_ALIAS 语句------do_table 中的这行代码是整条转换链的终点:

c 复制代码
/* scripts/mod/file2alias.c */
if (do_entry(mod->name, symval+i, alias)) {
    buf_printf(&mod->dev_table_buf,
               "MODULE_ALIAS(\"%s\");\n", alias);
}

缓冲区积累的语句由 modpost 写入每个模块的 <module>.mod.c 文件(add_moddevtable(&buf, mod)scripts/mod/modpost.c)。以 RK3399 时钟驱动(drivers/clk/rockchip/clk-rk3399.c)为例,其设备表含 rockchip,rk3399-cru 等两条 compatible,生成的文件中会出现:

c 复制代码
/* clk-rk3399.mod.c(节选,自动生成) */
MODULE_ALIAS("of:N*T*Crockchip,rk3399-cru");
MODULE_ALIAS("of:N*T*Crockchip,rk3399-cruC*");
/* rockchip,rk3399-pmucru 同理,共四条 */

<module>.mod.c 被编译为 .mod.o 并链入 .koMODULE_ALIAS 的定义是 MODULE_INFO(alias, _alias),于是每条 modalias 最终成为 .modinfo 段中的一条 alias=... 字符串。安装模块时,用户态的 depmod 收集所有 .koalias= 条目,汇总为 modules.alias 数据库。完整数据流如图 3 所示。

图 3:构建期的数据流

sequenceDiagram participant KB as Kbuild participant MP as modpost participant F2A as file2alias participant SRC as clk-rk3399.mod.c participant KO as clk-rk3399.ko participant DEP as depmod KB->>MP: 后处理已链接的模块 MP->>F2A: 逐符号调用 handle_moddevtable F2A->>F2A: 按结构体解释设备表生成 modalias F2A-->>MP: 返回缓冲的 MODULE_ALIAS 语句 MP->>SRC: 写入 mod.c 文件 KB->>KO: 编译 mod.c 为 mod.o 并重新链接 KO->>DEP: 安装时读取 .modinfo 的 alias 条目 DEP->>DEP: 汇总为 modules.alias 数据库

4.4 modalias 格式一览

modalias 是 <总线前缀>:<字段序列> 的文本,字段未指定时以 * 通配。下表列出本文涉及的总线,表中各串均由 file2alias 在构建期从驱动源码的设备表生成,语义是"驱动声明支持的设备集合":

总线 设备表结构体 生成的 modalias 示例
of of_device_id of:N*T*Crockchip,rk3399-cruof:N*T*Crockchip,rk3399-cruC*
spi spi_device_id spi:<设备名>
platform platform_device_id platform:<设备名>

其中 of 的格式需要单独说明。它由三段构成:of:N 设备树节点名、T 设备类型、C compatible 字符串,未指定的段以 * 占位------of:N*T*Crockchip,rk3399-cru 即"任意节点、任意类型、compatible 为 rockchip,rk3399-cru 的设备"。

of 的复杂之处在于:同一格式有两个互不知晓的生成方 ,分别读不同的数据。file2alias(构建期)读驱动设备表,为每条 compatible 生成精确与通配两个别名 (模式);内核运行时读设备树节点自身的 compatible 属性,生成设备的 modalias(被匹配串)。设备树节点可携带多条 compatible,内核侧生成的 modalias 将节点上的全部 compatible 依次以 C 为分隔拼接。以 RK3399 的 SD 卡控制器节点为例(rk3399.dtsi):

dts 复制代码
sdmmc: dwmmc@fe320000 {
    compatible = "rockchip,rk3399-dw-mshc",
                 "rockchip,rk3288-dw-mshc";
    /* ... */
};

该节点注册时,内核为其生成的 modalias 为 of:NsdmmcT*Crockchip,rk3399-dw-mshcCrockchip,rk3288-dw-mshc------两条 compatible 全部列出。自动加载的匹配发生在 modprobe 一侧,以整串通配匹配(fnmatch)进行:别名是模式,节点的 modalias 是被匹配的字符串,模式中的 * 可匹配任意内容,模式结束处要求字符串同时结束。

dw_mci_rockchip_matchdrivers/mmc/host/dw_mmc-rockchip.c)声明 rockchip,rk3288-dw-mshc 为例------file2alias 为这条 compatible 生成两个别名,它们对上述节点的命中情况不同:

  • 精确别名 of:N*T*Crockchip,rk3288-dw-mshc命中rk3288-dw-mshc 恰好是节点列表的最后一项,其前的 Crockchip,rk3399-dw-mshc 由模式中 T** 匹配,模式与字符串同时结束
  • 通配别名 of:N*T*Crockchip,rk3288-dw-mshcC*不命中 。它要求该项之后还有以 C 开头的内容,而该节点到此结束

通配别名服务的是另一种排布:驱动声明的 compatible 位于节点列表非末项 ------该项之后还排有其他 compatible,节点 modalias 在该项之后继续以 C 拼接。此时精确别名因"模式结束而字符串未结束"而失败,末尾的 C* 恰好匹配余下内容。设备树的惯例是把新 SoC 的 compatible 排在前面、旧 SoC 的作为回退排在后面,两种排布在真实硬件上都存在,因此 file2alias 为每条 compatible 生成精确与通配两个别名,覆盖全部情形。

五、运行时:从 uevent 到 probe

5.1 内核侧:uevent 携带 MODALIAS

PCI/USB 设备由总线在插入时枚举发现;不具备自描述能力的设备(如时钟控制器、SPI 外设)由设备树描述,在内核解析设备树、注册 platform 设备时进入系统。两类设备的注册都会经由 kobject 机制向用户态发出 uevent。platform 总线的 uevent 回调对设备树描述的设备优先生成 OF 风格的 modalias:

c 复制代码
/* drivers/base/platform.c */
static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    struct platform_device *pdev = to_platform_device(dev);
    int rc;

    /* Some devices have extra OF data and an OF-style MODALIAS */
    rc = of_device_uevent_modalias(dev, env);
    if (rc != -ENODEV)
        return rc;
    /* ACPI 分支略 */
    add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
                   pdev->name);
    return 0;
}

of_device_uevent_modaliasdrivers/of/device.c)从设备节点的 compatible 属性生成 modalias------设备节点 compatible 为 rockchip,rk3399-cru 时,uevent 携带 MODALIAS=of:N*T*Crockchip,rk3399-cru。无 OF 数据的设备回退到名称匹配的 platform: 前缀。file2alias 侧与之对应的 do_of_entry_multi 从设备表读出 compatible 字段,生成同一格式的别名串。这是同一格式的两份独立实现------内核侧用真实设备的字段填值,构建侧用设备表的字段生成模式串。两处代码没有任何编译期或运行期的关联,格式一致性完全依靠约定,是整条链路中最脆弱的一环。

设备树描述的设备在内核启动早期注册(of_platform_default_populate_initarch_initcall_sync 级别),而 udev 要到用户态初始化阶段才启动------uevent 发出时往往没有监听者。这不是缺陷:uevent 经 netlink 广播,无接收者时消息直接丢弃。补偿机制是冷插拔 (coldplug):device_add()drivers/base/core.c)为每个注册的设备在 sysfs 目录下创建 uevent 属性文件,udev 启动后执行 udevadm trigger,令内核对已注册的全部设备重新广播 uevent,早期丢失的事件由此补发。自动加载的时点因此从"设备注册时"推迟到"udev 完成冷插拔扫描时";若某设备的驱动须在启动早期即就绪,应将其改为 obj-y 内建,而非依赖模块自动加载。

5.2 用户态:modprobe 查表加载

udev 收到 uevent 后自动调用 modprobe <modalias>。modprobe 把参数当作别名,在 modules.alias 中查找匹配项(支持 * 通配),命中 clk-rk3399 后通过 finit_module 系统调用加载 clk-rk3399.ko------即标准的模块加载路径。

5.3 加载之后:回到内核匹配

模块的 init_module 执行后,驱动向总线注册,platform_driver.driver.of_match_table 指向的还是那张设备表。总线匹配循环(platform_match)先尝试 OF 与 ACPI 匹配------OF 匹配即比对设备节点的 compatible 与 of_match_table 中的各条 compatible------比对成功即绑定设备与驱动并调用驱动的 probe 函数。至此设备表的两次消费全部完成,如图 4 所示。

图 4:自动加载的完整时序

sequenceDiagram participant DEV as 设备树节点 rockchip,rk3399-cru participant K as 内核 platform 总线 participant U as udev participant M as modprobe participant L as 模块加载器 DEV->>K: 解析设备树注册 platform 设备 K->>U: uevent 携带 MODALIAS=of:N*T*Crockchip,rk3399-cru U->>M: modprobe of:N*T*Crockchip,rk3399-cru M->>M: 查 modules.alias 命中 clk-rk3399 M->>L: finit_module 加载 clk-rk3399.ko L->>K: 执行 init_module 注册 platform_driver K->>K: platform_match 比对 compatible 与 of_match_table K->>K: 匹配成功调用 probe

构建期 file2alias 读一次表(生成别名数据库),运行期总线匹配循环反复读同一张表(绑定设备)。驱动作者维护的始终只有一处数据。

六、内核中的实例

以下三个实例均源自 linux 内核,前两例展示 PCI、USB 总线的典型写法,第三例即贯穿全文的 RK3399 时钟驱动。

实例一:e1000e 网卡驱动(PCI)

c 复制代码
/*drivers/net/ethernet/intel/e1000e/netdev.c*/
static const struct pci_device_id e1000_pci_tbl[] = {
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82571EB_COPPER), board_82571 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82571EB_FIBER), board_82571 },
    /* ... */
    { 0, 0, 0, 0, 0, 0, 0 }    /* terminate list */
};
MODULE_DEVICE_TABLE(pci, e1000_pci_tbl);

表中 PCI_VDEVICE 宏展开后填入厂商号与设备号;board_82571 填充的是 driver_data 私有字段,probe 函数可据此区分同一张表内不同代际的硬件。内核侧的匹配经驱动结构体的 .id_table = e1000_pci_tbl 字段完成关联。

实例二:USB 驱动(USB)

c 复制代码
/*drivers/usb/usb-skeleton.c*/
static const struct usb_device_id skel_table[] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                    /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, skel_table);

表中 USB_DEVICE 宏只声明厂商ID与产品ID。

实例三:RK3399 时钟驱动(OF)

c 复制代码
/*drivers/clk/rockchip/clk-rk3399.c*/
static const struct of_device_id clk_rk3399_match_table[] = {
    {
        .compatible = "rockchip,rk3399-cru",
        .data = &clk_rk3399_cru_init,
    },  {
        .compatible = "rockchip,rk3399-pmucru",
        .data = &clk_rk3399_pmucru_init,
    },
    { }
};
MODULE_DEVICE_TABLE(of, clk_rk3399_match_table);

static struct platform_driver clk_rk3399_driver = {
    .driver     = {
        .name           = "clk-rk3399",
        .of_match_table = clk_rk3399_match_table,
        .suppress_bind_attrs = true,
    },
};

同一张表身兼两职:经 .of_match_table 交给 platform 总线做匹配,经 MODULE_DEVICE_TABLE 生成 modalias 供自动加载。两条 compatible 分别对应 RK3399 的主时钟控制器(cru)与电源管理时钟控制器(pmucru),.data 字段携带各自的时钟初始化数据------probe 函数用 of_device_get_match_data() 取回,据此对两款控制器执行不同的初始化路径。

三个实例的终止条目写法各异------{ 0, 0, ... }{ }------语义完全等价:结构体未显式初始化的字段默认为零,只要整条条目全零即可。

⚠️ 所有设备表必须以全零条目结尾,内核匹配循环与 file2alias 双方依赖此约定;file2alias 无条件跳过最后一条。

七、设计哲学小结

  1. 复用既有数据,不建立第二份对照表。设备表身兼内核匹配与自动加载两职,映射与匹配规则天然同步,不存在可能漂移的第二数据源
  2. 命名约定加符号表扫描,是成本最低的导出接口 。一个固定模式的符号名替代了注册 API、导出函数与额外存储;alias 属性让静态数据零成本获得一个全局可检索的名字
  3. 用文本中间语言解耦两端。modalias 让内核、kmod、udev 各自独立演进,互不知晓对方实现;代价是格式约定分散在两处代码中,属隐性契约
  4. 约定的执行强度可以分级。别名符号模式由 modpost 严格解析,结构体尺寸不符直接构建失败;终止条目全零却只有告警------强约定配强检查,弱约定依赖文档与作者自律,这是整套机制里并不完美但被实践接受的一处

附录 A:延伸阅读

  • include/linux/module.h --- MODULE_DEVICE_TABLEMODULE_ALIAS 定义
  • include/linux/mod_devicetable.h --- 各总线设备表结构体
  • scripts/mod/modpost.c --- 符号表扫描、<module>.mod.c 生成
  • scripts/mod/file2alias.c --- 设备表 → modalias 的全部解释函数
  • drivers/pci/pci-driver.c --- PCI 侧 uevent 生成与 pci_match_device 匹配循环
  • drivers/of/device.c --- OF 侧 of_device_uevent_modalias

版权声明(重申) 本文为原创技术文章,著作权归作者所有。未经作者书面授权,严禁任何形式的转载、摘编、复制、建立镜像、商业使用

Copyright © 2026. All rights reserved. Unauthorized reproduction, distribution, or commercial use is strictly prohibited.

相关推荐
yyk333241 小时前
Linux常见的基础命令
linux·运维·服务器
启博网络2 小时前
实战:无公网IP环境下的企业异地组网方案(附拓扑与IP规划)
linux·网络·tcp/ip
Soari3 小时前
Linux 内核对比:PREEMPT_DYNAMIC vs PREEMPT_RT
linux·运维·ubuntu
Dr_Fourier3 小时前
mmap的使用与底层原理
linux·c++
linx2955 小时前
单元五 · 对称认知·下:编译与链接
linux·c语言·开发语言·c++·嵌入式硬件
橘色的喵5 小时前
Claude Code 状态栏脚本:一行显示模型、上下文占用、git 分支和当前目录
linux·git·bash·claude
Jason_zhao_MR5 小时前
新国标下DTUFTU设计最优解——米尔基于全志T153核心板_排版优化
linux·单片机·架构·t113i·双处理器·mcu+linux·电力采集
程序员-Benothing5 小时前
Linux 用户与用户组管理:useradd usermod groupadd 实战
linux·运维·服务器
byte轻骑兵5 小时前
【BlueZ 】util 模块:通用工具函数,源码中高频复用的基础组件
linux·人工智能·bluez·电脑蓝牙·嵌入式蓝牙