嵌入式 Linux 驱动概述:分类、内核模块、开发环境 嵌入式 Linux 驱动概述分类、内核模块、开发环境嵌入式 Linux 驱动是连接硬件和应用的桥梁。这篇从零讲起驱动是什么、分哪几类、内核模块机制、开发环境怎么搭带你入门 Linux 驱动开发。大家好我是黒漂技术佬。做嵌入式的早晚要碰驱动。应用层调用 open/read/write 就能操作硬件背后都是驱动在干活。驱动直接跟内核、跟硬件打交道门槛比应用层高但也更底层、更有意思。这个系列 12 篇从内核模块到字符设备、中断、定时器、并发同步、设备树、I2C/SPI最后来个传感器驱动实战。第一篇先讲基础驱动是什么、分类、内核模块机制、开发环境搭建。一、Linux 驱动是什么作用驱动程序是硬件和应用之间的桥梁应用程序 → 系统调用 → 驱动程序 → 硬件应用层用 open、read、write、ioctl 这些标准接口驱动层把这些操作翻译成硬件能懂的读写寄存器、中断处理等硬件实际干活的外设GPIO、I2C、SPI、显示屏、传感器……为什么需要驱动统一接口不管什么硬件应用层都用 open/read/write不用管硬件差异权限隔离硬件操作在内核态应用在用户态安全稳定复用性一套驱动可以给多个应用用二、Linux 驱动的分类Linux 驱动主要分三大类1. 字符设备驱动Character Device按字节流顺序读写的设备最常见、最简单的一类例子串口、GPIO、LED、按键、I2C 设备、SPI 设备设备文件在/dev/xxx主设备号次设备号标识2. 块设备驱动Block Device按块一般 512 字节或 4KB随机读写的设备主要是存储类设备例子硬盘、SD 卡、eMMC、U盘有缓存机制比字符设备复杂3. 网络设备驱动Network Device负责网络数据包收发不走/dev文件系统用 socket 接口例子以太网、WiFi最复杂的一类驱动其他子系统除了三大类还有很多专门的子系统input 子系统按键、触摸屏、鼠标framebuffer / DRM显示驱动V4L2摄像头驱动ALSA音频驱动USB 驱动USB 设备PCIe 驱动PCIe 设备pinctrl / GPIO 子系统引脚控制这些底层还是基于字符设备/平台设备但内核提供了专门的框架。本系列重点入门系列以字符设备驱动为主这是最基础、最常用的。学会了字符设备其他类别的驱动思路是通的。三、内核模块Kernel Module是什么Linux 驱动一般以内核模块的形式存在可以动态加载和卸载不用重新编译内核。# 加载模块insmod xxx.ko modprobe xxx# 卸载模块rmmod xxx# 查看已加载模块lsmod.ko文件就是内核模块文件。为什么用模块灵活需要的时候加载不用就卸载节省内存开发方便改了驱动重新加载就行不用重启内核可裁剪嵌入式系统按需加载减小内核体积最简单的内核模块#includelinux/init.h#includelinux/module.h// 模块加载时调用staticint__inithello_init(void){printk(KERN_INFOHello, Linux Kernel!\n);return0;}// 模块卸载时调用staticvoid__exithello_exit(void){printk(KERN_INFOGoodbye, Kernel!\n);}module_init(hello_init);// 注册入口函数module_exit(hello_exit);// 注册出口函数MODULE_LICENSE(GPL);// 许可证必须有MODULE_AUTHOR(Heipiao);MODULE_DESCRIPTION(Hello World Module);关键宏说明宏作用module_init模块加载时调用的函数module_exit模块卸载时调用的函数__init标记初始化函数加载完后释放内存__exit标记退出函数只在卸载时用MODULE_LICENSE模块许可证GPL 才能用 GPL 的内核函数MODULE_AUTHOR作者MODULE_DESCRIPTION描述MODULE_ALIAS别名printk内核里打印用printk不是 printf。带日志级别KERN_EMERG 紧急 KERN_ALERT 警报 KERN_CRIT 严重 KERN_ERR 错误 KERN_WARNING 警告 KERN_NOTICE 注意 KERN_INFO 信息 KERN_DEBUG 调试用dmesg看内核日志。四、驱动开发环境1. 硬件开发板入门推荐树莓派资料多社区大适合入门正点原子 / 野火 IMX6ULL国产教程多配套齐全RK3588性能强AI 相关开发常用QEMU 模拟没有板子也能学用虚拟机2. 交叉编译工具链在 x86 电脑上编译 ARM 平台的代码需要交叉编译器# ARM 32位arm-linux-gnueabihf-gcc# ARM 64位aarch64-linux-gnu-gcc3. Linux 内核源码编译驱动需要对应版本的内核源码或内核头文件版本要跟开发板上运行的内核一致。内核源码树/ ├── arch/ 架构相关 ├── drivers/ 驱动源码 ├── include/ 头文件 ├── fs/ 文件系统 ├── kernel/ 内核核心 ├── mm/ 内存管理 ├── net/ 网络 └── Makefile4. Makefile编译内核模块的 Makefile 很固定# 内核源码路径根据实际情况改 KERNELDIR : /path/to/linux-kernel # 当前目录 PWD : $(shell pwd) # 交叉编译器 CROSS_COMPILE : arm-linux-gnueabihf- ARCH : arm obj-m : hello.o # 要编译的模块 all: $(MAKE) -C $(KERNELDIR) M$(PWD) modules ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean执行make就能生成hello.ko。5. 加载测试# 传到开发板上scphello.ko root192.168.1.100:/tmp/# 开发板上加载insmod hello.kodmesg|tail# 看打印# 卸载rmmod hellodmesg|tail五、内核态 vs 用户态区别用户态内核态权限受限不能直接操作硬件最高权限操作所有硬件内存独立的用户空间共享内核空间APIC 库、系统调用内核函数printk, kmalloc…稳定性崩溃了只是进程挂了崩溃了整个系统挂调试gdb 方便调试麻烦kgdb / printk驱动运行在内核态驱动代码运行在内核态所以不能用 C 库函数printf、malloc 不行要用内核提供的函数printk、kmalloc、copy_to_user 等出 bug 可能导致内核 panic系统死机内存分配要小心不能泄漏六、驱动的基本结构一个典型的字符设备驱动包含1. 模块入口出口module_init(xxx_init);module_exit(xxx_exit);2. 设备号申请alloc_chrdev_region(devid,0,1,xxx);// 动态分配register_chrdev_region(devid,1,xxx);// 静态指定3. cdev 注册cdev_init(cdev,fops);// 绑定操作函数cdev_add(cdev,devid,1);// 加入内核4. 文件操作集合file_operationsstaticconststructfile_operationsxxx_fops{.ownerTHIS_MODULE,.openxxx_open,.releasexxx_release,.readxxx_read,.writexxx_write,.unlocked_ioctlxxx_ioctl,};应用层的 open/read/write 对应到驱动里的这些函数。5. 硬件操作读写寄存器、配置 GPIO、处理中断……跟具体硬件相关。6. 自动创建设备节点class_create();device_create();加载模块自动在/dev下创建设备文件不用手动 mknod。七、学习路线建议入门阶段内核模块 hello world字符设备驱动基本框架open/read/write 实现GPIO 驱动LED、按键进阶阶段中断处理定时器并发与同步自旋锁、信号量、互斥锁设备树深入阶段I2C 子系统SPI 子系统platform 总线输入子系统、Framebuffer 等这个系列就按这个顺序来从简单到复杂。八、常见坑坑 1内核版本不匹配编译驱动的内核源码版本必须跟开发板运行的内核版本一致不然加载会失败version magic 错误。坑 2printk 不一定立刻打印内核有缓冲不一定立刻输出。用dmesg看或者printk级别设高一点。坑 3在内核里用 C 库函数不能用 printf、malloc、strcpy 这些用户态函数要用内核对应的printk、kmalloc、strlcpy……坑 4用户空间指针直接访问应用传下来的指针用户空间地址不能直接在内核里读写要用 copy_from_user / copy_to_user。坑 5忘了加 MODULE_LICENSE没加 GPL 许可证很多内核函数用不了会报错 unknown symbol。坑 6驱动 bug 导致系统死机内核态出错直接 panic不像应用层只是段错误。调试要小心多打日志。九、本系列内容安排嵌入式 Linux 驱动概述分类、内核模块、开发环境内核模块开发module_init/module_exit 与模块机制字符设备驱动file_operations 与读写设备号与设备节点动态分配、udev 自动创建中断处理顶半部底半部、tasklet/workqueue定时器与延时内核定时器、hrtimer并发与同步自旋锁、信号量、互斥锁平台设备驱动platform bus 与设备树GPIO 子系统与 pinctrlI2C 设备驱动开发SPI 设备驱动开发实战传感器驱动从 0 到 1十、本篇小结Linux 驱动分三类字符设备字节流、块设备存储、网络设备网卡驱动以内核模块形式存在可动态加载卸载.ko 文件模块两个关键函数module_init加载、module_exit卸载驱动运行在内核态权限高但出错代价大不能用 C 库开发环境开发板、交叉编译器、内核源码树、Makefile字符设备驱动基本结构设备号 cdev file_operations 硬件操作学习路线模块 → 字符设备 → 中断/定时器 → 并发 → 设备树 → I2C/SPI → 实战下一篇讲内核模块开发深入 module_init/module_exit、模块参数、模块依赖、导出符号。我是黒漂技术佬。