二. Linux下的设备驱动
Linux 将设备分为最基本的两大类,字符设备和块设备。字符设备是以单个字节为单位进行顺序读写操作,通常不使用缓冲技术,如鼠标等,驱动程序实现比较简单;而块设备则是以固定大小的数据块进行存储和读写的,如硬盘,软盘等。为提高效率,系统对于块设备的读写提供了缓存机制,由于涉及缓冲区管理,调度,同步等问题,实现起来比字符设备复杂的多。
Linux的设备管理是和文件系统解密结合的,各种设备名称都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开,关闭,读写这些设备文件,完成对设备的操作,就象操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘主设备号是3。在Linux的/dev/目录下使用ls -l命令可察看个设备文件的设备号。例如,/dev/hda为块设备,主设备号3,次设备号0,是系统的第一块硬盘。/dev/hd1主设备号3,次设备号1,为系统的第二块硬盘。我们将要介绍的显示设备也是一个设备文件/dev/fb,主设备号29。在编写设备驱动程序的时候,也要指明所操作设备的主设备号和次设备号。
Linux的特点之一,是为所有的文件,包括设备文件,提供了统一的操作函数接口,定义如下:
struct file_operations {
struct module *owner;
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 (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
结构体中的成员为一系列的接口函数,如用于读/写的read/ write函数,用于控制的ioctl等。打开一个文件就是调用这个文件file_operations中的open操作。不同类型的文件有不同的 file_operations成员函数。如普通的磁盘数据文件,接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的 I/O函数进行具体设备的操作。这样,应用程序根本不用考虑操作的是设备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口。所以 file_operations是文件层次的I/O接口。
但是,由于外设的种类繁多,操作方式也各不相同。如声音设备驱动要使用 DMA通道,显示设备驱动要提供对显存的操作,硬盘驱动要处理复杂的缓冲区结构,网络设备驱动和socket联系紧密。如果 file_operations中的函数都让驱动程序的开发人员来写,则就要处理大量的细节,几乎是不可能的。为了解决设备多样性的问题,Linux采用了特殊情况特殊处理的办法,为不同设备定义好了文件层次file_operations结构中的接口函数,其中处理了大多数设备相关的操作,如各种缓冲区的申请和释放等等,而具体操作底层硬件的一小部分则留给开发人员。所以Linux另外提供一个文件层到底层驱动程序的接口,通常为一个结构体,其中包含成员变量和函数指针。不同的设备驱动有不同的结构体。这样,一方面保证了文件层I/O接口file_operations的一致性,另一方面驱动程序的开发人员也不用了解太多细节,只专著于硬件相关的I/O操作就可以了。例如,一个有代表性的特殊设备是声音设备,其文件层的file_operations定义如下:
struct file_operations oss_sound_fops = {
owner: THIS_MODULE,
llseek: sound_lseek,
read: sound_read,
write: sound_write,
poll: sound_poll,
ioctl: sound_ioctl,
mmap: sound_mmap,
open: sound_open,
release: sound_release,
};
其中的sound_read,sound_write等函数Linux都已提供,处理了与声音设备相关的许多细节,如DMA的申请,释放和操作等。而文件层到驱动程序的接口为audio_driver结构,其中包含底层操作函数。文件层的sound_read,sound_write会在需要时调用 audio_driver中的函数。开发人员只要编写audio_driver中的函数就可以了,最大程度地减小了工作量。下面我们将看到,Linux为显示设备提供的帧缓冲驱动也是这种“文件层-驱动层”的接口方式。
二. Linux下的设备驱动
Linux 将设备分为最基本的两大类,字符设备和块设备。字符设备是以单个字节为单位进行顺序读写操作,通常不使用缓冲技术,如鼠标等,驱动程序实现比较简单;而块设备则是以固定大小的数据块进行存储和读写的,如硬盘,软盘等。为提高效率,系统对于块设备的读写提供了缓存机制,由于涉及缓冲区管理,调度,同步等问题,实现起来比字符设备复杂的多。
Linux的设备管理是和文件系统解密结合的,各种设备名称都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开,关闭,读写这些设备文件,完成对设备的操作,就象操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘主设备号是3。在Linux的/dev/目录下使用ls -l命令可察看个设备文件的设备号。例如,/dev/hda为块设备,主设备号3,次设备号0,是系统的第一块硬盘。/dev/hd1主设备号3,次设备号1,为系统的第二块硬盘。我们将要介绍的显示设备也是一个设备文件/dev/fb,主设备号29。在编写设备驱动程序的时候,也要指明所操作设备的主设备号和次设备号。
Linux的特点之一,是为所有的文件,包括设备文件,提供了统一的操作函数接口,定义如下:
struct file_operations {
struct module *owner;
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 (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
结构体中的成员为一系列的接口函数,如用于读/写的read/ write函数,用于控制的ioctl等。打开一个文件就是调用这个文件file_operations中的open操作。不同类型的文件有不同的 file_operations成员函数。如普通的磁盘数据文件,接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的 I/O函数进行具体设备的操作。这样,应用程序根本不用考虑操作的是设备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口。所以 file_operations是文件层次的I/O接口。
但是,由于外设的种类繁多,操作方式也各不相同。如声音设备驱动要使用 DMA通道,显示设备驱动要提供对显存的操作,硬盘驱动要处理复杂的缓冲区结构,网络设备驱动和socket联系紧密。如果 file_operations中的函数都让驱动程序的开发人员来写,则就要处理大量的细节,几乎是不可能的。为了解决设备多样性的问题,Linux采用了特殊情况特殊处理的办法,为不同设备定义好了文件层次file_operations结构中的接口函数,其中处理了大多数设备相关的操作,如各种缓冲区的申请和释放等等,而具体操作底层硬件的一小部分则留给开发人员。所以Linux另外提供一个文件层到底层驱动程序的接口,通常为一个结构体,其中包含成员变量和函数指针。不同的设备驱动有不同的结构体。这样,一方面保证了文件层I/O接口file_operations的一致性,另一方面驱动程序的开发人员也不用了解太多细节,只专著于硬件相关的I/O操作就可以了。例如,一个有代表性的特殊设备是声音设备,其文件层的file_operations定义如下:
struct file_operations oss_sound_fops = {
owner: THIS_MODULE,
llseek: sound_lseek,
read: sound_read,
write: sound_write,
poll: sound_poll,
ioctl: sound_ioctl,
mmap: sound_mmap,
open: sound_open,
release: sound_release,
};
其中的sound_read,sound_write等函数Linux都已提供,处理了与声音设备相关的许多细节,如DMA的申请,释放和操作等。而文件层到驱动程序的接口为audio_driver结构,其中包含底层操作函数。文件层的sound_read,sound_write会在需要时调用 audio_driver中的函数。开发人员只要编写audio_driver中的函数就可以了,最大程度地减小了工作量。下面我们将看到,Linux为显示设备提供的帧缓冲驱动也是这种“文件层-驱动层”的接口方式。
举报