驱动概述
驱动用在哪里?非标准类设备的编写和标准类设备的驱动移植。
驱动实际上是随着linux内核相伴而生的。某段代码能够控制我们的硬件去工作,去动,这段代码就称为我们的驱动代码。
技术只是一种手段,一种技巧,我们应该利用技术去搞出产品。
5.1.1_2.什么是驱动1_2
5.1.1.1、理解驱动的概念
(1)驱动一词的字面意思
(2)物理上的驱动
(3)硬件中的驱动
(4)linux内核驱动。软件层面的驱动广义上就是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序。(本质上是电力提供了动力,而驱动程序提供了操作逻辑方法)
狭义上驱动程序就是专指操作系统中用来操控硬件的逻辑方法部分代码。
5.1.1.2、linux体系架构(下层用来实现上层,上层来调用下层)
(1)分层思想
(2)驱动的上面是系统调用API(驱动对上对系统调用API负责,API又对应用程序负责)
(3)驱动的下面是硬件(驱动对下管理硬件,本质上驱动是操控硬件寄存器的)
(4)驱动自己本身也是分层的()
5.1.3.模块化设计
5.1.3.1、微内核和宏内核(操作系统设计的两种思路)
(1)宏内核(又称为单内核):将内核从整体上作为一个大过程实现(可以理解为整个内核就是一个裸机程序,整个内核是一起来写的),并同时运行在一个单独的地址空间。所有的内核服务都在一个地址空间运行,【相互之间直接调用函数】,简单高效,架构设计比较简单。(耦合性比较高,但是有缺陷,一个不工作其他也不能工作了)
(2)微内核:功能被划分成独立的过程,过程间通过【IPC进程间通信】进行通信。模块化程度高(耦合性比较低),一个服务失效不会影响另外一个服务。典型如windows
(3)linux:本质上是宏内核,但是又吸收了微内核的模块化特性,体现在2个层面
5.1.3.2、静态模块化:在编译时(源代码级别)实现可裁剪,特征是想要功能裁剪改变必须重新编译整个内核,比较复杂。得到一个新的zImage来代替旧的zImage,然后再去烧录才能起效果。【类似于安卓的卡刷】
这种方式来说这个驱动模块就属于内核。
5.1.3.3、动态模块化:zImage可以不重新编译烧录,甚至可以不关机重启就实现模块的安装和卸载。【类似于安卓的线刷】这种方式其实严格来说这个驱动模块不属于内核,它可以在需要的时候进行使用,在不需要的时候可以卸载。
5.1.4.linux设备驱动分类
5.1.4.1、驱动分类
(1)分3类:字符设备驱动、块设备驱动、网络设备驱动
(2)分类原则:设备本身读写操作的特征差异(硬件来决定软件怎么写,因为硬件进行了分类,所以操控硬件的软件程序(即驱动程序)也进行了分类)
就好像鞋子和脚的关系(鞋子的制作要适应脚的形状等特性)
5.1.4.2、三类驱动程序详细对比分析
(1)字符设备,准确的说应该叫“字节设备”,也就是这个设备的读写是:软件操作设备时是以【字节】为单位进行的。典型的如LCD、串口、LED、蜂鸣器、触摸屏······
(2)块设备,块设备是相对于字符设备定义的,块设备被软件操作时是以【块(多个字节构成的一个单位)】为单位的。设备的块大小是设备本身设计时定义好的,是硬件决定好的,软件是不能去更改的,不同设备的块大小可以不一样,但总的来说块的大小是固定的。常见的块设备都是存储类设备,如:硬盘、NandFlash、iNand、SD···· (块设备不能按照字节方式去读)块设备主要就是为存储设备而生的。
块设备要把整个块读到内存中,然后在内存中定位到某个字节中进行读写。
(3)网络设备,网络设备是【专为网卡设计】的驱动模型,linux中网络设备驱动主要目的是为了支持API中socket相关的那些函数工作。【因为网络通信有一套专用的接口和体系,所以单独放在一类中。】
5.1.4.3、为什么字符设备驱动最重要
(1)常见大量设备都属于字符设备
(2)举例说明【非标准类型】字符设备驱动
标准类设备是指比较普遍的设备,而非标准类型设备是为某种专有功能,特定制作的设备。
5.1.5.驱动程序的安全性要求(写驱动就相当于在写内核)
5.1.5.1、驱动是内核的一部分
(1)驱动已经成为内核中最庞大的组成部分
(2)内核会直接以函数调用的方式调用驱动代码(驱动如果有问题,内核就死掉了)
(3)驱动的动态安装和卸载都会“更改”内核(有点像改装车)
5.1.5.2、驱动对内核的影响
(1)驱动程序崩溃甚至会导致内核崩溃(也做了一些防护措施)
(2)驱动的效率会影响内核的整体效率
(3)驱动的漏洞会造成内核安全漏洞
5.1.5.3、常见驱动安全性问题
(1)未初始化指针(野指针)
(2)恶意用户程序
(3)缓冲区溢出(定义一个buffer来接收应用层的数据,但是应用层发给的数据大于你定义的buf,如果在驱动中做出一些检验则可以避免这种错误)
(4)竞争状态(多个进程同时争夺某一个共同的资源现象)
【驱动接收应用层传进来的参数。】
5.1.6.驱动应该这么学
5.1.6.1、先学好C语言(最重要的前提就是C语言)
5.1.6.2、掌握相关预备知识
(1)硬件操作方面
(2)应用层API
5.1.6.3、驱动学习阶段
(1)注重实践,一步一步写驱动(自己去写)
(2)框架思维(考虑这个代码的上层被谁调用和下层调用谁),多考虑整体(整个linux体系)和上下层
(3)先通过简单设备学linux【驱动框架】
(4)学会总结、记录,这会有助于理解
Linux操作系统包括(linux内核+linux应用程序),其中linux内核包括进程调度、内存管理、虚拟文件系统(字符设备驱动和块设备驱动)、网络接口(网络设备驱动)和进程通信五个子系统,应用程序比如shell等。
(1)
(2)
内核 驱动 硬件直接的关系:
linux驱动是直接和硬件打交道的软件程序。层次结构上它处于操作系统和硬件之间。
驱动与linux操作系统内核的关系
1.驱动程序提供的一组设备驱动接口函数DeviceDriverInterface给操作系统。在linux中这一组设备驱动接口函数一般包括open,close,read,write,ioctl等。 这一组函数是通过一个叫做fileoperations的结构体注册给linux内核的。 ■Linux内核提供特定的系统功能函数进行驱动程序的注册。注册时提供设备驱动文件名称设备号对应的fileoperations结构体fileoperations结构体中存储有一组设备驱动接口函数指针。
2.驱动程序还需要提供2个模块接口函数给操作系统。 Linux设备驱动作为一个linux内核模块存在。模块都有2个接口函数---模块初始化函数和模块退出函数。上面提到的驱动程序的注册一般是由模块初始化函数来实现的。模块退出函数则用于取消内核注册释放资源。 可见只有运行了驱动的这个模块初始化函数之后驱动程序才能够被注册内核才能找到设备驱动。 那么什么时候模块初始化函数才获得运行呢 ·动态加载时即运行insmode时。 ·静态加载时模块编译进内核系统初始化时会自动调用这个模块初始化函数。
3.驱动与应用程序的关系 对于应用程序来说驱动所对应的设备文件就代表着驱动。 应用程序通过linux系统提供的API调用使用驱动,我们写应用程序就是在写特定驱动,就是在写内核。应用程序通过linux文件操作系统调用使用驱动。也就是说设备驱动对于用户来说同操作一个文件没有区别。代表这个驱动的是驱动所对应的设备文件。
4.驱动与硬件的关系 硬件对于驱动程序来讲可以抽象为一组寄存器和需要响应的中断源。 对于统一寻址的系统比如ARM这一组寄存器就是一段地址空间。 驱动就是按照芯片手册规定的原则读取或者写入这些地址空间。 中断源是硬件产生的中断中断是由内核响应的需要给这个中断注册一个中断处理函数。这可以通过调用linux内核提供特定的系统功能函数进行。 一般注册中断处理函数可以在模块初始化函数里实现或者在设备驱动接口程序open中实现。
----------------------------------------------------------------------------------------------------------------------------------------------------------
LINUX驱动学习:
(1)驱动程序是硬件的灵魂,也是硬件和系统之间的桥梁。
(2)驱动程序的对象一般是存储器和外部设备。linux将这些设备分为3大类:【字符设备、块设备和网络设备】
字符设备是指能够一个字节一个字节读取数据的设备,字符设备一般需要在驱动层实现open(),close(),read(),write()和ioctl()函数。
(3)在linux内核中包含很多实现具体功能的模块,包括文件系统、网络协议栈、设备驱动、内核调度、内存管理和进程管理等。
(4)用户态和内核态:
Linux操作系统分为用户态和内核态,用户态处理上层的软件工作。内核态用来管理用户态的程序,完成用户态的请求的工作。驱动程序与底层的硬件交互,所以工作在内核态。
(5)模块机制是可以在运行时加入内核的代码,使得内核很容易的具有可裁剪性。
(6)静态装载和动态装载:
模块在内核启动时装载称为静态装载(烧录成镜像的形式),模块在内核已经运行起来时的装载称为动态装载。
(7)内核程序中包含的头文件是指内核代码树中的内核头文件,不是指开发应用程序时的外部头文件,eg:在内核中
实现的库函数中的打印函数printk()是C语言库函数printf()的内核版本。
(8)linux操作系统结构由4层组成:用户层、内核层、驱动层和硬件层。
(9)当内核启动后,第一件要做的事就是到存储设备(启动设备)中寻找根文件系统(包含了使系统运行的主要程序eg:shell和数据),其他普通的文件系统将来要挂载到根文件系统上来。
(10)内核启动后运行的第一个程序就是init,其将启动根文件系统中的shell程序,给用户提供一个友好的界面。
(11)根文件系统以树形结构来组织目录和文件的结构,系统启动后,根文件系统被挂接到根目录"/"上,这时候根目录下就包含了根文件系统的各个目录和文件。
(12)我们常使用busybox工具来构建根文件系统,可以从http://www.busybox.net/downloads下载其相应的版本。
(13)构建驱动程序模块时,必须考虑驱动程序与内核的兼容性。即使模块代码相同,标准内核模块和特定厂商的内核模块其模块格式也是不同的。
(14) 驱动模块 的组成:
1、头文件(必选) #include<linux/module.h> 和#include<linux/init.h>是必须的
2、模块参数(可选) 驱动模块加载时,需要传递给驱动模块的参数
3、模块功能函数(可选)
4、其他(可选)
5、模块加载函数(必须) 模块的加载函数,有点类似于main()函数
6、模块卸载函数(必须) 执行后清除了加载函数里分配的资源
7、模块许可声明(必须) 表示模块受到内核支持的程度,使用MODULE_LICENSE()表示许可权限的程度。
(15)模块的操作:
1)insmod命令加载模块
2)rmmod命令卸载模块
3)lsmod查看模块加载信息
(16)linux下的驱动主要分为字符设备驱动、块设备驱动和网路设备接口驱动。我们学习驱动就是学习这三类设备提供给我们的接口。
(17) 静态编译(重新下载编译linux内核)和动态编译。开发阶段以动态编译(不重新启动内核)为主。模块的方式动态加载。
(18)主设备号区分设备驱动程序。次设备号区分同一个驱动程序创建的多个设备。常见于多个串口和硬盘分区。次设备号通常依次对应同类型的多个设备。
(19)设备驱动的框架:
1、驱动程序的开始------设备的注册
2、注册的结构体
3、注册的fop指针(函数表)
(20)内核程序员获得内存的方式是kmalloc(分配出的地址空间都是物理上连续的),使用方法类似于用户空间的malloc版本。
kmalloc传递不同标志,导致了该函数的不同行为:
GFP_KERNEL
GFP_ATOMIC
GFP_USER
_GFP_DMA
2.4内核中register——char
2.6内核中调用cdev结构体的初始化,fop指针(函数表)要嵌入到这个cdev结构体中
我有个朋友是爵士音乐家,他有次跟我说:如果你不是乐队里最差的演奏者,马上换支乐队。我现在在Spotify工作,在这里我每天都觉得自己是个【狗屁程序员】,也因此我离开了上一家公司,从Spotify重头开始。当我在这里再也感觉不到自己是个【狗屁程序员】的时候,我会选择离开,然后找一个能让我再次感到自己是坨屎的地方。这种策略在我的职业生涯里确实 帮了我大忙。
------译自 Quora