Linux字符设备驱动架构
Table of Contents generated with DocToc
说明
该文章是liode学习笔记,根据https://www.bilibili.com/video/BV1Yb4y1t7Uj?p=4&spm_id_from=pageDriver来做的总结
应用程序如何使用驱动
应用程序只能通过open,read,write等标准库函数来访问到驱动程序,不能直接访问到硬件,只有驱动程序能访问到硬件,因此,驱动程序要提供对应的drv_open,drv_read,drv_write等库函数对应的底层的驱动
函数,来操作硬件。
下面这些是应用程序可以做的:
1 | /* 2. 打开文件 */ |
为什么open某一个文件,就能找到驱动
linux通过文件的属性来找到的驱动,比如:
这个tty4是c字符设备,主设备号是4,次设备号是4;linux根据这三个来找,主要是前两个。
大概的流程是这样的,这是个c设备,所以驱动在chrdevs[]里面,索引就是主设备号,这样就找到了相应的驱动。
在fs/char_dev.c中定义了这个容器,里面装了很多字符驱动
1 | static struct char_device_struct { |
怎么写驱动
1. 确定主设备号(数组的索引),0的话为系统自动分配
使用下面的命令可以查看有哪些驱动,哪些主设备号被占用了:
1 | book@100ask:/dev$ cat /proc/devices |
比如我现在就像被分配,那我就选一个0
2. 构造结构体 file_operations
用到什么构造什么,常用的read,write之类的,不需要全部构造,这是个很大的结构
在fs.h中:
1 | struct file_operations { |
使用的时候像下面这样,左边是成员名,右边是我们的函数名。
1 | /* 2. 定义自己的file_operations结构体 */ |
这个.是什么意思呢?这是c99引入的语法,如果没有这个东西,就必须像下面这样初始化结构体了
1 | /* 2. 定义自己的file_operations结构体 */ |
没有的项也必须写出来,而且必须要按顺序赋值。非常坑。
3. 注册驱动
注册也就是说,要把某个结构体,放到某个容器中去。
1 | major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */ |
第一个是主设备号,0是说系统返回一个可以用的。只要驱动文件的主设备号等于这个值,就可以
访问到驱动程序了。
那么谁来调用这个驱动程序呢?
4. 描述入口函数
就像应用程序有main入口一样,驱动也有入口函数,我们写的驱动程序,需要用module_init(**)
来描述它。当安装驱动的时候,就会使用到。
比如下面这样,这个hello_init函数经过module_init之后,就会被识别为入口函数了,而上面的注册,
就在这个函数里面。
1 | module_init(hello_init); |
1 | static int __init hello_init(void) |
其他
module_init是如何实现的
module_init是将一个普通函数,注册为驱动入口函数的功能。那么它是怎么实现这个功能的呢?
首先,这是一个宏,在module.h中被定义。
如果定义了module,它就是下面的宏,而我们的驱动都是一个module的形式
1 |
比如我们的hello_init是一个普通的函数,经过宏展开变成了什么呢?
1 | static inline initcall_t __inittest(void) \ |
变成了两个函数,一个是测试函数,并不重要,下面的很重要
1 | int init_module(void) __attribute__((alias(#hello_init))); |
相当于定义了一个init_module函数,同时它的别名是hello_init
也就是说,一个驱动程序,他的入口函数永远都是init_module,只不过它的别名不一样。
换句话说,如果你不想多一行代码:
1 | module_init(hello_init); |
那么就不要定义为hello_init,而需要定义为init_module
次设备号有什么作用
根据主设备号就已经确定了唯一的驱动,那么次设备号又有什么作用呢?
次设备的作用由驱动程序自己决定,完全可以不用,也可以使用。这个号码,可以作为参数被驱动程序
都进去,进而可以发挥作用。
比如:有两个led,这两个led的驱动都是一样的,但是他们的地址不同,那么我们就可以公用一个驱动程序,比如他们的驱动主设备号都是60,但是次设备号一个是1,一个是2,进去驱动之后,驱动根据1,2来
操作不同的地址来点灯。
open的时候文件名是怎么传递给内核的
寄存器
编译进内核重名吗?
每一个驱动程序最终的名字都是init_module,会重名吗?
不会,编译成ko文件与编译进内核不同,编译进内核的话,注册的宏定义会发生变化,builtin宏如下
第一层
1 |
第二层
1 |
第三层
1 |
第四层
1 |
当hello_init为驱动函数名的手,最终得到
1 | static initcall_t __initcall_hello_init6 __used \ |
也就是说,当驱动程序编译进内核的时候,使用module_init(hello_init),最终定义了一个叫__initcall_hello_init6的结构体,它有一个属性:段属性(这些驱动程序的结构体都放在一起)
而结构体的值等于hello_init这个函数指针,而这个结构体的名字是不会重名的。