本文,记录学习linux驱动的思考过程。
- 主题非常鲜明 ,消除拖沓,不跟随书本的目录,源思考路径可寻
- 点明提供解决问题的思路,并为此提供链接,可以重复修改
- 内容极端简洁,摒弃冗余
本文首先在2017/2/25 21:33编写,做出第一次书写,明天就将深入其中每一个细节,并且修改和补充笔记。(这是新笔记的回顾方式)
参考资料
- 《嵌入式Linux开发教程(下册)》
基础知识记录最为关键的知识结构,稍作备注,不作展开。
>>>
2.1内核模块,获取
- 头文件 <linux/module.h>和<linux/init.h>,构建只有入口与出口的内核模块
- 编译注意事项,内核树之外编译内核模块的通用Makefile文件(程序清单2.3)
- 加载与卸载(定义 __init与 __exit和 module_init与 module_exit),内核打印printk
- 最简单的内核模块(程序清单2.4)
其升级版是:带参数的内核模块(程序清单2.5)
注意:这里讲述的是内核模块,内核模块不止是驱动。
>>>
2.2设备分类,获取
- 设备类型(字符c,块b,网络)
- 设备文件(执行 ls -l /dev/mem即可,解析图2-1),设备编号( dev_t结构表示编号组成,头文件<linux/kdev_t.h>的宏静态获取设备号)
- 设备节点和设备号( cat /proc/devices查看,头文件 <linux/fs.h>获取与释放设备号--动态与静态)
- 设备的注册和注销(头文件 <linux/cdev.h>,结构struct cdev描述字符设备结构)
注意:这里讲述的是设备,设备不止是硬件设备,也就常说的硬件,用户想去控制的。
>>>
2.3设备驱动,获取:
- 驱动的入口与出口(程序清单2.11)
- 支持udev动态设备的驱动范例(程序清单2.14,头文件 <linux/device.h>)
- 以fops数据结构为核心的设备驱动程序实现操作设备的方法(头文件 <linux/fs.h> )
- 图2.4fops在设备驱动和系统调用之间的关系(用户空间->设备)
- 典型字符驱动框架缩略图--图2.6
- 字符驱动测试用例(程序清单2.18)
>>>
LED完整驱动示例,获取:
- ioctl命令构成(一个32位数据,分成多部分来构造命令)
- 构造ioctl命令(ioctl的第二个参数cmd,分别设置不同cmd的幻数、功能号、数据大小),构造命令解析
- 解析命令(传输方向、命令类型、命令编码、参数大小)
- 内核ioctl与用户ioctl(两个接口有所不同)
- LED驱动实现示例(程序清单2.20,版本兼容处理头文件 <linux/version.h>)以及测试用例(提出驱动所有命令都应该由用户测试完成,一般包含偷文件即可)
>>>
内核/用户空间的数据交换,获取:
- 地址合法检测(内核提供检测与非检测的两种传递数据工具--函数,看是否带__)
- 辅助单个或多个数据(put/get为单个--单个数据针对arm而言的并非一个字节,copy为多个--即数据块;头文件<asm/uaccess.h>)
- 设备读写方法实现的驱动示例(程序清单2.22)以及测试用例(读写回传判断数据是否正确)
>>>
使用中断的驱动,获取:
- 中断的申请与释放(头文件 </linux/interrupt.h>,中断号、中断是否屏蔽、IRQ指针、设备指针)
- 触发条件(头文件 <linux/irq.h>)
- 中断的使能与禁止,最为关键的是:IRQ处理函数编写
- 按键驱动示例(程序清单2.25)以及测试用例(查看系统中断次数 cat /proc/interrupts)
完整示例
LED驱动
多种实现办法,其一是传统的字符设备驱动,其二是利用LED子系统,其三是特定的mx28平台驱动模型
>>>
传统的字符设备驱动:程序清单2.20
- 调用imx28x的BSP所实现的GPIO底层接口移植,需要操作硬件资源
>>>
LED子系统:程序清单3.1--3.6
- 仅修改一个关键函数,驱动加载后会出现在led子系统上
>>>
平台驱动模型:程序清单3.7--3.12
- 暂时没看,因为这感觉不太好用。
点评:前两种较好,第一种灵活,第二种移植性强
按键驱动
两种实现方法,其一是传统的字符设备驱动,其二是将按键纳入Linux的输入子系统,重新实现按键功能。
>>>
传统的字符设备驱动(中断),获取:
>>>
输入子系统,获取:
- 内容是有点多,暂时不整理
释放疑问
>>>
内核模块编译,获取:
- 编译需要使用内核源码,编译模块的内核配置与所运行内核的编译配置一致(版本,内核树一致)。
- 直接在虚拟机ubantu上运行内核模块,下载目前内核运行的内核源码
备注:
参考链接
>>>
设备与驱动的关系,获取:
- 驱动是 Linux 系统中设备和用户之间的桥梁, Linux 系统中,访问设备必须通过设备驱动进行操作,用户程序是不能直接操作设备的。(示意图2-2,硬件、驱动、用户的关系)。这里,其实驱动,全名应该是设备驱动。
- 设备有软硬、虚拟真实的设备;驱动是操作某一类设备的操作程序。
- linux的设备节点(也就是目录/dev显示的设备文件),专门处理设备;而驱动则是由ko文件组成,并且insmod或rmmod装载或卸载驱动
- 执行过程如下:
- 当程序打开一个设备文件时,内核就可以获取对应设备的设备类型、主设备号和次设备号等信息,内核也就知道了程序需要操作使用哪个设备驱动程序。在程序随后对这个文件的操作都会调用相应的驱动程序的函数,同时把设备号传递给驱动程序。(理解内核执行过程)
- 主设备号标识设备对应的驱动程序,告诉 Linux 内核使用哪个驱动程序驱动该设备。如果多个设备使用同一个驱动程序,则它们拥有相同的主设备号。例如/dev/ttyS0~3 这 4 个设备,拥有相同的主设备号 4,说明它们使用同一份驱动。(理解设备号与驱动的关系)
- 设备注册后,该设备的主设备号与 fops 之间对应关系就一直存在于内核中,直到驱动生命周期结束(被卸载),应用程序发起系统调用,内核根据这个对应关系寻找正确的驱动程序来执行相关操作。(系统调用和驱动方法关系)。过程如下:
- 用户程序系统调用打开/dev/char设备文件,获得主次设备号;
- 根据主设备号,寻找对应的fops;
- 找到对应的fops,执行驱动的xxx_open方法的代码--从而打开这个设备(操作这个设备)
- fops在设备驱动和系统调用之间的关系(讨论用户程序如何从用户空间到达驱动程序的)图2-4
- 四层模型:用户空间--系统调用--VFS虚拟文件系统--设备驱动(在fops找到对应的cdev)
- 设备编号,结构体dev_t,高12位为主设备号,低20位为次设备号;
>>>
创建一份虚拟的字符设备文件
设备驱动程序,驱动
初始化函数xxx_init(),编写思路:
- 设备号申请(主次、静动)
- 设备注册(分配一个udev设备结构体),注意这里udev泛指受udev管理的设备,可以是cdev字符设备
- 硬件资源的初始化(根据驱动程序的设备资源而言)
- 设备驱动的操作方法(fops结构体填充、ioctl命令构造)
- 设备初始化(关联设备和fops)
- 将设备添加到系统(加入系统设备树当中)
- 创建设备”类“(设备class,交由udev管理,同时会在sysfs创建“类”目录)
- 将上述设备,加入到设备“类”当中(在sysfs系统中创建设备节点)
设备驱动程序,驱动
退出函数xxx_exit(),编写思路:
- 删除udev设备驱动结构
- 释放设备号
- 删除sysfs中的设备节点
- 删除sysfs中的设备“类”
>>>
sysfs文件系统,获取:
>>>
proc文件系统,获取:
>>>
udev自动设备管理,获取:
- udev的工作内容:根据sysfs中的设备信息,在/dev下动态创建设备文件
高级内容,需要简化,暂时未计划修改
>>>混合设备驱动,获取:
- 混合设备(简化字符驱动编写,头文件 <linux/miscdevice.h>,设备结构体 struct miscdevice)
- 混合设备驱动框架(程序清单2.27,misc没有主设备号)
>>>linux设备驱动模型,获取:(这部分有点大,个人感觉只能熟悉一下概念)
- 设备管理模型(头文件 <linux/device.h>,关键结构体 struct device,一般被嵌入到子系统里面)
备注:
- 2017/2/25 21:21,第一次看,当真不明白
- 2017/2/26 16:16,第二次看,其实这是非常容易移植且稳健的方式
>>>平台设备和驱动,获取:
- 平台驱动与普通驱动的区别(图2.8对比,platform方式编程,资源与驱动分离,便于移植)
- 平台驱动示例(程序清单2.40和2.41,一个是平台设备驱动,一个是该平台下的某个设备驱动)
- 测试用例(需要加载两个ko,一个平台,一个该平台下的子驱动)
高级疑问释放
>>>平台机制,获取: