马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1、什么是Linux内核驱动?
1.1、明白驱动概念
广义上:驱动步调指这一段代码利用了硬件去动,以是这一段代码就叫硬件的驱动步调。
狭义上:驱动步调就是专指利用体系中用来操控硬件的逻辑代码。
1.2、模块化筹划
微内核和宏内核 (1) 宏内核(又称为单内核):全部的内核服务都在一个地点空间运行,相互之间直接调用函数,简朴高效。 (2) 微内核:功能被分别成独立的历程,通过IPC举行通讯。模块化水平高,一个服务失效不会影响别的一个服务。范例如windows。 (3)Linux:本质上是宏内核,但是又罗致了微内核的模块化特性,提如今2个层面 静态模块化:在编译时实现可裁剪,特性是想要功能裁剪改变必须重新编译 动态模块化:zImage可以不重新编译烧录,乃至可以不关机重启就实现模块的安装和卸载。
1.3、linux装备驱动分类
分3类:字符装备驱动、块装备驱动、网络装备驱动(分类原则:装备自己读写利用的特性差别) 三类驱动步调详细对比分析 (1)字符装备,正确的说应该叫“字节装备”,软件利用装备时是以字节为单位举行的。范例的如LCD、串口、LED、蜂鸣器、触摸屏······(留意:字符装备驱动最紧张,由于常见大量装备都属于字符装备) (2)块装备,块装备是相对于字符装备界说的,软件利用时是以块(多个字节构成的一个单位)为单位的。装备的块巨细是装备自己筹划时界说好的,软件是不能去更改的,差别装备的块巨细可以不一样。常见的块装备都是存储类装备,如:硬盘、NandFlash、iNand、SD···· (3)网络装备,linux中网络装备是专为网卡筹划的驱动模子,重要目的是为了支持API中socket干系函数工作。
1.4、驱动步调的安全性要求
驱动是内核的一部分 (1)驱动已经成为内核中最巨大的构成部分 (2)内核会直接以函数调用的方式调用驱动代码 (3)驱动的动态安装和卸载都会“更改”内核
驱动对内核的影响 (1)驱动步调瓦解乃至会导致内核瓦解 (2)驱动的服从会影响内核的团体服从 (3)驱动的弊端会造成内核安全弊端
常见驱动安全性标题 (1)未初始化指针 (2)恶意用户步调 (3)缓冲区溢出 (4)竞争状态
2、字符装备驱动底子
2.1、驱动开发的预备工作
(1)正常运行linux体系的开发板。要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的。 (2)内核源码树,着实就是一个颠末设置编译之后的内核源码。 (3)NFS挂载的rootfs,主机ubuntu中必须搭建一个NFS服务器。
概念分析:
a)NFS: Network File System(网络文件体系)的缩写。它是一种答应通过网络共享文件的技能。利用 NFS,你可以在一台盘算机上存储文件,并让网络上的其他盘算机访问这些文件,就像它们是本地文件一样。如果你想将 root 文件体系 (rootfs) 通过 NFS 挂载到 Ubuntu 主机上,你须要在 Ubuntu 上设置一个 NFS 服务器。如许,其他装备或体系就可以通过网络访问这个 rootfs,就似乎它是本地存储的一样。这通常用于嵌入式开发、假造化情况大概须要跨呆板共享文件体系的场合。
b)zImage:zImage 是 Linux 内核的一种压缩格式。详细来说,它是颠末 zlib 压缩后的 U-Boot 加载器可以辨认的内核映像。这种格式重要用于较小的内存映像,即那些物理地点空间小于 16MB 的情况。 当内核巨细高出肯定限定时,原始的 Image 格式的内核大概无法精确加载,因此引入了 zImage 来办理这个标题。zImage 在加载前会举行解压,然后在目的呆板的 RAM 中运行。这种方式使得内核可以顺应更广泛的硬件平台和内存设置。 总结一下,zImage 是一种压缩的 Linux 内核映像,实用于小内存体系,而且可以通过 U-Boot 等引导加载步调加载。
2.2、驱动开发的步调
(1)驱动源码编写、Makefile编写、编译 (2)insmod装载模块、测试、rmmod卸载模块
驱动开发实践:
(1)copy原来提供的x210kernel.tar.bz2,找一个干净的目次(/root/driver),解压之,而且设置编译。编译完成后得到了:1、内核源码树。2、编译ok的zImage
(2)fastboot将第1步中得到的zImage烧录到开发板中去启动(大概将zImage丢到tftp的共享目次,uboot启动时tftp下载启动),未来驱动编译好后,就可以在这个内核中去测试(由于这个zImage和内核源码树是一伙的,以是驱动安装时版本校验不会堕落)。
2.3、最简朴的模块源码分析
常用的模块利用下令:
(1)lsmod(list module,将模块列表体现),功能是打印出当前内核中已经安装的模块列表
(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko
(3)modinfo(module information,模块信息),功能是打印出一个内核模块的信息。用法是modinfo xxx.ko
(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(留意卸载模块时只须要输入模块名即可,不能加.ko后缀)
(5)剩下的背面再说,暂时用不到(如modprobe、depmod等)
2.3.1、insmod与module_init宏
module_init(linux kernel 宏函数之“模块的出发点”)博客:https://zhuanlan.zhihu.com/p/615272622
模块源代码中用module_init宏声明白一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod下令绑定起来,也就是说当我们insmod module_test.ko时,insmod下令内部现实实行的利用就是帮我们调用chrdev_init函数。
照此分析,那insmod时就应该能看到chrdev_init中利用printk打印出来的一个chrdev_init字符串,但是现实没看到。缘故起因是ubuntu中拦截了,要怎么才气看到呢?在ubuntu中利用dmesg下令就可以看到了。关于dmesg下令详细作用请看:Linux dmesg 下令 | 菜鸟教程
2.3.2、modinfo与模块安装
模块的版本信息 (1)利用modinfo查察模块的版本信息,内核zImage中也有一个确定的版本信息。模块的版本信息是为了包管模块和内核的兼容性,是一种安全步调。 (2)insmod时模块的vermagic必须和内核的vermagic类似,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format。安装模块碰到的该错误可以参考博客:Linux驱动vermagic值-CSDN博客
(3)怎样包管模块的vermagic和内核的vermagic同等?编译模块的内核源码树就是我们编译正在运行的这个内核的谁人内核源码树即可。即模块和内核要同出一门。
2.3.3、rmmod与模块卸载
(1)module_exit和rmmod的对应关系 (2)lsmod查察rmmod前后体系的模块记载厘革
2.3.4、模块中常用宏
MODULE_LICENSE模块声明-CSDN博客 (1)MODULE_LICENSE,模块的答应证。一样平常声明为GPL答应证,即MODULE_LICENSE(“GPL”);,而且最好不要少,否则大概会出现莫名其妙的错误(譬如一些显着存在的函数提升找不到)。 (2)MODULE_AUTHOR // 声明作者 (3)MODULE_DESCRIPTION // 对这个模块作一个简朴的形貌 (4)MODULE_ALIAS // 这个模块的别名
2.3.5、函数修饰符
(1)init,本质上是个宏界说,在内核源代码中就有#define init xxxx。这个init的作用就是将被他修饰的函数放入.init.text段中去(原来默认情况下函数是被放入.text段中)。 整个内核中的全部的这类函数都会被链接器链接放入.init.text段中,以是全部的内核模块的init修饰的函数着实是被同一放在一起的。内核启动时同一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给开释掉以节流内存。 (2)__exit
2.3.6、printk函数详解
(1)printk在内核源码中用来打印信息的函数,用法和printf非常相似。 (2)printk和printf最大的差别:printf是C库函数,是在应用层编程中利用的,不能在linux内核源代码中利用;printk是linux内核源代码中自己封装出来的一个打印函数,是内核源码中的一个平常函数,只能在内核源码范围内利用,不能在应用编程中利用。 (3)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上体现的。应用步调中的调试信息要么全部打开要么全部关闭,一样平常用条件编译来实现(DEBUG宏),但是在内核中,由于内核非常巨大,打印信息非常多,偶尔候团体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。以是才有了打印级别这个概念。 (4)利用体系的下令行中也有一个打印信息级别属性,值为0-7。当前利用体系中实行printk的时间会去对比printk中的打印级别和我的下令行中设置的打印级别,小于我的下令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。 (5)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg下令去查察。
2.3.7、驱动模块的头文件
(1)驱动源代码中包罗的头文件和原来应用编程步调中包罗的头文件不是一回事。应用编程中包罗的头文件是应用层的头文件,是应用步调的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和利用体系无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件着实就是内核源代码目次下的include目次下的头文件。
2.4、驱动编译的Makefile分析
(1)KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目次 (2)obj-m += module_test.o,这一行就体现我们要将module_test.c文件编译成一个模块 (3)make -C $(KERN_DIR) M=pwd modules 这个下令用来现实编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目次下,然后在源码目次树下借用内核源码中界说的模块编译规则去编译这个模块,编译完成后把天生的文件还拷贝到当前目次下,完成编译。 (4)make clean ,用来扫除编译陈迹 总结:模块的makefile非常简朴,自己并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile自己黑白常模式化的,3和4部分是永世不消动的,只有1和2须要动。1是内核源码树的目次,你必须根据自己的编译情况
2.5.用开发板来调试模块
设置bootcmd使开发板通过tftp下载自己创建的内核源码树编译得到的zImage set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'
设置bootargs使开发板从nfs去挂载rootfs(内核设置记得打开使能nfs情势的rootfs) setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0 ff init=/linuxrc console=ttySAC2,115200
修改Makefile中的KERN_DIR使其指向自己创建的内核源码树 将自己编译好的驱动.ko文件放入nfs共享目次下去(目的是在开发板可以访问用于insmod等利用) 开发板启动后利用insmod、rmmod、lsmod等去举行模块实行
2.6、字符装备驱动工作原理
体系团体工作原理
(1)应用层->API->装备驱动->硬件 (2)API:open、read、write、close等 (3)驱动源码中提供真正的open、read、write、close等函数实体
file_operations结构体
(1)元素重要是函数指针,用来挂接实体函数地点 (2)每个装备驱动都须要一个该结构体范例的变量 (3)装备驱动向内核注册时提供该结构体范例的变量
注册字符装备驱动
(1)为何要注册驱动 (2)谁去负责注册 --> 驱动注册函数 (3)向谁注册 --> 内核 (4)注册函数从那里来 (5)注册前怎样?注册后怎样?注册产生什么结果?
Linux驱动字符装备注册接口
Linux内核提供了多种注册字符装备驱动的接口,register_chrdev(#include <linux/fs.h>)接口一样平常在Linux 2.6之后发起不再利用,保举利用alloc_chrdev_region大概register_chrdev_region。文章《https://zhuanlan.zhihu.com/p/576214922》分析下它们三者的实现,总结下三者的异同。
register_chrdev等注册接口作用:驱动向内核注册自己的file_operations
内核怎样管理字符装备驱动 (1)内核中有一个数组用来存储注册的字符装备驱动 (2)register_chrdev内部将我们要注册的驱动的信息(重要是 )存储在数组中相应的位置 (3)cat /proc/devices查察内核中已经注册过的字符装备驱动(和块装备驱动) (4)好好明白主装备号(major)的概念
回顾和猜测 (1)回顾:inline、static等关键字 (2)回顾:/proc文件体系的作用 (3)猜测:未来深入学习驱动时可以去跟register_chrdev到内部看,验证我们上面讲的原理
2.7、字符装备驱动代码实践
思绪和框架 (1)目的:给空模块添加驱动壳子 (2)焦点工作量:file_operations及其元素添补、注册驱动
怎样动手写驱动代码 (1)脑海里先有框架,知道自己要干嘛 (2)细节代码不须要一个字一个字敲,可以到内核中去探求参考代码复制过来改 (3)写下的全部代码必须内心清楚明白,不能似懂非懂
2.7.1、file_operations及其元素添补
(1)先界说file_operations结构体变量 (2)open和close函数原型确定、内容添补
file_operations结构体详细解读:6.3.4.3. Linux字符装备驱动 file_operations — 二进制安全学习条记 1.0.0 文档
2.7.2、注册驱动
(1)主装备号的选择 (2)返回值的检测
2.7.3、驱动测试
(1)编译等 make && make cp (2)insmod而且查察装备注册的征象 (3)rmmod而且查察装备注销的征象
让内核自动分配主装备号 (1)为什么要让内核自动分配 (2)怎样实现? (3)测试
2.8、应用步调怎样调用驱动
2.8.1、驱动装备文件的创建 (1)作甚装备文件?装备文件的关键信息是:装备号 = 主装备号 + 次装备号利用ls -l去查察装备文件,就可以得到这个装备文件对应的主次装备号。
(2)利用mknod创创建备文件:mknod /dev/xxx c 主装备号 次装备号
(3)写应用来测试驱动 照旧原来的应用,读写接口利用open、write、read、close等,实行征象猜测和验证。
2.8.2、应用和驱动之间的数据交换 (1)copy_from_user,用来将数据从用户空间复制到内核空间。copy_from_user函数的返回值界说,和通例有点差别。返回值如果乐成复制则返回0,如果 不乐成复制则返回尚未乐成复制剩下的字节数。 (2)copy_to_user 留意:复制是和mmap的映射相对应去区分的
2.9、驱动中怎样操控硬件
2.9.1、裸机和驱动操控硬件异同
类似点:照旧谁人硬件 (1)硬件物理原理稳固 (2)硬件利用接口(寄存器)稳固 (3)硬件利用代码稳固
差别点:那里差别了? (1)寄存器地点差别。原来是直接用物理地点,如今须要用该物理地点在内核假造地点空间相对应的假造地点。寄存器的物理地点是CPU筹划时决定的,从datasheet中查找到的。 (2)编程方法差别。裸机中风俗直接用函数指针利用寄存器地点,而kernel中风俗用封装好的io读写函数来利用寄存器,以实现最洪流平可移植性。
2.9.2、内核的假造地点映射方法
(1)为什么须要假造地点映射 (2)内核中有2套假造地点映射方法:动态和静态 (3)静态映射方法的特点: 内核移植时以代码的情势硬编码,如果要更改必须改源代码后重新编译内核 在内核启动时创建静态映射表,到内核关机时烧毁,中心不绝有效 对于移植好的内核,你用不消他都在那边 (4)动态映射方法的特点: 驱动步调根据须要随时动态的创建映射、利用、烧毁映射 映射是短期暂时的
2.9.3、怎样选择假造地点映射方法
(1)2种映射并不排他,可以同时利用 (2)静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存 (3)静态映射的长处是实行服从高,弊端是始终占用假造地点空间;动态映射的长处是按需利用假造地点空间,弊端是每次利用前后都须要代码去创建映射&烧毁映射(还得学会利用那些内核函数的利用)
2.9.4、静态映射利用LED
关于静态映射要说的 (1)差别版本内核中静态映射表位置、文件名大概差别 (2)差别SoC的静态映射表位置、文件名大概差别 (3)所谓映射表着实就是头文件中的宏界说
三星版本内核中的静态映射表 (1)主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h CPU在安排寄存器地点时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部的很多个寄存器的地点是连续的。以是内核在界说寄存器地点时都是先找到基地点,然后再用基地点+偏移量来探求详细的一个寄存器。 map-s5p.h中界说的就是要用到的几个模块的寄存器基地点。 map-s5p.h中界说的是模块的寄存器基地点的假造地点。 (2)假造地点基地点界说在:arch/arm/plat-samsung/include/plat/map-base.h #define S3C_ADDR_BASE (0xFD000000) // 三星移植时确定的静态映射表的基地点,表中的全部假造地点都是以这个地点+偏移量来指定的 (3)GPIO干系的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h 表中是GPIO的各个端口的基地点的界说 (4)GPIO的详细寄存器界说位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h
2.9.4.1、参考裸机中的利用方法添加LED利用代码
(1)宏界说 (2)在init和exit函数中分别点亮和熄灭LED (3)insmod和rmmod时观察LED亮灭厘革 (4)打印出寄存器的值和静态映射表中的分析相对比
2.9.4.2、将代码移动到open和close函数中去
添加驱动中的写函数 (1)先界说好应用和驱动之间的控制接口,这个是由自己来界说的。譬如界说为:应用向驱动写"on"则驱动让LED亮,应用向驱动写"off",驱动就让LED灭 (2)应用和驱动的接口界说做的只管简朴,譬如用1个字目来体现。譬如界说为:应用写"1"体现灯亮,写"0"体现让灯灭。
写应用来测试写函数
驱动和应用中来添加读功能
2.9.5、怎样创建动态映射
创建动态映射
(1)request_mem_region,IO内存申请。即向内核申请(陈诉)须要映射的内存资源。 (2)ioremap,真正用来实现映射,传给他物理地点他给你映射返回一个假造地点。
怎样烧毁动态映射
(1)iounmap (2)release_mem_region,IO内存归还。
留意:映射创建时,是要先申请再映射;然后利用;利用完要清除映射时要先清除映射再开释申请。
《ioremap和iounmap -- 内核态地点映射》博客:ioremap和iounmap -- 内核态地点映射_iounmap完还须要赋值为null吗-CSDN博客
《request_mem_region和release_mem_region明白》博客:Linux驱动技能(二) _访问I/O内存 - Abnor - 博客园
3、字符装备驱动高级
3.1、注册字符装备驱动新接口
(1)老接口:register_chrdev (2)新接口:register_chrdev_region/alloc_chrdev_region + cdev
3.2、为什么须要新接口?
特性旧接口 register_chrdev新接口 alloc_chrdev_region + cdev主装备号分配手动指定,易辩说动态分配,制止辩说次装备号支持固定为 0,仅支持单实例支持多个次装备号,可管理多个实例装备生命周期管理无动态创建/烧毁装备节点的本领支持动态创建/烧毁装备节点(device_create)与装备模子集成无可与 class 和 device 结构体联合,支持 udev机动性低高安全性低(手动指定装备号易堕落)高(动态分配,制止辩说) 总结:新接口 alloc_chrdev_region + cdev 的引入,重要办理了旧接口 register_chrdev 的以下标题:
- 动态分配装备号:制止手动指定主装备号导致的辩说。
- 支持多装备实例:通过次装备号管理多个逻辑装备。
- 与装备模子集成:支持动态创创建备节点和 udev 管理。
- 更好的资源管理:通过 cdev 结构体实现模块化和安全性。
因此,新接口是更当代、机动且安全的字符装备注册方式,保举在新驱动开发中利用。旧接口 register_chrdev 已被标志为废弃(Deprecated),未来大概被移除。
(1)cdev结构体
- <include/linux/cdev.h>
-
- struct cdev {
- struct kobject kobj; //内嵌的内核对象.
- struct module *owner; //该字符设备所在的内核模块的对象指针.
- const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
- struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
- dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
- unsigned int count; //隶属于同一主设备号的次设备号的个数.
- };
复制代码 (2)干系函数:cdev_alloc、cdev_init、cdev_add、cdev_del
《字符装备驱动结构cdev先容》博客:https://zhuanlan.zhihu.com/p/666666017
(3)cdev_alloc的编程实践 从内存角度了解cdev_alloc用与不消的差别,这就是非面向对象的语言和面向对象的代码。再次感叹C语言的博大博识,好好去看《4.C语言高级专题》
3.3、装备号
(1)主装备号和次装备号(二者一起为装备号) (2)dev_t范例:linux内核中,装备号用dev_t来形貌 (3)MKDEV、MAJOR、MINOR三个宏
- #define MINORBITS 20
- #define MINORMASK ((1U << MINORBITS) - 1)
- #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
- #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
- #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))</span>
复制代码 在32位机中是4个字节,高12位体现主装备号,低20位体现次装备号。 内核也为我们提供了几个方便利用的宏实现dev_t: 1)从dev_t装备号中提取major和minor MAJOR(dev_t dev) MINOR(dev_t dev) 2)利用下列宏则可以通过主装备号和次装备号天生dev_t,构创建备号: MKDEV(int major, int minor) 注:这只是构创建备号。并未注册,须要调用register_chrdev_region 静态申请;
3.4、分配装备号
静态分配:**register_chrdev_region()**
利用register_chrdev_region + cdev_init + cdev_add举行字符装备驱动注册。
事先知道要利用的主、次装备号时利用的;要先查察cat /proc/devices去查察没有利用的。
动态分配:**alloc_chrdev_region()**
利用alloc_chrdev_region自动分配装备号。更轻便、更智能的方法是让内核给我们自动分配一个主装备号 留意:自动分配的装备号,我们必须知道他的主次装备号,否则背面没法去mknod创建他对应的装备文件。
得到分配的主装备号和次装备号 (1)利用MAJOR宏和MINOR宏从dev_t得到major和minor (2)反过来利用MKDEV宏从major和minor得到dev_t。 (3)利用这些宏的代码具有可移植性
总结:register_chrdev_region()函数用于已知起始装备的装备号的情况,而alloc_chrdev_region()用于装备号未知,向体系动态申请未被占用的装备号的情况,函数调用乐成之后,会把得到的装备号放入第一个参数dev中。alloc_chrdev_region()相比于register_chrdev_region()的长处在于它会自动避开装备号重复的辩说。
3.5、字符装备驱动注册代码分析
老接口分析
register_chrdev __register_chrdev_region cdev_alloc cdev_add
新接口分析 register_chrdev_region __register_chrdev_region
alloc_chrdev_region __register_chrdev_region
3.6、自动创建字符装备驱动的装备文件
标题形貌: (1)团体流程回顾 (2)利用mknod创创建备文件的缺点 (3)能否自动天生和删除装备文件
办理方案:udev(嵌入式中用的是mdev) (1)什么是udev?应用层的一个应用步调,用于动态装备管理。
《利用udev高效、动态的管理Linux装备文件》博客:利用udev高效、动态的管理Linux装备文件 - gegeman - 博客园 (2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议) (3)应用层启用udev,内核驱动中利用相应接口 (4)驱动注册和注销时信息会被传给udev,由udev在应用层举行装备文件的创建和删除
3.7、内核驱动装备类干系函数(驱动接口)
(1)class_create (2)device_create
sys文件体系简介 (1)sys文件体系的筹划头脑 (2)装备类的概念 (3)/sys/class/xxx/中的文件的作用
- class_create
- __class_create
- __class_register
- kset_register
- kobject_uevent
复制代码- device_create
- device_create_vargs
- kobject_set_name_vargs
- device_register
- device_add
- kobject_add
- device_create_file
- device_create_sys_dev_entry
- devtmpfs_create_node
- device_add_class_symlinks
- device_add_attrs
- device_pm_add
- kobject_uevent
复制代码 3.8、静态映射表创建过程分析
创建映射表的三个关键部分 (1)映射表详细物理地点和假造地点的值干系的宏界说 (2)映射表创建函数。该函数负责由(1)中的映射表来创建linux内核的页表映射关系。 在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数 smdkc110_map_io s5p_init_io iotable_init 结论:经太过析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射形貌了一段物理地点到假造地点之间的映射。这个结构体数组所记载的几个映射关系被iotable_init所利用,该函数负责将这个结构体数组格式的表创建成MMU所能辨认的页表映射关系,如许在开机后可以直接利用相对应的假造地点来访问对应的物理地点。
(3)开机时调用映射表创建函数 标题:开机时(kernel启动时)smdkc110_map_io怎么被调用的? start_kernel setup_arch paging_init devicemaps_init if (mdesc->map_io) mdesc->map_io();
3.9、内核提供的寄存器读写接口
(1)writel和readl (2)iowrite32和ioread32
4、驱动框架入门
4.1、作甚驱动框架?
驱动是谁写的 (1)驱动开发工程师 (2)内核维护者
驱动编程协作要求 (1)接口尺度化 (2)只管低沉驱动开发者难度
到底什么是驱动框架? (1)内核中驱动部分维护者针对每个种类的驱动筹划一套成熟的、尺度的、范例的驱动实现,然后把差别厂家的同类硬件驱动中类似的部分抽出来自己实现好,再把差别部分留出接口给详细的驱动开发工程师来实现,这就叫驱动框架。 (2)内核维护者在内核中筹划了一些同一管控体系资源的体系,这些体系让内核可以或许对资源在各个驱动之间的利用同一和谐和分配,包管整个内核的稳固康健运行。譬如体系中全部的GPIO就属于体系资源,每个驱动模块如果要利用某个GPIO就要先调用特别的接口先申请,申请到后利用,利用完后要开释。又譬如制止号也是一种资源,驱动在利用前也必须去申请。这也是驱动框架的构成部分。 (3)一些特定的接口函数、一些特定的数据结构,这些是驱动框架的直接体现。
4.2、内核驱动框架中LED的根本情况
4.2.1、干系文件
(1)drivers/leds目次,这个目次就是驱动框架规定的LED这种硬件的驱动应该待的地方。 (2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们形貌的是内核中全部厂家的差别LED硬件的类似部分的逻辑。 (3)leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由差别厂商的驱动工程师编写添加的,厂商驱动工程师联合自己公司的硬件的差别情况来对LED举行利用,利用第一部分提供的接口来和驱动框架举行交互,终极实现驱动的功能。
4.2.2、范例的驱动开发行业近况
驱动框架的关键点:分清楚内核开发者提供了什么,驱动开发者自己要提供什么
(1)内核开发者:对驱动框架举行开发和维护、升级,对应led-class.c和led-core.c (2)SoC厂商的驱动工程师:对装备驱动源码举行编写、调试,提供参考版本,对应leds-s3c24xx.c (3)做产物的厂商的驱动工程师:以SoC厂商提供的驱动源码为底子,来做移植和调试
4.3、开端分析led驱动框架源码
4.3.1、涉及到的文件
(1)led-core.c (2)led-class.c
4.3.2、subsys_initcall宏
(1)颠末基天职析,发现LED驱动框架中内核开发者实现的部分重要是led-class.c (2)我们发现led-class.c就是一个内核模块,对led-class.c分析应该从下往上,服从对模块的基天职析方法。 (3)为什么LED驱动框架中内核开发者实现的部分要实现成一个模块?由于内核开发者盼望这个驱动框架是可以被装载/卸载的。如许当我们内核利用者不须要这个驱动框架时可以完全去掉,须要时可以随时加上。 (4)subsys_initcall是一个宏,界说在linux/init.h中。颠末对这个宏举行睁开,发现这个宏的功能是:将其声明的函数放到一个特定的段:.initcall4.init。 subsys_initcall define_initcall("4",fn,4) (5)分析module_init宏,可以看出它将函数放到了.initcall6.init段中。 module_init initcall device_initcall __define_initcall("6",fn,6) (6)内核在启动过程中须要次序的做很多事,内核怎样实现按照先后次序去做很多初始化利用。内核的办理方案就是给内核启动时要调用的全部函数归类,然后每个类按照肯定的序次去调用实行。这些分类名就叫.initcalln.init,n的值从1到8。内核开发者在编写内核代码时只要将函数设置符合的级别,这些函数就会被链接的时间放入特定的段,内核启动时再按照段次序去依次实行各个段即可。 (7)经太过析,可以看出,subsys_initcall和module_init的作用是一样的,只不外前者所声明的函数要比后者在内核启动时的实行次序更早。
4.3.3、led_class_attrs
(1)什么是attribute?对应未来/sys/class/leds/目次里的内容,一样平常是文件和文件夹。这些文件着实就是sysfs开放给应用层的一些利用接口(非常类似于/dev/目次下的那些装备文件) (2)attribute有什么用?作用就是让应用步调可以通过/sys/class/leds/目次下面的属性文件来利用驱动进而利用硬件装备。 (3)attribute着实是另一条驱动实现的门路。有区别于之前讲的file_operations那条线。
4.3.4、led_classdev_register
led_classdev_register device_create (1)分析可知,led_classdev_register这个函数着实就是去创建一个属于leds这个类的一个装备。着实就是去注册一个装备。以是这个函数着实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。 (2)当我们利用led驱动框架去编写驱动的时间,这个led_classdev_register函数的作用类似于我们之前利用file_operations方式去注册字符装备驱动时的register_chrdev函数。
4.4、在内核中添加或去除某个驱动
4.4.1、去除九鼎移植的LED驱动
(1)九鼎移植的驱动在应用层的接口在/sys/devices/platform/x210-led/目次下,有led1、led2、led3、led4四个装备文件,各自管理一个led。 (2)要去掉九鼎自己移植的led驱动,要在make menucofig中去掉选择项,然后重新make得到zImage,然后重启时启动这个新的zImage即可。 (3)新的内核启动后,如果/sys/devices/platform/目次下已经没有了x210-led这个目次,就分析我们去掉这个驱动乐成了。
4.4.2、为什么make menuconfig能去掉这个驱动?
menuconfig是Linux平台用于管理代码工程、模块及功能的实用工具。上至决定某一步调模块是否编译,下到某一行详细的代码是否须要编译以及某个项的值在本次编译时该是什么都可由menuconfig来界说。
menuconfig的利用方式通常是在编译体系之前在体系源代码根目次下实行 make menuconfig 下令从而打开一个图形化设置界面,再通过对各项的值按需设置从而到达影响体系编译结果的目的。
一个范例的 menuconfig 设置界面如下图所示:
menuconfig 着实只能算是一个“前端”,用于支持它、决定它拥有什么设置项的“后端”则被称为 Kconfig。
Kconfig 严酷来讲是一种编程语言,它拥有自己的语法及结构。正是这些语法和结构构成了menuconfig在用户眼前差别的体现情势。
menuconfig 功能简述请看如下面链接:Linux下menuconfig与Kconfig底子知识概要 - 大洞穴 - 博客园
4.4、添加led驱动框架支持
(1)当前内核中是没有LED驱动框架的,我们要去添加它。
sysfs中的内容分析 后续猜测:完成leds-x210.c
基于驱动框架写led驱动1 (1)参考那里? drivers/leds/leds-s3c24xx.c (2)关键点:led_classdev_register
4.5、添加硬件利用
在驱动中将4个LED分开长处
驱动层实现对各个LED装备的独立访问,并向应用层展示出4个利用接口led1、led2、led3、led4,如许应用层可以完全按照自己的须要对LED举行控制。
驱动的筹划理念:
不要对终极需求功能举行假定,而应该只是直接的对硬件的利用。有一个概念就是:机制和战略的标题。在硬件利用上驱动只应该提供机制而不是战略。战略由应用步调来做。
4.6、怎样实现将LED分开(gpiolib引入)
(1)一个究竟:很多硬件都要用到GPIO、GPIO会复用 (2)如果同一个GPIO被2个驱动同时控制了,就会出现bug (3)内核提供gpiolib来同一管理体系中全部GPIO (4)gpiolib自己属于驱动框架的一部分
4.6.1、linux内核的gpiolib学习1
gpiolib学习重点 (1)gpiolib的创建过程 (2)gpiolib的利用方法:申请、利用、开释 (3)gpiolib的架构:涉及哪些目次的哪些文件
gpiolib的学习方法 (1)以一条主线进去,对峙主线 (2)中途碰到杂碎知识,彻底搞定之,然后继承主线 (3)随时做条记以加深明白和影象 (4)学习途中留意架构头脑,提升自己大脑的空间复杂度
主线1:gpiolib的创建(厂商驱动工程师负责)
从驱动框架角度再来分析一下gpiolib。之前的分析已经告一段落,停止如今我们已经搞清楚了gpiolib的创建工程。但是这只是整个gpiolib创建的一部分,是厂商驱动工程师负责的那一部分;尚有另一部分是内核开发者提供的驱动框架的那一部分,就是我们背面要去分析的第2条主线。
主线2:内核开发者写的gpiolib框架部分
drivers/gpio/gpiolib.c这个文件中全部的函数构成了我们第2部分,也就是内核开发者写的gpiolib框架部分。
这个文件中提供的函数重要有以下部分:
- gpiochip_add: 是框架开出来的接口,给厂商驱动工程师用,用于向内核注册我们的gpiolib
- gpio_request: 是框架开出来的接口,给使用gpiolib来编写自己的驱动的驱动工程师用的,
- 驱动中要想使用某一个gpio,就必须先调用gpio_request接口来向内核的
- gpiolib部分申请,得到允许后才可以去使用这个gpio。
- gpio_free: 对应gpio_request,用来释放申请后用完了的gpio
- gpio_request_one/gpio_request_array: 这两个是gpio_request的变种
- gpiochip_is_requested: 接口用来判断某一个gpio是否已经被申请了
- gpio_direction_input/gpio_direction_output: 接口用来设置GPIO为输入/输出模式,
- 注意该函数内部实际并没有对硬件进行操作,只是通过chip结构体变量的
- 函数指针调用了将来SoC厂商的驱动工程师写的真正的操作硬件实现gpio
- 设置成输出模式的那个函数。
复制代码 以上的接口属于一类,这些都是给写其他驱动而且用到了gpiolib的人利用的。 剩下的尚有别的一类函数,这类函数是gpiolib内部自己的一些功能实现的代码。
4.6.2、利用gpiolib完成led驱动
流程分析: (1)第1步:利用gpio_request申请要利用的一个GPIO (2)第2步:gpio_direction_input/gpio_direction_output 设置输入/输出模式 (3)第3步:设置输出值gpio_set_value 获取IO口值gpio_get_value
代码实践: (1)在led1上编写代码测试通过 (2)扩展支持led2和led3、led4.可以分开注册也可以利用gpio_request_array去一次注册 (3)学习linux中查察gpio利用情况的方法 内核中提供了假造文件体系debugfs,内里有一个gpio文件,提供了gpio的利用信息。 利用方法:mount -t debugfs debugfs /tmp,然后cat /tmp/gpio即可得到gpio的全部信息,利用完后umount /tmp卸载掉debugfs
4.6.3、将驱动添加到内核中
驱动的存在情势 (1)野生,上风是方便调试开发,以是在开发阶段都是这种 (2)家养,上风可以在内核设置时make menuconfig决定内核怎么编译,方便集成
驱动开发的一样平常步调 (1)以模块的情势在外部编写、调试 (2)将调试好的驱动代码集成到kernel中
实践 (1)关键点:Kconfig、Makefile、make menuconfig (2)利用步调: 第1步:将写好的驱动源文件放入内核源码中精确的目次下 第2步:在Makefile中添加相应的依赖 第3步:在Kconfig中添加相应的设置项 第4步:make menuconfig
5、Linux装备驱动模子
《深入解说,linux内核——装备驱动模子》博客:https://zhuanlan.zhihu.com/p/501817318
5.1、linux装备驱动模子简介
5.1.1、什么是装备驱动模子
(1)类class、总线bus、装备device、驱动driver (2)kobject和对象生命周期 (3)sysfs (4)udev
5.1.2、为什么须要装备驱动模子
(1)早期内核(2.4之前)没有同一的装备驱动模子,但照旧可以用 (2)2.6版本中正式引入装备驱动模子,目的是在装备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更良好。 (3)装备驱动模子负责同一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等底子办法 (4)装备驱动模子目的是简化驱动步调编写,但是客观上装备驱动模子自己筹划和实现很复杂。
5.1.3、驱动开发的2个点
(1)驱动源码自己编写、调试。重点在于对硬件的相识。 (2)驱动什么时间被安装、驱动中的函数什么时间被调用。跟硬件无关,完全和装备驱动模子有关。
5.2、装备驱动模子的底层架构
5.2.1、kobject
(1)界说在linux/kobject.h中 (2)各种对象最根本单位,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的体现 (3)装备驱动模子中的各种对象其内部都会包罗一个kobject (4)职位相当于面向对象体系架构中的总基类
5.2.2、kobj_type
(1)很多书中简称为ktype,每一个kobject都须要绑定一个ktype来提供相应功能 (2)关键点1:sysfs_ops,提供该对象在sysfs中的利用方法(show和store) (2)关键点2:attribute,提供在sysfs中以文件情势存在的属性,着实就是应用接口
5.2.3、kset
kset是具有类似范例的kobject的聚集
(1)kset的重要作用是做顶层kobject的容器类 (2)kset的重要目的是将各个kobject(代表着各个对象)构造出目次条理架构 (3)可以以为kset就是为了在sysfs中弄出目次,从而让装备驱动模子中的多个对象可以或许有条理有逻辑性的构造在一起
- struct kset {
- struct list_head list; //连接链表
- spinlock_t list_lock; //链表的自旋锁
- struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
- const struct kset_uevent_ops *uevent_ops; //热插拔事件
- };
复制代码 5.3、总线式装备驱动构造方式
Linux装备驱动模子构造方式有个图如下,出自若下博客链接:Linux装备驱动模子-根本概念_linux driver model-CSDN博客
5.3.1、Bus(总线)
(1)物理上的真实总线及其作用(英文bus) (2)驱动框架中的总线式筹划 (3)bus_type结构体,关键是match函数和uevent函数
5.3.2、Device(装备)
(1)struct device是硬件装备在内核驱动框架中的抽象 (2)device_register用于向内核驱动框架注册一个装备 (3)通常device不会单独利用,而是被包罗在一个详细装备结构体中,如struct usb_device
5.3.3、Device Driver(驱动)
(1)struct device_driver是驱动步调在内核驱动框架中的抽象 (2)关键元素1:name,驱动步调的名字,很紧张,常常被用来作为驱动和装备的匹配依据 (3)关键元素2:probe,驱动步调的探测函数,用来检测一个装备是否可以被该驱动所管理
5.3.4、Class(分类)
(1)干系结构体:struct class 和 struct class_device (2)udev的利用离不开class (3)class的真正意义在于作为同属于一个class的多个装备的容器。也就是说,class是一种人造概念,目的就是为了对各种装备举行分类管理。固然,class在分类的同时还对每个类贴上了一些“标签”,这也是装备驱动模子为我们写驱动提供的底子办法。特别地,利用Class结构,在装备模子中引入面向对象的概念,如答应以最大限度地抽象共性,淘汰驱动开发的重复劳动。
5.3.5、总结
(1)模子头脑很紧张,着实就是面向对象的头脑 (2)满是结构体套结构体,对根本功(语言功底和大脑复杂度)要求很高
5.4、platform平台总线工作原理
5.4.1、作甚平台总线
(1)相对于usb、pci、i2c等物理总线来说,platform总线是假造的、抽象出来的。 (2)回顾裸机中讲的,CPU与外部通讯的2种方式:地点总线式毗连和专用接口式毗连。平台总线对应地点总线式毗连装备,也就是SoC内部集成的各种内部外设。 (3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?
5.4.2、平台总线下管理的2员大将
(1)platform工作体系都界说在drivers/base/platform.c中 (2)两个结构体:platform_device和platform_driver (3)两个接口函数:platform_device_register和platform_driver_register
- struct platform_device {
- const char * name; // 平台总线下设备的名字
- int id;
- struct device dev; // 所有设备通用的属性部分
- u32 num_resources; // 设备使用到的resource的个数
- struct resource * resource; // 设备使用到的资源数组的首地址
- const struct platform_device_id *id_entry; // 设备ID表
-
- /* arch specific additions */
- struct pdev_archdata archdata; // 自留地,用来提供扩展性的
- }
- struct platform_driver {
- int (*probe)(struct platform_device *); // 驱动探测函数
- int (*remove)(struct platform_device *); // 去掉一个设备
- void (*shutdown)(struct platform_device *); // 关闭一个设备
- int (*suspend)(struct platform_device *, pm_message_t state);
- int (*resume)(struct platform_device *);
- struct device_driver driver; // 所有设备共用的一些属性
- const struct platform_device_id *id_table; // 设备ID表
- };
复制代码 5.4.3、平台总线体系的工作流程
(1)第一步:体系启动时在bus体系中注册platform (2)第二步:内核移植的人负责提供platform_device (3)第三步:写驱动的人负责提供platform_driver (4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后装备就工作起来了。
代码分析:platform自己注册 (1)每种总线(不但是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver举行匹配。理论上每种总线的匹配算法是差别的,但是现实上一样平常都是看name的。 (2)platform_match函数就是平台总线的匹配方法。
match函数的工作方法是:
如果有id_table就分析驱动大概支持多个装备,以是这时间要去对比id_table中全部的name,只要找到一个类似的就匹配上了不再找了,如果找完id_table都还没找到就分析每匹配上;如果没有id_table大概没匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。
5.4.4、以leds-s3c24xx.c为例来分析platform装备和驱动的注册过程
(1)platform_driver_register
(2)platform_device_register
这部分有点没看懂,详细可以看知乎链接:https://zhuanlan.zhihu.com/p/501817318
6、miss类装备与蜂鸣器驱动
本章起首先容板载蜂鸣器驱动测试、其次先容misc类装备先容以及misc驱动框架源码分析,末了基于九鼎提供的buzzer驱动源码举行蜂鸣器驱动源码分析,详细内容可以查察《- misc类装备与蜂鸣器驱动》博客链接:Linux驱动开发(8)------- misc类装备与蜂鸣器驱动_什么是misc装备,为什么要引入misc装备-CSDN博客
|