目录
前言:
写本篇文章就是记录一下初学Linux驱动对于驱动的疑问,为什么写好了一个驱动程序,然后在应用中调用,例如open、read、write等函数,他会直接去访问你写好的驱动里的函数。
1.单片机和Linux驱动之间的区别
- 在单片机中可以直接读写寄存器
- 在linux中无法直接读取寄存器,只能通过驱动进行硬件操作。
-
单片机:MCU微控制器
跑Linux的:MPU微处理器,有MMU内存管理单元(有权限管理)
单片机程序中没有严格的区分APP和Driver,可以直接调用驱动,通过分层思想进行人为区分,Linux中有严格的划分,APP不能直接调用驱动函数。
2.APP如何找驱动
系统内部,会将open函数中的参数保存在寄存器里 ,如文件地址保存在a寄存器,操作指令保存在R寄存器,然后会调用一条swi汇编指令,这个swi汇编指令最终会导致某个硬件出现异常,一但发送异常就是进入到异常处理函数(由内核提供),在异常处理函数中根据R寄存器中的值,判断你是要调用内核提供的什么函数,如sys_open。
通过swi这种汇编指令触发一个异常,cpu执行异常处理函数(内核提供),在执行之前,设置一个寄存器,用于区分open、read、write
swi是软件异常,software interrupt
- open、write、read是一个库写的,如glibc
- glibc中通过设置寄存器,并触发swi
- 然后cpu处理异常,分辨寄存器中的内容
- 判断内容是普通文件还是特殊文件,如果是设备,则调用驱动
为什么打开某个设备文件,能够被正确识别:
根据主设备号找到驱动程序。
以上述的代码片段为例,为什么要去打开“/dev/hello”,我们可以先去查看一下/dev目录下有什么。
在开发板上执行:
[root@100ask:/]# cd /dev/
[root@100ask:/dev]# ls -l
可以查看到如下:
前面带有c开头的都是字符设备节点,4则是他们的主设备号(不是所有的字符设备节点主设备号都是4,这只是我截图恰巧),4旁边就是他们的次设备号。(这是大概的说法,便于理解)
驱动程序是存储在一个结构体链表中,当你使用open去打开某个字符设备节点时 ,他会根据主设备号去到链表中查找驱动程序。
3.数据传输
1.APP和驱动间的数据传输
- APP无法直接访问内核:否则内核态容易被破坏了。
- APP也就无法传递数据给驱动,驱动属于内核的一部分。
- 驱动也无法直接方位APP的变量。
- 驱动要访问APP的数据,必须使用这2个函数:
-
copy_to_user
-
copy_from_user
-
2.驱动和硬件间数据传输
4.如何写驱动程序
这篇主要讲app访问驱动的原理,具体怎么写一个驱动程序可以查看这篇文章:
1.确定主设备号
既然是根据主设备号查找驱动程序,当然得先分配主设备号。可以通过/proc/devices来找到目前被占用的,也可以直接设置为0,让系统自行选择没有占用的。
2.构造结构体
驱动程序是存储在结构体链表中的,当然得为他构造一个结构体(struct file_operations),才能将他放入链表中。
3.注册
注册就是将这个结构体放入链表中,然后会给你返回一个主设备号。那么以后当我们要访问这个驱动程序的时候,只需要根据主设备号进入链表中查询就可以了。
4.入口
注册函数当然得有人来调用了,这个人就是入口函数,当我们装载一个驱动时,首先会进入驱动程序的入口函数,这些注册函数都是写在入口函数中,将我们构造的结构体放入链表。
5.入口详解
我们以有多个led为例:
当你使用register_chrdev
注册字符设备驱动后得到一个主设备号,这个主设备号用于标识设备的类型,通常代表一类设备。
对于有多个 LED 灯的情况,你可以使用class_create
创建一个设备类,这样有助于对这一类 LED 设备进行统一的管理和分类展示。设备类主要是在逻辑上对具有相似特性的设备进行分组,它在 /sys/class/
下创建一个目录,方便用户和系统以一种更有条理的方式查看和管理设备。
然后使用device_create
为每个具体的 LED 灯创建设备实例,并在 /dev/
下创建对应的设备文件节点。通过这种方式,每个 LED 灯都有一个对应的设备文件,可以被用户空间程序通过操作这些设备文件来控制相应的 LED 灯。每个设备实例可以有不同的次设备号,用于区分不同的 LED 灯。
就像是他们有着同一个主设备号,代表他们的逻辑相似,但他们有着不同的次设备号,代表他们是不同的个体,然后 device_create会为他们创建具体的设备节点。
6.设备树
1.内核对设备树的处理
dtb中每一个节点都被转换为device_node结构体
2.设备树语法
设备树(Device Tree)是一种描述硬件设备的数据结构,用于向操作系统传递硬件信息。以下是设备树的基本语法:
-
节点(Nodes):
- 设备树由一个或多个节点组成,每个节点代表一个硬件设备或硬件组件。
- 节点用大括号括起来,节点名称放在括号前面,例如:
node-name {... }
。 - 节点名称通常反映了硬件设备的类型或功能。
-
属性(Properties):
- 节点可以包含一个或多个属性,用于描述硬件设备的特性。
- 属性用键值对的形式表示,例如:
property-name = "value";
。 - 属性值可以是字符串、整数、数组等数据类型。
-
引用(References):
- 节点可以通过引用其他节点来表示硬件设备之间的关系。
- 引用使用节点路径的形式,例如:
&node-name
。 - 引用可以用于指定设备的父节点、子节点、连接的设备等。
-
子节点(Child Nodes):
- 节点可以包含子节点,用于表示硬件设备的层次结构。
- 子节点放在父节点的大括号内,例如:
parent-node { child-node {... } }
。
-
标签(Labels):
- 节点可以有一个或多个标签,用于方便地引用节点。
- 标签用尖括号括起来,放在节点名称后面,例如:
node-name<label1><label2> {... }
。
以下是一个简单的设备树示例:
/dts-v1/;
/ {
model = "My Board";
compatible = "my-board,generic-arm";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
}
}
memory {
device_type = "memory";
reg = <0x0 0x20000000 0x0 0x40000000>;
}
};
在这个示例中,根节点 /
代表整个设备,包含了 model
和 compatible
属性,以及 cpus
和 memory
子节点。cpus
节点包含了一个 cpu@0
子节点,代表一个 CPU。memory
节点描述了设备的内存。
3.节点的常见属性
在设备树中,属性用于描述硬件设备的特性和配置信息。以下是一些常见的设备树属性:
-
compatible:用于指定设备的兼容性列表。它是一个字符串数组,每个字符串表示一种设备的兼容性。操作系统可以根据这个属性来识别和加载相应的设备驱动程序。
- 例如:
compatible = "fsl,imx6qdl-sabresd", "fsl,imx6q"
表示该设备与飞思卡尔 i.MX6QDL SabreSD 开发板以及其他兼容 i.MX6Q 的设备兼容。
- 例如:
-
model:描述设备的型号或名称。
- 例如:
model = "My Board Model"
。
- 例如:
-
reg:用于描述设备的寄存器地址范围。通常是一个地址和长度的列表。
- 例如:
reg = <0x10000000 0x1000 0x20000000 0x2000>
表示设备的寄存器地址范围从 0x10000000 开始,长度为 0x1000,另一个地址范围从 0x20000000 开始,长度为 0x2000。
- 例如:
-
interrupts:描述设备的中断请求(IRQ)线。
- 例如:
interrupts = <0 23 4>
表示设备使用中断号 23,触发方式为上升沿触发(4 表示上升沿触发,具体的触发方式定义可能因平台而异)。
- 例如:
-
clocks:指定设备使用的时钟源。
- 例如:
clocks = <&clocks clk1>, <&clocks clk2>
表示设备使用名为 clk1 和 clk2 的时钟源。
- 例如:
-
status:表示设备的状态,可以是 "okay"、"disabled" 等。
- 例如:
status = "disabled"
表示设备当前被禁用。
- 例如:
-
address-cells 和 size-cells:用于描述子节点的地址和大小的单元数量。在设备树的层次结构中,父节点可以包含多个子节点,每个子节点可能有不同的地址和大小。这两个属性告诉操作系统如何解析子节点的地址和大小信息。
- 例如:在一个总线节点中,
address-cells = <2>
和size-cells = <1>
表示子节点的地址使用两个单元(通常是 32 位地址和 32 位地址偏移),大小使用一个单元(通常是 32 位长度)。
- 例如:在一个总线节点中,
-
#address-cells 和 #size-cells:与
address-cells
和size-cells
类似,但用于描述当前节点的地址和大小的单元数量。- 例如:在一个设备节点中,
#address-cells = <1>
和#size-cells = <0>
表示该设备的地址使用一个单元(通常是 32 位地址),大小不使用单元(即固定大小)。
- 例如:在一个设备节点中,