第一章:内核模块基础
各位同学,欢迎来到《手机SoC神经网络加速器驱动开发指南》的第一章。
做NPU驱动,说白了就是跟Linux内核打交道。你想想看,一个神经网络加速器要跑起来,首先得让内核认识它、管理它。怎么做到?靠的就是内核模块。
我个人习惯把内核模块比作「乐高积木」。内核是那个大底板,模块就是一块块积木。你需要什么功能,就插什么模块。不需要了,拔掉就行。不用重新编译整个内核,也不用重启机器。嗯,这个设计思路,真的很妙。
核心要点:内核模块是Linux内核动态扩展能力的基石。在NPU驱动开发中,我们几乎所有的加速器控制逻辑,都会以内核模块的形式存在。
1.1 内核模块框架
一个最简单的内核模块,长什么样?我直接给你看代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init my_npu_init(void)
{
printk(KERN_INFO "NPU加速器模块加载成功\n");
return 0;
}
static void __exit my_npu_exit(void)
{
printk(KERN_INFO "NPU加速器模块卸载成功\n");
}
module_init(my_npu_init);
module_exit(my_npu_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("NPU加速器驱动模块示例");
这段代码,是所有NPU驱动的起点。我当年第一次写NPU驱动时,就是从这么个空壳子开始的。
注意看几个关键点:
- module_init 和 module_exit:这是内核模块的入口和出口。加载时调用init函数,卸载时调用exit函数。
- __init 和 __exit:这两个宏是给内核的提示。__init标记的函数,加载完后内核会把这段内存释放掉。省内存的小技巧。
- MODULE_LICENSE:必须声明许可证。不写的话,内核会报「tainted kernel」的警告。我在项目中遇到过,有些芯片厂商的闭源驱动用了非GPL许可证,结果内核社区的人都不愿意帮他们看问题。
我的习惯:每个模块文件开头,我都会写清楚MODULE_DESCRIPTION。半年后回头看代码,你会感谢自己写了这行注释。
1.2 模块加载与卸载
写好了模块代码,怎么让它跑起来?
编译:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
加载:
sudo insmod my_npu.ko
查看:
lsmod | grep my_npu
卸载:
sudo rmmod my_npu
这里有个坑,我曾经踩过。insmod加载模块时,如果依赖其他模块,你得先加载依赖。NPU驱动通常依赖几个基础模块,比如内存管理、中断控制器。顺序搞反了,加载就会失败。
注意:rmmod卸载模块时,如果模块正被其他进程使用,卸载会失败。NPU驱动里,如果推理任务还没跑完,你强行卸载,系统可能会崩溃。我建议在卸载前,先检查一下设备文件是否还在被占用。
为什么会这样?因为内核模块的生命周期,跟用户空间的进程是绑定的。你想想看,NPU正在算一个模型,你突然把驱动拔了,硬件寄存器全失效了,内核不panic才怪。
1.3 printk调试技巧
做驱动开发,调试手段很有限。没有gdb,没有断点。你靠什么?靠printk。
printk是内核版的printf。用法差不多,但有几个关键区别:
| 日志级别 | 宏定义 | 使用场景 |
|---|---|---|
| KERN_EMERG | 0 | 系统崩溃前,紧急信息 |
| KERN_ALERT | 1 | 需要立即处理 |
| KERN_CRIT | 2 | 严重错误 |
| KERN_ERR | 3 | 一般错误 |
| KERN_WARNING | 4 | 警告信息 |
| KERN_NOTICE | 5 | 正常但重要的信息 |
| KERN_INFO | 6 | 信息性消息 |
| KERN_DEBUG | 7 | 调试信息 |
我个人习惯,调试NPU寄存器读写时,用KERN_INFO。排查bug时,用KERN_DEBUG。生产环境里,只留KERN_ERR以上的日志。不然dmesg会被刷爆。
看日志的命令:
dmesg | tail -20
或者实时监控:
tail -f /var/log/kern.log
避坑指南:我曾经在NPU中断处理函数里用了printk。结果一触发中断,printk打印,控制台输出,中断又被触发...死循环了。记住,中断上下文里别用printk,除非你想看系统卡死。
printk还有个好处,可以加格式控制。比如打印寄存器地址:
printk(KERN_INFO "NPU reg 0x%08x = 0x%08x\n", reg_addr, reg_val);
嗯,这里要注意。内核里不能用%llx打印64位值?其实可以,用%llx就行。但有些老内核不支持,我建议统一用%pad打印物理地址,用%pap打印指针。
1.4 本章知识体系
为了让你更直观地理解本章内容,我画了一张图:
这张图把本章的三个核心知识点串起来了。模块框架是骨架,加载卸载是动作,printk是眼睛。三者缺一不可。
补充一句:实际项目中,NPU驱动模块往往不止一个文件。我习惯把硬件操作放一个文件,把上层接口放另一个文件。模块化设计,后期维护会轻松很多。
好了,第一章就到这里。记住,内核模块是你和NPU硬件之间的桥梁。桥搭得稳不稳,就看你这模块写得扎不扎实。
公众号:蓝海资料掘金营,微信deep3321