100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 设备驱动理论详解 Linux操作系统原理与应用

设备驱动理论详解 Linux操作系统原理与应用

时间:2021-11-25 06:28:12

相关推荐

设备驱动理论详解 Linux操作系统原理与应用

目录

一、概述

1、设备是什么

2、设备的分类

3、设备驱动分层结构

4、设备的信息

5、设备驱动程序是什么

6、设备管理

7、用户进程请求设备进行输入/ 输出的简单流程

二、设备驱动程序框架

三、I/O空间的管理

1、I/O 端口

2、I/O 内存

3、I/O资源管理

4、访问 I/O 端口空间和 I/O 内存资源

四、字符设备驱动程序

1、字符设备的数据结构

2、分配和释放设备号

3、字符设备驱动的组成

4、加载和卸载函数

5、实现 file_operations 中定义的函数

6、编写一个字符设备驱动程序的具体流程

五、块设备驱动程序

1、块设备驱动程序的注册

2、块设备请求

3、块设备驱动程序的实现

一、概述

计算机最基本的的硬件是CPU,内存和输入输出(I/O)设备,文件操作是对设备操作的组织和抽象,而设备操作则是对文件操作的最终实现。

1、设备是什么

Linux把设备纳入文件系统的范畴来管理。即把设备看成是文件,具体含义如下:

(1)每个设备都对应一个文件名,在内核中也就对应一个索引节点。

(2)对文件操作的系统调用大都适用于设备文件。

(3)从应用程序的角度看,设备文件逻辑上的空间是一个线性空间(起始地址为0,每读取一个字节加 1 )。从这个逻辑空间到具体设备物理空间(如磁盘的磁道,扇区)的映射则是由内核提供,并被划分为文件操作和设备驱动两个层次。

文件系统通常都建立在块设备上,比如磁盘。

由此可以看出,对于一个具体的设备而言,文件操作和设备驱动是一个事物的不同层次。从这种观点出发,概念上可以把一个系统划分为应用、文件系统和设备驱动三个层次。

2、设备的分类

Linux 将设备分成三大类,就是块设备、字符设备和网络设备。

(1)块设备:以块或扇区为单位,成块进行输入/输出的设备。如:磁盘,软盘驱动器、CD-ROM驱动器和闪存等。

(2)字符设备:以字符(字节)为单位,逐个字符进行输入/输出的设备。如:键盘,打印机。

(3)网络设备:用来将各类服务器、PC、应用终端等节点相互连接,构成信息通信网络的专用硬件设备。如:路由器,交换机,集线器,网关等。

3、设备驱动分层结构

一个系统划分为应用、文件系统和设备驱动三个层次。

(1)第一层抽象,设备驱动。从物理空间到逻辑空间的抽象,就是把柱面、磁道、扇区这样的三维数据转换为一维线性地址空间的“块”。

(2)第二层抽象,文件系统。将块抽象和组件为文件系统。

对于普通文件,即磁盘文件,文件的逻辑空间在文件系统层内按具体文件系统结构和规则映射到设备的线性逻辑空间,然后在设备驱动层进一步从设备的逻辑空间映射到其物理空间。这样一共经历了两次映射,或者反过来说,磁盘设备的物理空间经过两层抽象而成为普通文件的线性逻辑空间。

对于设备文件,则文件的逻辑空间通常直接等价于设备的逻辑空间,所以在文件系统层不需要映射。

4、设备的信息

(1)一个物理设备用唯一的索引节点标识,(与文件用唯一的索引节点标识相似),索引节点中记载着与特定设备建立连接所需的信息。

(2)这种信息由三部分组成:文件(包括设备)的类型、主设备号和次设备号。其中设备类型和主设备号结合在一起唯一确定了设备的驱动程序及其接口,而次设备号则说明目标设备是同类设备中的第几个。

(3)要使一项设备在系统中成为可见、应用程序可以访问的设备,首先要在系统中建立一个代表此设备的设备文件,这是通过 mknode 命令或者 mknode() 系统调用实现的。除此之外,更重要的是在设备驱动层要有这种设备的驱动程序。

(4)设备驱动层直接与物理设备打交道,在实际的实现中则因系统的结构和具体设备的物理特性不同而有不同的驱动方式。

