尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux字符设备驱动开发实战:从框架到设备树与I2C

Linux字符设备驱动开发实战:从框架到设备树与I2C 1. 从点亮一块板子说起字符设备驱动为什么是入门必修课我最早接触Linux设备驱动是在一块老掉牙的ARM9开发板上。那时候什么都不懂只知道照着教程敲代码先写一个hello world模块然后insmod、rmmod看着dmesg里打印出两行字觉得自己已经会写驱动了。后来真正开始做项目才知道那连入门都算不上。Linux设备驱动开发这个领域表面上是写C代码实际上是在跟内核的运作方式打交道跟硬件的数据手册打交道跟设备树里每一个节点属性打交道。等你把这些串起来的时候才算真正入了门。这篇文章不是从头讲语法、讲内核源码分析而是以我实际做项目过程中的思路为主线从字符设备驱动框架开始到设备树配置、file_operations数据通路、I2C设备驱动实战再到调试和排错把一条完整的嵌入式Linux驱动开发链路过一遍。适合刚接触嵌入式Linux、准备做驱动方向或者已经在写应用层想往底层走一步的朋友参考。看完你应该能独立搭出一个可用的驱动框架也大致知道出了问题该往哪个方向查。为什么大家都建议从字符设备驱动入手因为大部分简单外设都属于字符设备LED、按键、GPIO、I2C传感器、SPI Flash、串口等等它们的数据是按字节流访问的没有复杂的缓冲区和随机访问要求。更重要的是字符设备驱动的框架短小精悍可以把驱动到底是怎么被内核加载的应用层open/read/write是怎么一步步跑到驱动函数里的数据和指针是怎么在内核态和用户态之间跨越的这几个核心问题全部讲清楚。把这条链路打通了后面不管是块设备、网络设备还是总线驱动都只是在这个基础上换一套接口模型而已。在开始动手之前先明确一个概念驱动不是程序。程序有main函数驱动没有程序跑在用户态驱动跑在内核态程序出错最多崩自己驱动出错可能直接panic整个系统。写驱动的时候始终要记住这一点很多新手踩坑都是因为把写应用层程序的习惯带进了内核模块。2. 环境准备与最小字符设备驱动框架搭建2.1 必备的开发环境清单先说环境因为我在实际中见过太多人在这一步卡住然后误以为自己不适合写驱动。其实驱动开发的环境要求很简单需要的不是最新最强的工具链而是版本匹配——内核源码树和交叉编译工具链必须对应目标平台这一步错了后面全是白费劲。Linux主机一台虚拟机也可以但真要调试硬件的话建议原生Linux目标板子对应的内核源码并确保该源码已经编译通过交叉编译工具链如果目标平台是x86直接用gcc就行根文件系统里带好insmod/rmmod/dmesg这些基础工具如果是纯软件环境学习比如想先在PC上跑通驱动逻辑完全可以在自己的Linux发行版上装linux-headers包然后编译内核模块加载到当前系统里。这样调试起来最方便dmesg、/proc/devices、/sys/class这些都能直接看。我第一次在PC上写驱动就是这么干的虽然加载的是自己电脑的内核模块但该学到的机制一样不少。内核源码树准备好之后建议先确认一件事/lib/modules/$(uname -r)/build这个软链接指到哪了。如果是PC环境apt install linux-headers-$(uname -r) 就行如果是交叉编译Makefile里要用ARCHarm CROSS_COMPILEarm-linux-gnueabihf-指定架构和工具链前缀。注意模块编译时用的内核源码必须跟目标板子上运行的内核是同一份版本号差一个patch都不行这是新手最容易犯的错。2.2 手写一个带完整生命周期的字符设备驱动直接上一个可以用的最小框架这段代码就是字符设备驱动的骨架#include linux/init.h #include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/uaccess.h #define DEVICE_NAME mydrv #define CLASS_NAME mychrdev static int major; static struct class *my_class; static struct cdev my_cdev; static int mydrv_open(struct inode *inode, struct file *filp) { printk(KERN_INFO mydrv: open\n); return 0; } static ssize_t mydrv_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { char kernel_buf[64] hello from kernel\n; size_t len strlen(kernel_buf); if (count len) return -EINVAL; if (copy_to_user(buf, kernel_buf, len)) return -EFAULT; return len; } static ssize_t mydrv_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
返回列表