以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、系统工作原理
1、工作流程
系统的整体工作流程是:应用层—>API—>设备驱动—>硬件。
操作系统提供的API包括open、read、write、close等函数,它们只是一种操作逻辑,并不涉及具体的硬件操作。驱动源码提供真正的open、read、write、close等函数实体。
换个形象的说法,API就好比“吃饭”这个操作逻辑(没有说明具体怎样吃),而驱动程序详细地说明了吃饭的方式,比如用什么餐具、吃什么东西、先吃什么后吃什么等等。
2、举例说明
比如通过QQ与他人进行文件传输,则需要调用操作系统提供的与套接字有关的API,而这些API的逻辑操作最终将映射为网卡驱动中的某些具体操作函数。网卡驱动里面的具体操作函数,通过对网卡这个硬件(的寄存器)的一些设置,达到数据传输的目的。
二、注册字符设备
1、如何表示设备与驱动
(1)设备的表示方法
1)与设备有关的两个概念:设备文件、设备编号。
在Linux系统中一切皆文件,所以设备也表现为文件,这种文件叫做设备文件。设备文件的名字就是设备的名字。设备文件一般在/dev目录中。
不同的设备具有不同的设备号,以区分不同的设备。设备号由主设备号和次设备号组成,其中主设备号表示某一种类的设备,次设备号用来区分同一类型的设备。
设备文件名与设备号都是设备的象征,就好比某个同学的名字和学号都指向这个同学。
2)设备文件的创建
在/dev目录下使用ls -l查看字符设备文件(开头字母为'c'的文件),可以得知已有的设备文件的主次设备号。
xjh@ubuntu:/dev$ ls -l 总用量 0 crw-rw---- 1 root video 10, 175 Jul 16 22:09 agpgart crw------- 1 root root 10, 235 Jul 16 22:09 autofs drwxr-xr-x 2 root root 640 Jul 16 22:09 block drwxr-xr-x 2 root root 100 Jul 17 19:23 bsg crw------- 1 root root 10, 234 Jul 16 22:09 btrfs-control drwxr-xr-x 3 root root 60 Jul 16 22:09 bus lrwxrwxrwx 1 root root 3 Jul 16 22:09 cdrom -> sr0 drwxr-xr-x 2 root root 3560 Jul 19 12:56 char crw------- 1 root root 5, 1 Jul 16 22:09 console lrwxrwxrwx 1 root root 11 Jul 16 22:09 core -> /proc/kcore drwxr-xr-x 2 root root 60 Jul 16 22:09 cpu
使用cat /proc/devices可以查看内核中已经注册过的字符设备驱动和块设备驱动(见下文),然后找到一个没被占用的主设备号,使用“mknod /dev/xxx c 主设备号 次设备号”来创建设备文件,其中xxx表示设备文件名,c表示字符设备。
注意,xxx不能使用vim来打开,但可以用ls /dev/xxx -l查看。
/dev目录下的设备文件,与/poc/devices文件中的设备列表,它们之间有什么关系?proc目录是一个虚拟文件系统,可以为linux用户空间和内核空间提供交互;它只存在于内存中,而不占实际的flash或硬盘空间。/proc/devices/里的设备是加载驱动程序时生成的,而/dev/下的设备是通过创建设备节点生成的,用户通过此设备节点来访问内核里的驱动。
(2)驱动的表示方法
可以这么理解,file_operation 结构体就象征着驱动。
- 这个结构体的元素主要是函数指针,用来挂接实体函数地址。
- 每个设备驱动都需要一个该结构体类型的变量。
- 设备驱动向内核注册时,提供该结构体类型的变量。
- 该结构体的成员函数是字符设备驱动程序设计的主体内容。
- 这些函数在应用程序进行open()、read()等系统调用时被内核调用。
关于此结构体的更多解释,见博客:file_operations结构体_天糊土的博客-CSDN博客。
2、字符设备的注册:register_chrdev()函数
在没有驱动模型前(注意不是驱动框架。驱动模型是面向对象的设计,区分设备、驱动和总线;而驱动框架是指驱动的分层设计,包括核心层和具体操作层),驱动与设备的注册混为一谈,设备号和 file_opertion紧结合,一个驱动的信息包含两者。
使用硬件设备之前,需要先注册其驱动程序。驱动程序利用内核提供的注册函数,向内核注册自己。注册之后,内核可以调用此驱动程序去使用硬件。
这里说的注册函数是指:register_chrdev()函数。
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) {return __register_chrdev(major, 0, 256, name, fops); }
函数所在文件路径 /include/linux/fs.h 函数作用 向内核注册某设备的 file_operations 返回值 major如果设置为0,则返回自动分配的主设备号;
如果设置为指定的主设备号,成功则返回值为0,失败返回负数。
参数说明 major,表示当前设备的主设备号,范围是1~254。可以自己指定,也可以设置为0让内核自动分配。犹如学号。
name,表示当前设备的驱动名称,犹如名字。
fops,是file_operations结构体指针。
inline修饰符说明 此函数定义在头文件里面,如果被两个文件包含,就会重复定义。使用inline后,就表示函数体,而不是函数定义。
三、内核如何管理字符设备驱动
1、管理方法
内核中使用一个数组,来存储已注册的字符设备(或者说驱动,因为这里还没有驱动模型的概念)。register_chrdev函数将驱动的信息(包括设备号和 file_operations)存储在数组中相应的位置。
2、查看已注册的设备驱动
使用cat /proc/devices可以查看内核中已经注册过的字符设备驱动和块设备驱动。
从中我们看到:
(1)有很多同类设备的主设备号是相同的;
(2)有些编号默认分配给了某些设备;
(3)由于使用数组来存储的缘故,设备编号只有1~254。
xjh@ubuntu:/proc$ cat /proc/devices Character devices:1 mem4 /dev/vc/04 tty4 ttyS5 /dev/tty5 /dev/console //省略 253 watchdog 254 rtcBlock devices:1 ramdisk2 fd 259 blkext7 loop //省略 254 mdp