(5)多数设备都是中断驱动的,而块设备往往都采用DMA(直接访问内存)方式,所以物理设备的输入/输出从本质上说大都是异步的。

(6)文件操作既可以是同步的,也可以是异步的,但多数情况下是异步的。

(7)从键盘读入一个字符,用户进程通过系统调用read()企图从标准输入文件读一个字符,但是真正的、物理意义上的从键盘读入通常并不是发生在用户进程调用read()的瞬间。如果在此之前,用户已经按了键,那么所敲的字符已经通过中断服务程序读了进来,放在缓冲区中等待由进程读取,此时上述read()操作立即就可以完成而返回这个字符。但是,如果缓冲区中没有字符可读,那当前进程通常要睡眠等待。等待到什么时候?等到用户按键的时候,那是异步的,也就是无法预测何时会发生的。从这个意义上说,设备驱动程序是上层的同步操作与低层的异步操作之间的桥梁。

5、设备驱动程序是什么

设备驱动程序是处理和管理硬件控制器的软件,是内核和硬件之间的接口(系统调用是内核和应用程序之间的接口)。它为应用程序屏蔽了硬件的细节。

6、设备管理

Linux 内核的设备管理是由一组运行在特权级上,驻留在内存以及对底层硬件进行处理的共享库的驱动程序来完成的 。

设备管理的一个基本特征是设备处理的抽象性,即所有硬件设备都被看成普通文件,可以通过用操纵普通文件相同的系统调用来打开、关闭、读取和写入设备 。

7、用户进程请求设备进行输入/ 输出的简单流程

(1)用户进程发出输入输出请求时,系统把请求处理的权限放在文件系统。

(2)文件系统通过驱动程序提供的接口,将任务下放到驱动程序。

(3)驱动程序根据需要对设备控制器进行操作。

(4)设备控制器再去控制设备本身。

这样通过层层隔离,对用户进程基本上屏蔽了设备的各种特性,使用户的操作简单易行,不必去考虑具体设备的运作,就像操作文件一样去操作设备。因为实际上在驱动程序向文件系统提供的接口已经屏蔽掉了设备的电器特性。

设备控制器对设备本身的控制是电器工程师所关心的事情,操作系统对输入/输出设备的管理只是通过文件系统和驱动程序来完成。也就是说在操作系统中,输入/输出系统所关心的只是驱动程序。

二、设备驱动程序框架

由于设备种类繁多,相应的设备驱动程序也非常多。为了让设备驱动程序的开发建立在规范的基础上,就必须在驱动程序和内核之间有一个严格定义和管理接口,例如SVR4提出了 DDI/DKI规范,其含义就是设备与驱动程序接口 / 设备驱动程序与内核接口。

Linux 的设备驱动程序与外设的接口与DDI/ DKI 规范相似,可以分为以下三个部分:

(1)驱动程序与内核的接口,这是通过数据结构file_operations 来完成的。

(2)驱动程序与系统引导的接口,这部分利用驱动程序对设备进行初始化。

(3)驱动程序与设备的接口,这部分描述了驱动程序如何与设备进行交互,这与具体设备密切相关。

其中struct file_operations 是驱动程序主要关注的对象,其结构如下:

struct file_operations{int (*open) (struct inode *, struct file *); // 打开int (*close) (struct inode *, struct file *); // 关闭loff_t (*llseek) (struct file *, loff_t, int); // 修改文件当前的读写位置ssize_t (*read) (struct file *, char *, size_t, loff_t *); // 从设备中同步读取数据ssize_t (*write) (struct file *, const char *, size_t, loff_t *); // 向设备中发送数据int (*mmap) (struct file *, struct vm_area_struct *); // 将设备的内存映射到进程地址空间int (*ioctl) (struct inode *, strcut file *, unsigned int, unsigned long); // 执行设备上的I/O控制命令unsigned int (*poll) (struct file *, struct poll_table_struct *); // 轮询,判断是否可以进行非阻塞的读取或者写入...};

三、I/O空间的管理

一般来说,一个外设的寄存器通常被连续编址。CPU对外设I/O端口物理地址的编址方式有两种:I/O端口(独立编址)和I/O内存(统一编址)

设备驱动程序要直接访问外设或其接口卡上的物理电路,通常以寄存器的形式出现。外设寄存器也称为I/O端口,通常包括控制寄存器、状态寄存器和数据寄存器三类。具体描述如下:

(1)CPU把要发给设备的命令写入控制寄存器,并从状态寄存器中读出表示设备内部状态的值。

(2)CPU还可以通过读取输入寄存器的内容,从设备取得数据。

(3)CPU还可以通过向输出寄存器中写入字节而把数据输出到设备。

1、I/O 端口

(1)什么是I/O 端口编址

一些体系结构的CPU(典型如x86)则为外设专门实现了一个单独的地址空间,称为“I/O 端口空间”。这是一个与CPU的内存物理地址空间不同的地址空间,所有外设的I/O 端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如x86的 IN 和 OUT 指令)来访问这一空间中的地址单元(也即 I/O 端口),这就是所谓的“I/O端口”,即 I/O 端口编址。

