资讯动态

嵌入式学习:Linux驱动开发总结

发布时间:2026/10/16 8:44:32 来源:尧图企业网站定制
一、驱动分类类型访问方式例子字符设备按字节流LED、按键、串口块设备按块硬盘、SD 卡网络设备按包网卡驱动实验主要是字符设备。二、驱动的两种编译方式方式做法特点编译进内核obj-y开机自动加载改驱动要重编内核编译成模块obj-minsmod加载rmmod卸载方便调试开发阶段用模块方式。三、字符设备驱动核心概念概念说明设备号主设备号 次设备号标识驱动和设备cdev内核里描述字符设备的结构体file_operations描述驱动支持的操作设备文件/dev/下的文件应用通过它访问驱动应用层和驱动层的关系text应用层open(/dev/xxx) ↓ 系统调用 驱动层xxx_open() ↓ 操作硬件 硬件层GPIO / I2C / SPI应用调用open、read、write、close驱动里对应的函数就会被调用。四、字符设备驱动框架1. 注册流程固定text1. alloc_chrdev_region → 申请设备号 2. cdev_alloc → 申请 cdev 3. pcdev-ops fops → 绑定 file_operations 4. cdev_add → 注册字符设备记忆口诀号 → 存 → 绑 → 册。2. 卸载流程固定text1. cdev_del → 删除 cdev 2. unregister_chrdev_region → 释放设备号3. file_operationscstatic struct file_operations fops { .owner THIS_MODULE, .open xxx_open, .release xxx_release, .read xxx_read, .write xxx_write, };函数对应系统调用xxx_openopenxxx_releaseclosexxx_readreadxxx_writewrite4. 数据拷贝函数方向copy_from_user用户 → 内核writecopy_to_user内核 → 用户read内核不能直接访问用户空间必须用这两个函数。五、自动创建设备节点cstatic struct class *pclass; static struct device *pdevice; // 加载时 pclass class_create(THIS_MODULE, xxx); pdevice device_create(pclass, NULL, devno, NULL, xxx0); // 卸载时 device_destroy(pclass, devno); class_destroy(pclass);加载后/dev/xxx0自动创建不用mknod。六、并发控制机制特点原子操作简单计数自旋锁不睡眠关中断信号量可以睡眠互斥体可以睡眠最常用多进程同时操作设备时用互斥体保护临界区。七、sysfs 调试接口cstatic ssize_t xxx_show(struct device *dev, struct device_attribute *attr, char *buf) { ... } static ssize_t xxx_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { ... } static struct device_attribute attr __ATTR(attr, 0664, xxx_show, xxx_store); // 加载时 device_create_file(pdevice, attr); // 卸载时 device_remove_file(pdevice, attr);用户通过echo和cat直接控制设备不用写应用程序。八、设备树1. 为什么用设备树把硬件信息从驱动中独立出来实现设备与驱动分离。2. pinctrl 节点dtsiomuxc { pinctrl_xxx: xxxgrp { fsl,pins MX6UL_PAD_XXX__GPIOX_IOXX 0x10B0 ; }; };3. 设备节点dtsxxx { compatible vendor,xxx; pinctrl-names default; pinctrl-0 pinctrl_xxx; gpio-xxx gpioX Y 1; status okay; };4. 驱动里读设备树cplednode of_find_node_by_path(/xxx); ledgpiono of_get_named_gpio(plednode, gpio-xxx, 0);九、驱动通用框架核心1. 整体框架text头文件 ↓ 全局变量保存设备结构 ↓ 操作函数read / write / ioctl ↓ file_operations ↓ miscdevice ↓ probe匹配成功时调用 ↓ remove卸载时调用 ↓ 匹配表of_device_id xxx_device_id ↓ xxx_driver 结构体 ↓ xxx_register_driver / xxx_unregister_driver ↓ module_init / module_exit ↓ MODULE_LICENSEplatform、I2C、SPI框架都一样只是 API 不同。2. 通用模板头文件c#include linux/init.h #include linux/module.h #include linux/cdev.h #include linux/fs.h #include linux/xxx.h // 总线头文件 #include linux/miscdevice.h #include asm/uaccess.h全局变量cstatic struct xxx_device *pdev NULL;操作函数cstatic ssize_t xxx_read(struct file *fp, char __user *puser, size_t n, loff_t *off) { // 1. 硬件操作 // 2. copy_to_user return sizeof(data); } static ssize_t xxx_write(struct file *fp, const char __user *puser, size_t n, loff_t *off) { // 1. copy_from_user // 2. 硬件操作 return n; }file_operationscstatic struct file_operations fops { .owner THIS_MODULE, .read xxx_read, .write xxx_write, };miscdevicecstatic struct miscdevice xxx_misc { .minor MISC_DYNAMIC_MINOR, .name xxx_misc, .fops fops, };probecstatic int xxx_probe(struct xxx_device *pdev_param) { pdev pdev_param; // 申请资源、初始化硬件 misc_register(xxx_misc); return 0; }removecstatic int xxx_remove(struct xxx_device *pdev_param) { misc_deregister(xxx_misc); return 0; }匹配表cstatic struct xxx_device_id xxx_id_table[] { {.name xxx}, {}, }; static struct of_device_id xxx_of_match_table[] { {.compatible vendor,xxx}, {}, };driver 结构体cstatic struct xxx_driver xxx_drv { .probe xxx_probe, .remove xxx_remove, .driver { .name xxx, .owner THIS_MODULE, .of_match_table xxx_of_match_table, }, .id_table xxx_id_table, };注册和注销cstatic int __init xxx_drv_init(void) { return xxx_register_driver(xxx_drv); } static void __exit xxx_drv_exit(void) { xxx_unregister_driver(xxx_drv); } module_init(xxx_drv_init); module_exit(xxx_drv_exit); MODULE_LICENSE(GPL);十、各种总线的对应关系必背项目platformI2CSPI设备结构platform_devicei2c_clientspi_device驱动结构platform_driveri2c_driverspi_driver设备 ID 表—i2c_device_idspi_device_idprobe 参数platform_device *i2c_client *spi_device *注册platform_driver_registeri2c_add_driverspi_register_driver注销platform_driver_unregisteri2c_del_driverspi_unregister_driver传输结构—i2c_msgspi_transferspi_message传输函数—i2c_transferspi_sync规律xxx_device、xxx_driver、xxx_register_driver、xxx_unregister_driver。十一、写驱动的通用流程text1. 确定总线类型GPIO / I2C / SPI 2. 选框架platform / i2c / spi 3. 写全局变量保存设备结构 4. 写操作函数read / write 5. 写 file_operations 6. 写 miscdevice 7. 写 probe存设备、申请资源、注册 misc 8. 写 remove注销 misc 9. 写匹配表of_device_id xxx_device_id 10. 写 xxx_driver 结构体 11. 写注册和注销 12. module_init / module_exit 13. MODULE_LICENSE十二、操作函数的区别总线操作函数里的硬件操作platformgpio_set_value/gpio_get_value/readl/writelI2Ci2c_transfer/master_xferSPIspi_write_then_read/spi_sync框架不变操作函数里的硬件代码不同。十三、设备树模板dtsxxx { compatible vendor,xxx; reg ...; // I2C 从机地址 或 SPI 片选编号 spi-max-frequency ...; // SPI 用 pinctrl-names default; pinctrl-0 pinctrl_xxx; gpio-xxx gpioX Y 1; // GPIO 用 interrupt-parent gpioX; // 中断用 interrupts Y IRQ_TYPE_EDGE_FALLING; status okay; };十四、中断 等待队列1. 为什么用中断轮询方式 CPU 占用高响应慢。中断方式硬件触发CPU 占用低响应快。2. 中断处理cstatic irqreturn_t xxx_handler(int irqno, void *pdev) { condition 1; wake_up_interruptible(wq); return IRQ_HANDLED; } // probe 里申请中断 irqno of_irq_get(pdevice-dev.of_node, 0); devm_request_irq(dev, irqno, xxx_handler, IRQF_TRIGGER_FALLING, xxx, NULL);3. 等待队列cstatic wait_queue_head_t wq; static int condition 0; // probe 里初始化 init_waitqueue_head(wq); // read 里阻塞等待 condition 0; wait_event_interruptible(wq, condition);4. 流程text应用 read ↓ xxx_read 里 wait_event_interruptible → 进程睡眠 ↓ 硬件触发中断 ↓ xxx_handler → condition 1唤醒进程 ↓ xxx_read 继续读数据返回十五、中断下半部1. 为什么需要下半部中断处理函数不能做耗时操作否则阻塞其他中断。部分作用特点上半部紧急操作不可打断要快下半部耗时操作可打断2. tasklet 和工作队列方式上下文能否睡眠tasklet中断上下文❌工作队列进程上下文✅需要在中断下半部睡眠必须用工作队列。十六、input 子系统Linux 统一管理输入设备键盘、鼠标、触摸屏、按键。c// 1. 申请 input 设备 pkey_dev input_allocate_device(); // 2. 说明设备能力 input_set_capability(pkey_dev, EV_KEY, KEY_0); // 3. 注册 input 设备 input_register_device(pkey_dev); // 4. 上报事件 input_event(pkey_dev, EV_KEY, KEY_0, 1); input_sync(pkey_dev);应用程序从/dev/input/eventX读取事件。十七、内核定时器cstatic void timeout(unsigned long data) { pr_info(timeout ok!\n); } static DEFINE_TIMER(mtimer, timeout, 0, 0); // 加载时 init_timer(mtimer); add_timer(mtimer); mod_timer(mtimer, jiffies msecs_to_jiffies(100)); // 卸载时 del_timer(mtimer);延时执行函数超时后内核自动调用timeout。十八、核心思想思想说明设备与驱动分离硬件信息放设备树驱动代码不写死地址一切皆是文件应用通过/dev/xxx访问驱动分层设计上层应用、中层驱动、下层硬件并发控制多进程访问用锁保护中断 阻塞提高效率降低 CPU 占用

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