首页 理论教育设备驱动程序加载过程

设备驱动程序加载过程

【摘要】:设备驱动程序的加载涉及几个重要的概念,下面分别进行介绍。在Linux下,操作系统没有对I/O端口屏蔽,任何驱动程序都可以对任意的I/O端口操作,这样很容易引起混乱,因此每个驱动程序都应该避免误用端口。

设备驱动程序的加载涉及几个重要的概念,下面分别进行介绍。

1.内存与I/O端口

内存与I/O端口是Linux设备驱动开发经常用到的两个概念,而往往编写驱动程序大多数情况下其本质都是对内存和I/O端口的操作。

(1)内存。针对实际的物理内存而言,Linux系统中经常会用虚拟内存的技术,虚拟内存技术可被视为系统在硬盘上建立的缓冲区,它并不是真正在实际内存,是计算机使用的临时存储器,用来运行所需内存大于计算机具有的内存的程序,因此必然涉及Linux的各种类型地址,Linux通用有以下几种地址类型:

用户虚拟地址;这类地址是用户空间编程的常规地址,该地址通常是32位或64位的,它依赖于使用的硬件体系结构,并且每个进程有其自己的用户空间。

物理地址;这类地址是用在处理器和系统内存之间的地址,该地址通常是32位或64位的,某些情况下,32位系统可以使用更大的物理地址。

总线地址;这类地址是用在处理外围总线和内存之间,通常它们和被CPU使用的物理地址一样,一些系统结构,可以提供一个I/O内存管理单元,它可以在总线和内存之间重新映射地址。

内核逻辑地址;该类地址是由普通的内核地址空间组成的,这些地址映射一部分或全部内存,并且经常被如同物理地址一样对待。

内核虚拟地址;从内核空间地址映射到物理地址时,内核虚拟地址与内核逻辑地址类似,但不一定与内核逻辑地址线性、一一对应。内核虚拟地址通常存储在指针变量中。

(2)I/O端口。在Linux下,操作系统没有对I/O端口屏蔽,任何驱动程序都可以对任意的I/O端口操作,这样很容易引起混乱,因此每个驱动程序都应该避免误用端口。I/O端口类似于内存位置,可以用访问内存芯片相同的电信号对它进行读/写,但两者实际上并不一样,端口操作是直接对外设进行的,和内存相比更不灵活,而且有不同的端口存在,不能混淆使用。

根据CPU系统结构的不同,CPU对I/O端口的编址方式通常有两种:

第一种是I/O映射方式,如x86处理器为外设专门实现了一个单独的地址空间,称为I/O地址空间,CPU通过专门的I/O指令来访问这一空间的地址单元;

第二种是内存映射方式,RSIC指令系统的CPU(如ARM、Power PC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分,此时CPU访问I/O端口就像访问一个内存单元,不需要单独的I/O指令。这两种方式在硬件实现上的差异对软件来说是完全可见的。I/O端口的主要作用是用来控制硬件,也就是对I/O端口进行具体操作。

2.并发控制

在驱动程序中经常会出现多个进程同时访问相同的资源时可能会出现竞态(race condition),即竞争资源状态,因此必须对共享资料进行并发控制。Linux内核中解决并发控制最常用的方法是自旋锁(spinlocks)和信号量(semaphores)。

(1)自旋锁。自旋锁是一个互斥现象的设备,它只能是两个值:locked(锁定)或unlocked(解锁)。它通常作为一个整型值的单位来实现。在任何时刻,自旋锁只能有一个保持者,也就是说在同一时刻只能有一个进程获得锁。

(2)信号量。信号量是一个结合一对函数的整型值,这对函数通常称为P操作和V操作。需要注意的是信号量只能在同一时刻被一个进程或线程拥有,信号量使用这种模式下通常被称为互斥体。

3.阻塞与非阻塞

在驱动程序的处理过程中我们提到了阻塞的概念,这里进行以下说明。阻塞(blocking)和非阻塞(nonblocking)是设备访问的两种不同模式,前者在I/O操作暂时不可进行时会让进程睡眠,而后者在I/O操作暂时不可进行时并不挂起进程,它或者放弃,或者不停地查询,直到可以进行操作为止。

(1)阻塞与非阻塞操作。阻塞操作是指在执行设备操作时,若不能获得资源则进程挂起,直到满足可操作的条件再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列中移走,直到等待条件被满足。

非阻塞操作是在不能进行设备操作时并不挂起,它会立即返回,使得应用程序可以快速查询状态。

(2)异步通知。异步通知是指一旦设备准备就绪,则该设备会主动通知应用程序,这样应用程序就不需要不断地查询设备状态,通常把异步通知称为信号驱动的异步I/O(SIGIO),这有点类似于硬件上的中断。(www.chuimin.cn)

4.设备号

Linux系统通过设备号来区分不同的设备。设备号主要分为主设备号和次设备号,内核通过主设备号将设备与相应的驱动程序对应起来。主设备号的取值范围是0~255。当一个驱动程序要控制若干个设备时,就要用次设备号来区分它们。

对于标准设备,可以在Linux内核资源文档中找到标准设备对应的主设备号,嵌入式Linux内核资源文档在系统的/∗usr_linux_sr/linux/Documentation/devices.txt文件中(这里,./iux s5C指用户安装Linux系统文件的目录,根据用户安装的目录位置不同,其目录名有所不同,如/pxa270_linux linux Documentation/devices.txt)。

对于自定义的产品设备,则需要开发者自己定义设备的主设备号。这时要注意自定义的主设备号不能与已经存在的主设备号冲突。通过查看位于文件系统/dev目录下的设备文件,可以查到每个设备的名称.主从设备号及文件属性等信息。

5.设备文件节点

用户进程与硬件的交流是通过设备文件进行的,硬件在系统中会被抽象成为一个设备文件(节点),访问设备文件就相当于访问其所对应的硬件。每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备。

设备节点的主要作用就是设备驱动程序向内核注册设备号和设备名,如果设备注册成功,则设备名就会写入到/proc/devices文件中。

对于设备节点(设备文件),可以像操作磁盘上的普通文件一个,进行删除(rm)、移动(mv)和复制(cp)等操作。

6.加载设备驱动程序

创建了设备节点,并不代表设备驱动和设备硬件已经就绪,只是为将设备驱动程序加载到内核时建立了一个入口点,从而在需要使用设备时就可以将设备驱动程序加载到内核中。

前面提到应用程序都有一个main函数作为程序的入口点,而驱动程序却没有main函数,要通过命令insmod加载设备驱动程序。

(1)加载设备驱动程序的一般格式为:

insmod <设备驱动程序.o>

(2)要察看当前加载了哪些设备驱动程序则使用下列命令:

lsmod -l

(3)若要卸载驱动程序,则使用命令:

rmmod <设备驱动程序.o>

在用户空间通过命令insmod向内核空间加载设备驱动程序模块,此时程序的入口点是初始化函数init_module(),在该函数中完成设备的注册。完成设备注册加载后,此时,系统将设备驱动加载到内核中,在用户空间的用户应用程序就可以通过调用驱动程序的功能接口函数对该设备进行操作。设备用完后,可以在用户空间通过移除已加载的驱动设备命令rmmod将设备御教,此时的入口点是clenup_module函数,在该函数中完成设备的卸载。设备驱动程序的动态加载与卸载的工作过程如图9.2所示。

图9.2 设备驱动程序的动态加载与卸载的工作过程