(2)特点:与内存物理地址空间相比,I/O 地址空间通常都比较小,如x86CPU的 I/O 空间就只有 64 KB(0~0xffff)。其实是一个缺点。

2、I/O 内存

有些体系结构的CPU(如PowerPC,m68k等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设的 I/O 端口的物理地址就被映射到CPU的单一物理地址空间中,从而成为内存的一部分。此时,CPU可以像访问一个内存单元那样访问外设 I/O 端口,而不需要设立专门的外设 I/O 指令。这就是所谓的“I/O 内存”,即I/O内存编址。

3、I/O资源管理

Linux 将基于 I/O 端口和 I/O 内存的映射方式统称为“I/O” 区域( I/O Region)。

Linux 设计了一个通用的数据结构 resource 来描述各种 I/O 资源,该结构定义在include/linux/ioport.h 头文件中:

struct resource{resource_size_t start; // 资源范围的开始resource_size_t end;// 资源范围的结束const char *name; // 资源拥有者的名字unsigned long flags;// 各种标志struct resource *parent, *sibling, *child; // 指向资源树中父,兄以及孩子的指针};

资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。所有的同种资源都插入到一个树状结构中;节点的孩子被收集在一个链表中,其第一个元素由 child 指向,sibling 字段指向链表中的下一个节点。

当前分配给 I/O 设备的所有 I/O 地址的树都可以从 /proc/ioports 文件中查看,例如:

$ cat /proc/ioports

任何设备驱动程序都可以使用下面的三个函数申请、分配和释放资源,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址。

request_resource() // 把一个给定范围分配给一个 I/O 设备allocate_resource() // 在资源树中寻找一个给定大小和排列方式可用的范围release_resource() // 释放以前分配给 I/O 设备的给定范围

I/O 区域仍然是一种 I/O 资源,使用下面的函数来对 I/O 区域进行操作的接口函数。

__request_region() // I/O 区域的分配__release_region() // I/O 区域的释放__check_region()// 检查指定的 I/O 区域是否已被占用

对 I/O 端口空间进行操作的接口函数

request_region() // 请求在 I/O 端口空间中分配指定范围的 I/O 端口资源check_region()// 检查 I/O 端口空间中的指定 I/O 端口资源是否已被占用release_region() // 释放 I/O 端口空间中的指定 I/O 端口资源

对 I/O内存资源进行操作的接口函数

request_mem_region() // 请求分配指定的 I/O 内存资源check_mem_region()// 检查指定的 I/O 内存资源是否已被占用release_mem_region() // 释放指定的 I/O 内存资源

4、访问 I/O 端口空间和 I/O 内存资源

在读写 I/O 端口时要注意,大多数平台都区分 8 位、16 位和 32 位的端口。

如果CPU读写其 I/O 端口的速度太快,那就可能会发生丢失数据的现象。解决方法是在两次连续的 I/O 操作之间插入一段微小的时延,以便等待慢速外设,即“暂停 I/O”。

Linux 实现了一系列读写 I/O 内存的函数,这些函数在不同的平台上有不同的实现。

四、字符设备驱动程序

(这里只讲理论,具体实现在我的另外一篇博客里)

一般认为,一个主设备号对应一个驱动程序,当然也可以一个主设备号对应多个驱动程序,但是一个次设备号对应一个设备。一个驱动程序可以管理多个此类型的设备。

1、字符设备的数据结构

Linux 内核中使用 struct cdev 来表示一个字符设备,该结构位于 linux/include/linux/cdev.h 文件中:

struct cdev{struct kobject kobj; // 驱动模型的基础对象struct module *owner; // 设备驱动所属的内核模块const struct file_operations *ops; // 定义一个文件操作结构体指针struct list_head list;dev_t dev; // 设备号,前 12 位主设备号,后 20 位为次设备号unsigned int count;};

cdev 结构是内核对字符设备驱动的标准描述,在实际的设备驱动开发中,通常使用自己定义的结构体来描述一个特定的字符设备,这个自定义的结构体必然会包含 cdev 结构,另外还要包含一些描述这个具体设备某些特性的字段。

2、分配和释放设备号

对于每一设备,必须有一个唯一的设备号与之对应。对于设备号有以下几个常用的宏(定义于 linux/include/linux/kdev_t.h 文件中):

#define MINORITS 20 // 次设备号的位数#define MINORMASK ((1U << MINORITS) - 1)// 次设备号掩码#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 从设备号提取主设备号#define MINOR(dev) ((unsigned int) ((dev) & MINORBITS)) // 从设备号提取次设备号#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi)) // 通过主次设备号组合出设备号

在设备驱动程序中,首先向系统申请设备号。申请的设备号一般都是一段连续的号,有两种方法申请,分别用下面两个函数实现:

// 一般用于已知主设备号的情况int register_chrdev_region(dev_t from, unsigned count, const char *name)// 一般用于未知主设备号的情况,动态申请一个设备号int alloc_chrdev_region(dev_t *dev, unsigned baseminor, usigned count, const char *name)

3、字符设备驱动的组成

实现一个基本的字符设备驱动程序需要完成三个部分:字符设备驱动模块的加载、卸载函数和 file_operations 结构中的成员函数。其中 file_operations 结构体中包含许多函数指针,这些函数指针是字符设备驱动和内核的接口,实现该结构中的这些函数也是整个字符设备驱动程序的核心工作。

4、加载和卸载函数

字符设备驱动程序是以内核模块的形式加载到内核的,因此程序中必须有内核模块的加载和卸载函数。其中,加载函数完成的工作有设备号的申请、cdev 的注册。而对于字符设备卸载函数而言,所做的工作就是加载的逆向操作:

(1)将 cdev 从系统中注销

(2)释放设备结构体所占用的内存空间

(3)释放设备号。

5、实现 file_operations 中定义的函数

file_operations 中定义的函数很多,最基本的函数包括 open()、release()、read() 和 write()。对于这些函数的具体实现还要根据具体的设备要求来完成。

6、编写一个字符设备驱动程序的具体流程

(1)定义一个设备结构体 cdev

(2)定义一个设备结构体对象 dev

(3)定义并实现file_operations 函数

(4)定义一个加载函数,完成设备号的申请和 dev 的注册

(5)定义一个卸载函数,将 dev 从系统中注销,释放设备结构体所占用的内存空间,释放设备号

(6)调用内核模块函数导入加载函数和卸载函数,应声明许可证。

其中,(3)、(4)、(5)可以调换顺序,只有符合函数识别的语法都行,不过不建议这样操作。(这里只讲理论,具体实现在我的另外一篇博客里)

五、块设备驱动程序

(这里只讲理论,具体实现在我的另外一篇博客里)

块设备驱动程序提供了对面向块的设备的访问,这种设备以随机访问的方式传输数据,并且数据总是具有固定大小的块,因此,对块设备的操作要经过文件页面缓存(数据缓冲层)。

1、块设备与字符设备的区别

(1)块设备上可以 mount(挂载)文件系统,而字符设备是不可以的。如磁盘(块设备)上有文件系统,而键盘(字符设备)却没有。

(2)数据经过块设备相比字符设备需要多经历一个数据缓冲层。这样做的根本原因是为了提高系统整体性能。

(3)块设备驱动程序的接口比字符设备的更加复杂。

1、块设备驱动程序的注册

和字符设备驱动程序一样,内核使用主设备号来标识块设备驱动程序,但块主设备号和字符主设备号是互不相干的。即一个主设备为 32 的块设备和具有相同的主设备号的字符设备可以同时存在,因为它们具有各自独立的主设备号分配空间。

用来注册和注销块设备驱动程序的函数如下:

#include <linux/fs.h>int register_blkdev(unsigned int major, const char *name, struct block_device_operations *bdops);int unregister_blkdev(unsigned int major, const char *name);

其中 struct block_device_operations 是块驱动程序的接口,定义如下:

struct block_device_operations{int (*open) (struct inode *, struct file *); // 打开设备文件int (*release) (struct inode *, struct file *); // 关闭对块设备文件的最后一个引用// 在块设备文件上发出 ioctl() 系统调用int (*ioctl) (struct inode *, sstruct file *, unsigned, unsigned long);int (*check_media_change) (kdev_t); // 检查介质是否已经变化int (*revalidate) (kdev_t);// 检查块设备是否持有有效数据};

需要注意的地方,block_device_operations 接口中没有 read() 或者 write() 操作。所有涉及块设备的 I/O 通常由系统进行缓冲处理,用户进程不会对这些设备执行直接的 I/O 操作。

在也用户模式下对块设备的访问,通常隐含在对文件系统的操作中,而这些操作能够从 I/O 操作缓冲当中获得明显的好处。但是,对块设备的“直接”I/O 访问,比如在创建文件系统时的 I/O 操作,也一样要通过 Linux 的缓冲区缓存。为此,内核为块设备提供了一组单独的读写函数 generic_file_read() 和 generic_file_write() ,驱动程序不必理会这些函数。

然而,块设备驱动程序最终必须提供完成实际块 I/O 操作的机制。在 Linux 当中,用于这些 I/O 操作的方法称为 request(请求)。request 方法同时处理读取和写入操作。

在块设备的注册过程中,必须告诉内核实际的 request 方法。然而,该方法并不在 block_device_operations 结构中指定(这出于历史和性能两方面的考虑),相反,该方法和用于该设备的挂起 I/O 操作队列关联在一起。默认情况下,对每个主设备号并没有这样一个对应的队列。块设备驱动程序必须通过 blk_init_queue 初始化这一队列。队列接口定义如下:

#include <linux/blkdev.h>blk_init_queue(request_queue_t *queue, request_fn_proc *request);blk_cleanup_queue(request_queue_t *queue);

注册和注销一个驱动程序模块时所要的调用的函数关系:

(1)加载模块时,insmod命令调用init_module()函数,该函数调用register_blkdev()和blk_init_queue()分别进行驱动程序的注册和请求队列的初始化。

(2)register_blkdev()把块驱动程序接口block_device_operations加入blkdevs[]表中。

(3)blk_init_queue()初始化一个默认的请求队列,将其放入blk_dev[]表中,并将该驱动程序的 request 函数关联到该队列。

(4)卸载模块时,rmmod命令调用cleanup_module()函数,该函数调用unregister_blkdev()和blk_cleanup_queue()分别进行驱动程序的注销和请求队列的清除。

2、块设备请求

在内核安排一次数据传输时,首先在一个表中对该请求排队,并以最大化系统性能为原则进行排序。然后,请求队列被传到驱动程序的 request 函数。

块设备的读写操作都是由 request() 函数完成的,对于具体的块设备,request() 函数当然是不同的,所有的读写都存储在 request 结构的链表中。

request()函数从INIT_REQUEST宏命令开始,它对请求队列进行检查,保证请求队列中至少有一个请求在等待处理。如果没有请求,INIT_REQUEST宏命令将使request()函数返回,任务结束。

块设备驱动程序初始化时,由驱动程序的init()完成。为了引导内核时调用init(),需要在blk_dev_init()函数中增加一行代码Mysdd_init()。

块设备驱动程序初始化的工作主要包括以下几点:

(1)检查硬件是否存在。

(2)登记主设备号。

(3)利用 register_blkdev() 函数对设备进行注册。

(4)将块设备驱动程序的数据容量传递给缓冲区。

(5)将 request() 函数的地址传递给内核。

3、块设备驱动程序的实现

(这里只讲理论,具体实现在我的另外一篇博客里)

关于驱动程序更详细的内容请参看《Linux 设备驱动程序》一书。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。