23.1 迁移总览
| 维度 |
4.0 |
7.2.5 |
影响 |
| 硬件描述 |
板级文件 + 部分设备树 |
设备树/ACPI 唯一主线 |
板级文件全部删除 |
| 编译器 |
GCC 4.x |
GCC 11+ / Clang(LLVM=1) |
新警告默认开启 |
| C 标准 |
GNU89/GNU11 |
GNU11 |
变量声明位置、fallthrough |
| 模块签名 |
可选 |
常用(Secure Boot) |
sign-file |
| 符号版本 |
CRC32 |
CRC32 或 DWARF(CONFIG_GENDWARFKSYMS) |
Rust 必需 |
| 构建 |
-C $KDIR M=$PWD |
-f $KDIR/Makefile M=$PWD(6.13+) |
外部模块 Makefile |
| 字符设备 |
cdev + file_operations |
同,但 class_create(name) 单参数 |
见 23.3 |
| 中断 |
request_irq + tasklet |
threaded IRQ + 工作队列 |
tasklet 仅维护旧代码 |
| 定时器 |
init_timer() |
timer_setup() |
init_timer 已删除 |
| 块设备 |
请求队列 |
blk-mq |
旧接口删除 |
| 网络 |
netdev_ops 雏形 |
NAPI + DMA + XDP + ethtool_ops |
见 23.6 |
| 总线 |
i2c_new_device |
i2c_new_client_device |
见 23.7 |
| GPIO |
整数 GPIO |
描述符 API |
见 23.8 |
| 电源 |
手写 clk/regulator |
CCF/regulator/OPP/genpd |
见 23.9 |
| 调试 |
printk + GDB |
+ dynamic debug/ftrace/perf/eBPF/KASAN |
见第 21 章 |
23.2 编译与构建
23.2.1 外部模块 Makefile
# ---- 4.0 时代 ----
# KDIR ?= /lib/modules/$(shell uname -r)/build
# all:
# $(MAKE) -C $(KDIR) M=$(PWD) modules
# ---- 7.2.5 推荐(6.13 起支持 -f)----
obj-m += mydrv.o
KDIR ?= ~/works/study/build/linux-7.2.5-x86_64
PWD := $(shell pwd)
all:
$(MAKE) -f $(KDIR)/Makefile M=$(PWD) modules
clean:
$(MAKE) -f $(KDIR)/Makefile M=$(PWD) clean
-f 写法与 -C 等价,但不切换工作目录,路径处理更直观。
23.2.2 内核编译
# 4.0 时代
# make menuconfig && make -j8
# 7.2.5 推荐(外部构建 + 配置片段)
export KSRC=~works/study/linux-7.2.5
export KBUILD=~works/study/build/linux-7.2.5-x86_64
make -C "$KSRC" O="$KBUILD" x86_64_defconfig
"$KSRC/scripts/kconfig/merge_config.sh" -O "$KBUILD" \
"$KBUILD/.config" "$KSRC/kernel/configs/kvm_guest.config"
make -C "$KSRC" O="$KBUILD" olddefconfig
make -C "$KSRC" O="$KBUILD" -j"$(nproc)"
# Clang/LLVM 构建
make -C "$KSRC" O="$KBUILD" LLVM=1 defconfig
make -C "$KSRC" O="$KBUILD" LLVM=1 -j"$(nproc)"
自带的配置片段 (kernel/configs/):kvm_guest.config、debug.config、 hardening.config、rust.config、x86_debug.config、nopm.config。
23.2.3 编译器/语言变化
| 变化 |
说明 |
应对 |
| GNU11 |
变量可在块中间声明 |
无(更好写) |
-Wimplicit-fallthrough |
switch 穿透要显式 |
加 fallthrough; |
-Wunused-but-set-variable |
未使用变量报错 |
删除 |
-Wformat-truncation |
可能截断的 snprintf |
检查缓冲区 |
strlcpy 移除 |
用 strscpy |
见 23.4 |
strncpy 不推荐 |
用 strscpy/strscpy_pad |
|
23.3 字符设备与设备模型
| 4.0 写法 |
7.2.5 写法 |
说明 |
class_create(owner, name) |
class_create(name) |
少一个参数 |
class_destroy(cls) |
class_destroy(cls) |
不变 |
device_create(cls, parent, devt, drvdata, fmt, ...) |
同 |
不变 |
struct file_operations |
同 |
但 ioctl 必须用 unlocked_ioctl |
proc_create(name, mode, parent, fops) |
proc_create(name, mode, parent, proc_ops) |
用 struct proc_ops |
create_proc_entry() |
已删除 |
用 proc_create() |
create_proc_read_entry() |
已删除 |
用 proc_create() + seq_file |
init_timer(&t) + t.function= |
timer_setup(&t, fn, flags) |
init_timer 已删除 |
struct device_driver(device.h) |
include/linux/device/driver.h |
头文件拆分 |
struct bus_type(device.h) |
include/linux/device/bus.h |
头文件拆分 |
struct class(device.h) |
include/linux/device/class.h |
头文件拆分 |
mod_devicetable.h 定义所有 id |
include/linux/device-id/*.h |
头文件拆分 |
struct miscdevice 无 nodename |
新增 nodename/mode |
见 include/linux/miscdevice.h |
7.2.5 头文件拆分的实际影响 :老代码 #include <linux/device.h> 仍然能编译(它会转发),但新代码应包含具体头文件:
#include <linux/device/class.h> /* struct class, class_create */
#include <linux/device/driver.h> /* struct device_driver */
#include <linux/device/bus.h> /* struct bus_type */
#include <linux/device-id/i2c.h> /* struct i2c_device_id */
23.4 内核编程通用 API
| 4.0 |
7.2.5 |
说明 |
strlcpy(dst, src, size) |
strscpy(dst, src, size) |
strlcpy 已移除 |
strncpy |
strscpy / strscpy_pad |
|
snprintf |
scnprintf(内核更常用) |
|
init_MUTEX() |
已删除 |
用 mutex_init() |
DECLARE_MUTEX |
已删除 |
用 DEFINE_MUTEX |
sleep_on() |
已删除 |
用等待队列 |
interruptible_sleep_on() |
已删除 |
用 wait_event_interruptible() |
check_region() |
已删除 |
用 request_region() |
get_user_pages() |
仍存在,但推荐 get_user_pages_fast() / pin_user_pages*() |
见 include/linux/mm.h |
| 手动加锁/解锁 |
guard(mutex)(&lock)、scoped_guard() |
include/linux/cleanup.h |
手动 kfree |
__free(kfree) |
include/linux/cleanup.h |
kmalloc + 检查 |
devm_kzalloc |
推荐 devm |
WARN_ON 大量使用 |
WARN_ON_ONCE 优先 |
避免日志风暴 |
7.2.5 的 cleanup.h(RAII 风格):
#include <linux/cleanup.h>
/* 自动释放锁 */
guard(mutex)(&my_lock);
/* 出作用域自动 unlock */
/* 自动 kfree */
struct my_data *d __free(kfree) = kzalloc(sizeof(*d), GFP_KERNEL);
/* 出作用域自动 kfree(d) */
/* 作用域内加锁 */
scoped_guard(mutex, &my_lock) {
...
}
23.5 中断、时钟与并发
| 4.0 |
7.2.5 |
说明 |
request_irq() |
request_threaded_irq() / devm_request_irq() |
推荐线程化 + devm |
| tasklet |
工作队列 / threaded IRQ |
tasklet 仅维护旧代码 |
init_timer() |
timer_setup() |
见 23.3 |
setup_timer() |
timer_setup() |
已删除 |
mod_timer() |
同 |
|
| hrtimer 手写 |
hrtimer_init() + hrtimer_start() |
不变 |
smp_mb__after_atomic() 等 |
同 |
内存屏障 API 稳定 |
atomic_dec_and_test() |
同 |
|
| 手写 RCU |
rcu_read_lock()/synchronize_rcu() |
不变 |
struct completion |
同 |
|
7.2.5 中断要点:
/* 推荐:devm + threaded */
ret = devm_request_threaded_irq(dev, irq,
my_hardirq_handler, /* 顶半部(可选 NULL) */
my_thread_fn, /* 底半部(可睡眠) */
IRQF_ONESHOT, "mydrv", priv);
IRQF_ONESHOT 表示顶半部返回 IRQ_WAKE_THREAD 后, 中断在底半部完成前保持屏蔽。
23.6 网络设备
| 4.0 |
7.2.5 |
说明 |
init_etherdev() |
已删除 |
用 alloc_etherdev() |
alloc_netdev() |
alloc_netdev() / alloc_etherdev_mqs() |
|
ndo_set_multicast_list |
ndo_set_rx_mode |
|
ndo_tx_timeout(dev) |
ndo_tx_timeout(dev, txqueue) |
多队列 |
ethtool get_settings |
get_link_ksettings |
|
netif_napi_add(dev, napi, poll, weight) |
netif_napi_add_weight() 或 netif_napi_add()(新签名) |
见 netdevice.h |
| 手动 NAPI 调度 |
napi_schedule() |
不变 |
eth_change_mtu |
已删除 |
用默认 MTU 处理 |
中断里 netif_rx() |
napi_gro_receive() |
GRO |
dev_kfree_skb() |
dev_kfree_skb_irq()(中断中) |
|
| 单队列 |
多队列 + netif_set_real_num_tx_queues() |
|
| 无 XDP |
XDP + AF_XDP |
7.2.5 特性 |
7.2.5 的 ndo_tx_timeout 签名(这是最常踩的坑):
/* 4.0 */
void my_tx_timeout(struct net_device *dev);
/* 7.2.5 */
void my_tx_timeout(struct net_device *dev, unsigned int txqueue);
23.7 I²C / SPI / USB
| 4.0 |
7.2.5 |
说明 |
i2c_driver.probe(client, id) |
probe(client) |
少一个参数 |
i2c_driver.remove 返回 int |
返回 void |
|
i2c_new_device() |
i2c_new_client_device() |
|
i2c_new_probed_device() |
i2c_new_scanned_device() |
|
i2c_add_adapter() 手动删 |
devm_i2c_add_adapter() |
推荐 |
| 裸 SMBus 调用 |
regmap |
推荐 |
struct spi_master |
struct spi_controller |
重命名 |
spi_alloc_master() |
spi_alloc_host() |
|
spi_register_master() |
spi_register_controller() |
|
spi_master_get/put |
spi_controller_get/put |
|
spi_driver.probe(spi) |
不变(单参数) |
|
spi_driver.remove 返回 int |
返回 void |
|
usb_driver.probe(intf, id) |
不变(两参数) |
注意与 i2c/spi 不同 |
usb_driver.disconnect |
返回 void |
不变 |
usb_buffer_alloc/free |
已删除 |
用 usb_alloc_coherent/free_coherent |
usb_alloc_urb |
不变 |
|
legacy gadget(g_serial 等) |
configfs + libcomposite |
|
| OTG transceiver |
role switch + Type-C |
|
注意 :usb_driver.probe 在 7.2.5 中仍然是两个参数 , 与 i2c_driver/spi_driver 的单参数 probe 不同。这是最容易搞混的地方。
23.8 GPIO / pinctrl / 时钟 / 电源
| 4.0 |
7.2.5 |
说明 |
gpio_request() |
devm_gpiod_get() |
整数接口在 CONFIG_GPIOLIB_LEGACY=n 时不可用 |
gpio_get_value() |
gpiod_get_value() |
|
gpio_set_value() |
gpiod_set_value() |
|
gpio_direction_output() |
gpiod_direction_output() |
|
of_get_named_gpio() |
devm_gpiod_get() |
|
include <linux/gpio.h> |
<linux/gpio/consumer.h> (消费者) <linux/gpio/driver.h>(驱动) |
gpio.h 已标注"must not be included" |
gpio_request_array() |
devm_gpiod_get_array() |
|
| 手动 pinctrl apply |
内核自动 pinctrl_bind_pins() |
|
clk_get() + clk_enable() |
devm_clk_get_enabled() |
|
clk_prepare() 手动 |
devm_clk_get_prepared() |
|
regulator_get() + enable |
devm_regulator_get_enable() |
|
dev_pm_opp_set_regulator() |
devm_pm_opp_set_config() |
|
pm_qos_add_request() |
cpu_latency_qos_add_request() |
|
pm_runtime_get_sync() |
pm_runtime_resume_and_get() |
失败自动回退 |
pm_runtime_enable() 手动 disable |
devm_pm_runtime_enable() |
|
手写 dev_pm_ops |
DEFINE_SIMPLE_DEV_PM_OPS() |
一行 |
GPIO 头文件的现代规则(源码里写得很明确):
| 场景 |
应包含 |
| 实现 GPIO 控制器 |
<linux/gpio/driver.h> |
| 使用 GPIO(消费者) |
<linux/gpio/consumer.h> |
| 使用遗留整数接口 |
<linux/gpio/legacy.h>(且需 CONFIG_GPIOLIB_LEGACY) |
23.9 块设备
| 4.0 |
7.2.5 |
说明 |
blk_init_queue() |
已删除 |
用 blk-mq |
struct request_queue 手动 |
blk_mq_alloc_tag_set() + blk_mq_alloc_disk() |
|
make_request_fn |
已删除 |
用 blk_mq_ops.queue_rq |
elv_add_request() |
已删除 |
|
blk_queue_make_request() |
用 blk_alloc_disk() 或 blk_mq_alloc_disk() |
|
add_disk() |
add_disk() / device_add_disk() |
|
bio_endio() |
同 |
|
submit_bio() |
同 |
|
struct block_device_operations |
同(submit_bio 成员可用) |
|
7.2.5 的现代块设备骨架:
struct blk_mq_tag_set tag_set = {
.ops = &my_mq_ops,
.nr_hw_queues = 1,
.queue_depth = 128,
.numa_node = NUMA_NO_NODE,
};
blk_mq_alloc_tag_set(&tag_set);
disk = blk_mq_alloc_disk(&tag_set, NULL);
disk->major = MY_MAJOR;
disk->first_minor = 0;
disk->fops = &my_fops;
disk->private_data = priv;
strscpy(disk->disk_name, "myblk", DISK_NAME_LEN);
set_capacity(disk, MY_SECTORS);
add_disk(disk);
23.10 迁移检查清单
从 4.0 代码迁移到 7.2.5 时,按此清单逐项检查:
init_timer() → timer_setup()
create_proc_entry() → proc_create() + struct proc_ops
class_create(owner, name) → class_create(name)
strlcpy → strscpy
i2c_driver.probe 改单参数,remove 改 void
spi_master → spi_controller,spi_alloc_master → spi_alloc_host
spi_driver.remove 改 void
ndo_tx_timeout 加 txqueue 参数
netif_napi_add 检查新签名
- 整数 GPIO →
gpiod_* 描述符
pm_qos_* → cpu_latency_qos_*
pm_runtime_get_sync → pm_runtime_resume_and_get
dma_request_slave_channel → dma_request_chan
dmaengine_terminate_all → dmaengine_terminate_sync
blk_init_queue → blk-mq
platform_driver.remove 返回 void
- 检查所有
remove 是否还有 int 返回值
- 用
checkpatch.pl 检查风格
- 在 QEMU 上跑一遍(第 22 章)
23.11 常见错误
| 错误 |
现象 |
修正 |
用 -C 且假设 cwd 不变 |
路径错 |
用 -f 或显式 cd |
class_create 两参数 |
编译错误 |
单参数 |
proc_ops 用 fops 成员名 |
编译错误 |
用 proc_open/proc_read |
strlcpy |
链接错误 |
strscpy |
init_timer |
编译错误 |
timer_setup |
platform_driver.remove 返回 int |
编译错误 |
返回 void |
| i2c probe 两参数 |
编译错误 |
单参数 |
| usb probe 一参数 |
编译错误 |
仍是两参数 |
spi_master |
编译警告 |
spi_controller |
| 用整数 GPIO |
编译失败 |
gpiod_* |
pm_qos_add_request |
编译失败 |
cpu_latency_qos_* |
blk_init_queue |
编译失败 |
blk-mq |
忘记 fallthrough; |
警告变错误 |
显式标注 |
23.12 本章实验清单
| 实验 |
目的 |
关键观察 |
| 编译 4.0 风格模块 |
看编译错误 |
逐条对照本章修复 |
| 字符设备迁移 |
掌握 class/proc 变化 |
编译通过 |
| I²C 驱动迁移 |
掌握 probe 签名 |
在 i2c-stub 上 probe |
| 网络驱动迁移 |
掌握 NAPI/ethtool |
ip link 正常 |
| GPIO 迁移 |
掌握描述符 |
gpiod_* 工作 |
| PM 迁移 |
掌握 PM QoS/Runtime PM |
runtime_status |
| checkpatch |
风格检查 |
无 error |
23.13 总结
- 4.0 → 7.2.5 最大变化是硬件描述从板级文件转向设备树/ACPI。
- 大量旧 API 被删除 而非仅弃用:
init_timer、create_proc_entry、 strlcpy、init_MUTEX、sleep_on、check_region、usb_buffer_alloc、 blk_init_queue、init_etherdev 等。
- probe/remove 签名 是高频踩坑点:
i2c_driver/spi_driver 是单参数 probe + void remove, usb_driver 仍是两参数 probe。
- GPIO 描述符 API 全面取代整数 GPIO;
linux/gpio.h 已标注禁止包含。
- regmap、devm_、cleanup.h(guard/__free) 是现代驱动的三大便利设施。
- blk-mq 是块设备的唯一路径;请求队列旧接口全部删除。
- 构建侧:
O= 外部构建 、-f $KDIR/Makefile M=$PWD 、 LLVM=1 、配置片段合并 是 7.2.5 的标准做法。
- 迁移后用
checkpatch.pl + QEMU 实验 双重验证(第 21、22 章)。