linux驱动之一语点破天机
<const 关键字>
在嵌入式系开发中,const关键字就是“只读”的意思
<为什么要ARM需要进行C语言环境的初始化>
在汇编情况下,指令的跳转,保护现场需要保存的数据很少,并且可以直接访问寄存器,但是到了C语言环境中,函数的调用。
(1)无时无刻都需要保护现场,(2)并且无法直接访问寄存器,(3)函数需要传递参数,参数的保存地址。所以最好的方式就是指定一个对整个系统而言来说是一个约定固定的地址作为现场数据的保存地方。———堆栈
<bss段>
一个程序编译后分为“数据段”.data和“指令段”.text还有“全局未初始化段”.bss,首先将数据和指令分开是因为,数据段执是可读可写的,而指令段是不可读也不可写,为了防止将指令串改,所以分开存放保险写。
<完成量>
struct completion {
unsigned int done;/*用于同步的原子量*/
wait_queue_head_t wait;/*等待事件队列*/
};
所谓的完成量能够在互斥访问中激活对应的进程就是靠着wait_queue_head_t 上面睡眠的进程
<USB中struct device 和struct interface之间的关系>
举个例子:现代的打印机一般是一个复合设备一般具有“打印”,“扫描”,“复印”等功能这些功能都统筹到一
struct usb_device 的结构体中。但是从Linux设备驱动模型来看,应该将每一个功能划分出一个device出来。这样做就存在一个问题是驱动将变得相当繁琐复杂。于是驱动框架开发者就设计出了interface来代替device的一些功能。
<USB固件程序与USB设备驱动>
一般USB固件中包含“出厂信息”,“厂商ID”,"产品ID",主版本号,另USB设备固件中还包含有一组程序用于USB设备的协议处理和设备的读写操作(将数据发送到总线,或将总线上的数据存储到USB设备)。
USB设备驱动只是将USB设备规范定义的请求发送给固件程序。
usb设备中的 各种*****description 都是在产品出场的时候烧写进去的,当设备接入系统,usb_core 就会去读取这些信息
<init进程问题>
Linux中所有进程都是从init进程而来,子进程会复制父进程的“代码段”“数据段”“堆栈段”,那么 子进程和父进程之间就没有什么区别了,但是实际情况不是这样为什呢?
<ext2和ext3>
ext2文件系统适用于高速读写
ext3在ext2的基础上增加了记录日志文件的功能
<memset()函数>
Linux内核中开辟内存的函数kmolloc()并不将开辟出来的内存中数据清零,所以在编写驱动的时候为某个设设备结构体开辟内存的时候一定要清零。要不然会出现一些稀奇古怪的错误。
<非总线上的设备和驱动>
对于Linux中非总线上的设备(比如IIC 控制器,usb控制器),对于这类设备往往是在驱动中完成设备模型的直接将其分配并初始化然后注册进内核。(不像总线驱动和设备,有一个匹配match()和probe())
<Linux驱动之串口和USB之间的关系>
a:由上图可以看出UART 和USB串口之间是一种平行关系,并不是包含关系。
b:线路规程的作用,就是进行数据数据封装,因为UART 设备有很多(网卡,蓝牙,wifi ,红外线通信)。针对的设备其数据格式肯定不一样,所以线路规程就是一种以各种协议为基础的数据包装作用。
c:tty的作用,由于存在形形色色的硬件,tty 作为中间层,将硬件和和上层驱动隔离开来提供统一接口(api)
d:各层次关系
<输入子系统和tty 之间的关系>
<I/O端口>
很多soc都会进行I/O操作,经常提及I/O空间。所谓的I/O空间就是指外设的各种寄存器(数据寄存器,控制寄存器,状态 寄存器),外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声 明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间。
但要使用I/O内存首先要申请,然后才能映射,使用I/O端口首先要申请,或者叫请求,对于I/O端口的请求意思是让内核知道你要访问这个端口,这样内核知道了以后它就不会再让别人也访问这个端口了.毕竟这个世界僧多粥少啊.申请I/O端口的函数是request_region, 申请I/O内存的函数是request_mem_region。
<设备号>
Linux中申请设备号的目的是为了设备文件进行准备。根据主设备号相同次设备号
不同的设备可以使用相同的驱动原理,加强了驱动的复用性
<回调函数和一般函数>
A:对普通函数的调用:
调用程序发出对普通函数的调用后,程序执行立即转向被调用函数执行,直到被调用函数执行完毕后,再返回调用程序继续执行。从发出调用的程序的角度看,这个过程为“调用-->等待被调用函数执行完毕-->继续执行”
B:对回调函数调用:
调用程序发出对回调函数的调用后,不等函数执行完毕,立即返回并继续执行。这样,调用程序执和被调用函数同时在执行。当被调函数执行完毕后,被调函数会反过来调用某个事先指定函数,以通知调用程序:函数调用结束。这个过程称为回调(Callback),这正是回调函数名称的由来。
<平台设备开发流程>
<input设备(输入子系统)概要>
A:输入子系统所有设备公用一个主设备号(13),0_31子设备号的设备公用一个handler()函数
由上图可以看出input_handle 在input子系统中起到"总线的作用"将input_device 和input_handler联系起来。
<windows 应用程序安装机制>
A:在program file 文件中新建安装文件夹(一般是默认)
B:复制相关动态链接库(依赖库)到程序文件夹火系统文件夹(比如system 32)下面,比如dll文件。
C:复制可执行文件到程序文件夹下面。比如(.exe 文件)
D:复制相关配置文件到程序文件夹下或系统文件夹下面。比如ini文件。
E:把启动配置或者程序依赖的配置放到注册表中
F:如果有自定义的服务程序,注册并启动服务程序、。
<linux 应用程序安装机制>
A:在usr文件中新建程序安装文件
B:复制相关的动态链接库(依赖库)到程序安装文件或系统文件(/bin)(Linux 中的动态链接库是.so)
C:复制可执行文件(根据文件权限)程序安装文件夹下面。
D:复制相关配置文件到程序安装文件(一般是usr/”程序文件夹“/etc 或者是/usr/etc 主要是/etc).
E;如果有自定义的服务程序,注册并启动(主要是相关etc文件夹下面的init.d 文件夹)
注:etc 文件夹下面凡是后缀带有rc 的一般都是启动脚本文件
<linux 启动机制(Ubuntu)>
A:init文件夹放启动配置文件
B:init.d 文件放启动守护进程
C:Linux启动的时候是以xwindows 还是一字符形式启动取决与启动等级(不同的Linux发行版本同等级效果不一样)
D:一般服务性进程都会安装在/sbin....目录下,但是我们一般都可以在/etc/init.d 目录下找到其映射。如果想要查看配置,可以在/etc.....目录下找到一个同名的.conf 文件(所谓配置:Linux中断 服务也是一种程序,程序就会有相应的配置,比如SSH服务就会配置默认文件传输目录)
<国嵌移植系统到tq2440步骤>
A:使用USB转串口线将windows客户机和开发板连接起来(并安装USB转串口驱动)
注意:这样就可以使secureCRT 软件将开发板和windows 客户机链接起来,接下来会使用该软件进行Linux系统的移植工作。
B:windows 上安装jtag软件,使用该软件将开发板辅助程序(images中的supervivi-128.bin)下载到norflash 中。
C:将红帽企业版6 的usb下载驱动安装到虚拟机中
D:将开发板的启动开关拨到norflash启动,同时将usb下载线和开发板链接,usb转串口线也连接好。
E:打开开发板电源,并使用secureCRT 观察启动情况,并使用选择[v]该选项,这时supervivi-128,将处于等待状态。
F:使用Linux中的usb下载驱动将bootloader (images/Linux/supervivi-128)下载到开发板内存中去。
./dnw filename 30000000
注:下载完成后,安装辅助程序就会将bootloader移动到nandflash 中
G:在secureCRT 中选择选项[k],进行kernel 下载,此时安装辅助程序处于等待状态
H:进入虚拟机中将kernel(zImage_35) 下载到开发板内存中去。当下载完成后,安装辅助程序再将其移动到nandflash 中去。
./dnw filename 30000000
I:在secureCRT 中选择选项[y],进行文件系统的下载,此时安装辅助程序处于安装等待状态。
J:进入虚拟机将文件系统(root_fsqtopia_qt4)下载到开发板内存中去
./dnw filename 30000000
k:将开发板启动选项开关拨到nandflash。启动开发板。
注:为什么需要一个安装辅助程序,因为nandflash 并没有和CPU直接相连,需要通过控制器访问nandflash ,说白了安装辅助程序就是用来控制nandflash的控制器的。也就是说nandflash 并没有参与CPU的统一编址。
<GNU 关键字volatile >
a:对于硬件的操作代码中,经常且必须使用该关键字。比如
P1 = 0X010011
P1 = 0X001000
对于上面两个两个语句,对于编译器来讲,就会进行优化,只是执行第二句。在嵌入式开发中,可能这两条语句是设置硬件的两种硬件状态,而不是对内存的同一个单元赋值。
b:对于声明为volatile 的数据,对数据的操作都是基于内存的操作,不会将数据放入到CPU的内部寄存器中做预取处理,这样可以防止内存数据和寄存器数不同步(内部寄存器的数据已经被修改,但是内存中的数据没有更改)
<linux 系统操作IO内存空步骤>
a:request_men_region()
申请空间struct resource *request_mem_region(unsigned long start, unsinged long len, char *name) 该函数从start开始分配len字节长的内存空间。实际上是平台设备的资源的动态分配。(注:该函数并不是必须的,但是建议使用,该函数的作用是检查申请的资源是不是可用,如果可用,则申请成功,并标志为申请成功,然后其他驱动再想使用就会申请失败。)
b:ioremap()
该函数实际上是调用的phy_to_virt() 函数,应为在有操作系统的情况下不能直接操作硬件寄存器,所以需要将其转换成虚拟地址。
c:ioread32()/iowrite32()
d:iounmap()
e:release_men_region()
<PCI 设备的访问>
a:什么是PCI 配置空间?
PCI 配置空间是是指PCI device上的一些寄存器(存在于设备中)。
b:如何访问PCI 配置空间
cpu首先要访问pci控制器,pci控制器访问pci设备
pci设备有3种空间:配置空间、IO空间、mem空间,所以要区分每个pci设备就需要完全区分这3种空间。
其中mem空间就是pci设备分得的pci总线地址,每一个pci设备分得的pci地址空间是不会重叠的。cpu通过pci总线控制器访问不同的pci地址空间,就能访问到不同的pci设备。
x86的CPU只有内存和I/O两种空间,没有专用的配置空间,PCI协议规定利用特定的I/O空间操作驱动PCI桥路转换成配置空间的操作。这种机制使用了两个特定的32位I/O空间,即CF8h和CFCh。这两个空间对应于PCI桥路的两个寄存器,当桥路看到CPU在局部总线对这两个I/O空间进行双字 操作时,就将该I/O操作转变为PCI总线的配置操作。寄存器CF8h用于产生配置空间的地址(CONFIG-ADDRESS),寄存器CFCh用于保存 配置空间的读写数据(CONFIG-DATA)。
比较特殊的是pci配置空间,但是没有pci配置空间,也就不可能配置IO空间和mem空间。
每个pci设备(这里只逻辑设备,一个pci物理设备可以有多个逻辑设备,就是所谓的多功能pci设备)的配置空间的地址,是一个32位的地址。由总线号+槽位号+功能号组成。
pci总线控制器决定了pci总线号。
<Linux文件系统>
a:什么是inode?
首先inode块中保存文件系统中全部的inode.在Linux中所有的资源都被当作文件来看待。当系统中添加文件或硬件设备的时候,系统就会在inode块中为该文件或设备分配一个inode节点。这个inode节点中保存了大量的文件属性(注意:有两个文件属性不保存在inode 中,分别是文件名和节点号,原因是inode节点按照顺序排列,所以文件系统就采用了简单的算法,就可以得到inode节点号)。
b:怎么通过inode 访问磁盘上的文件?
在in偶的节点中存储这一个重要信息——保存了一个包含了13-15位指针元素的数组,这些元素是磁盘块区 的地址。系统就是依靠这些指针定位到磁盘中的文件。所以说inode是问价系统的核心。
c:超级快是什么?
如果说inode是文件系统的核心,那么超级块就是文件系统是心脏。主要是超级块中保存了全局文件信息(比如:磁盘使用空间,数据块可用空间,inode节点信息)。做个形象是比如,超级块就像是企业是资产负载表,一个文件系统有哪些信息都记录在这个表中。当操作系统启动的时候,操作系统把超级快中的信息复制包内存中,并间断性跟新其中的内容,这就导致内存中的超级块和硬盘中的超级块有可能是不同步的,如果出现意外掉电导致内存中的信息没有来得及写入磁盘,会导致系统不稳定。工程师往往使用命令sysn将没来的急写入超级块中的信息写入磁盘,重新开机会对比其中的信息并更新。这也是Linux比windows更加稳定的原因。
<ARM MMU虚拟地址转物理地址>
a:转换原理图
b:TTB(转换页表基地址)
这个基地址是由工程师来规定,该地址存在CP15 (总共有16个32位的寄存器)的C2中。
c:translation table
MMU会利用这张表来索引物理地址,系统首先使用虚拟地址的高12位[31-20],结合TTB索引到一级页表的位置。
根据索引的一级页表的后2位[1-0]来判断如何索引二级页表。
00:该次转换无效
01:粗页转换
10:段转换
截取该一级页表的高12位作为物理地址的高12位(寻址1M)作为基地址寻址,虚拟地址的低20位[19-0]作为偏移。
11:细页转换
细页转细页转换也是Linux系统采用的方式。截取一级页表的高20位[31-20]作为二级页表的基地址的索引。利用虚拟地址的中间10位[19-10],作为二级页表的索引。根据表项的最后两位判断是1k/4k/64k的页表
00:无效转换
01:64k
10:4k
11:1k
然后再根据虚拟地址的后10[9-0]位索引到物理地址.
<CP15协处理器的访问>
MCR{<cond>} <p>,<opcode_1>,<Rd>,<CRn>,<CRm>{,<opcode_2>}
MCR{<cond>} p15,0,<Rd>,<CRn>,<CRm>{,<opcode_2>}
其中,<cond>为指令执行的条件码。当<cond>忽略时指令为无条件执行。
<opcode_1>为协处理器将执行的操作的操作码。对于CP15协处理器来说,< opcode_1>永远为0b000,当< opcode_1>不为0b000时,该指令操作结果不可预知。
<Rd>作为源寄存器的ARM寄存器,其值将被传送到协处理器寄存器中。
<CRn>作为目标寄存器的协处理器寄存器,其编号可能是C0,C1,…,C15。
<CRm>和<opcode_2>两者组合决定对协处理器寄存器进行所需要的操作,如果没有指定,则将为<CRm>为C0,opcode_2为0,否则可能导致不可预知的结果。
<makefile 注解>
a:编译驱动模块
obj-m: 编译成内核模块
obj-y: 编译进内核
obj-n:不编译
<2440 内存起始地址为什么是0x30000000>
该芯片总共有27根地址总线,和8根片选信号线。CPU利用这几根线对外部的RAM和ROM进行编址。RAM的基地址被被设定为0x30000000。(注意:CPU需要通过存储控制器才能访问外部的rom和ram。)
<并发访问注意事项(互斥)>
任何拥有锁的代码都必须是原子的,不能休眠。
a:自旋锁:
(1)获得锁是要注意不能调用会导致休眠的API。
(2)拥有自旋锁之前必须禁止本地中断。
b:原子变量
原子变量不能是一个大于24位的数据。
<等待队列头和等待队列>
等待队列的实现
(1)双向列表:
struct list_head {
struct list_head *next, *prev;
};
(2)等待队列头:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
(3)等待队列:
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
<enum经常使用的地方>
enum 是一种用在大规模使用define定义常量的时候取代define。
比如有些地方要定义一个星期:
#define 1 Mon;
#define 2 Tue;
#define 3 Wed;
#define 4 Thu;
#define 5 Fri;
#define 6 Sat;
#define 7 Sun;
定义7个还好,但是多了就显得麻烦了,这时候可以使用enum代替。
<union的使用>
a:先说struct的定义
struct student
{
char mark;
long num;
float score;
};
{
char mark;
long num;
float score;
};
该结构体在内存中的分布方式:
调用函数:sizeof(struct student) =12;
b:再说union的定义
union test
{
char mark;
long num;
float score;
}
该结构体在内存中的分布:
调用函数sizeof(union test) = 4;
c:总结
struct 会为该结构体中的所有变量分配相印的内存,然而union 只会分配该结构体中的定的数据中占用内存最大的一个,并且只会保存最后写入该内存中的数据,覆盖之前的数据。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">