Linux字符设备基础字符设备驱动程序在系统中的位置操作系统内核需要访问两类主要设备,简单的字符设备,如打印机,键盘等;块设备,如软盘、硬盘等。与此对应,有两类设备驱动程序。分别称为字符设备驱动程序和块设备驱动程序。两者的主要差异是:与字符设备有关的系统调用几乎直接和驱动程序的内部功能结合在一起。而读写块设备则主要和快速缓冲存储区打交道。只有需要完成实际的输入/输出时,才用到块设备驱动程序。见下图:Linux设备驱动程序的主要功能有:对设备进行初始化;使设备投入运行和退出服务;从设备接收数据并将它们送到内核;将数据从内核送到设备;检测和处理设备出现的错误。当引导系统时,内核调用每一个驱动程序的初始化函数。它的任务之一是将这一设备驱动程序使用的主设备号通知内核。同时,初始化函数还将驱动程序中的函数地址结构的指针送给内核。内核中有两张表。一张表用于字符设备驱动程序,另一张用于块设备驱动程序。这两张表用来保存指向file_operations结构的指针,设备驱动程序内部的函数地址就保存用户程序系统调用接口文件系统高速缓存字符设备块设备驱动程序硬设备在这一结构中。内核用主设备号作为索引访问file_operations结构,因而能访问驱动程序内的子程序。从开机到驱动程序的载入系统启动过程中可能出现几种不同的方式检测设备硬件。首先机器硬件启动时BIOS会检测一部分必要的设备,如内存、显示器、键盘和硬盘等等。机器会把检测到的信息存放在特定的位置,如CMOS数据区。而另外某些设备会由设备驱动程序进行检测。1开机2引导部分(linux/config.h,arch/i386/boot/bootsect.S)3实模式下的系统初始化(arch/i386/boot/setup.S)4保护模式下的核心初始化5启动核心(init/main.c)init函数中函数调用关系如下:main.cinit()filesystems.csys_setup()genhd.cdevice_setup()mem.cchr_dev_init()至此,驱动程序驻入内存。设备驱动程序基本数据结构:structdevice_struct系统启动过程中要登记的块设备和字符设备管理表的定义在文件fs/devices.c中:structdevice_struct{constchar*name;structfile_operations*fops;};staticstructdevice_structchrdevs[MAX_CHRDEV];staticstructdevice_structblkdevs[MAX_BLKDEV];其实块设备表和字符设备表使用了相同的数据结构。在某些系统中,这些设备表也称作设备开关表,不同的是它们直接定义了一组函数指针进行对设备的管理。而这里系统用文件操作(file_operations)代替了那组开关。文件操作是文件系统与设备驱动程序之间的接口,系统特殊文件在建立的时候并没有把两者对应起来,只是把设备的缺省文件结构和i节点结构赋给设备文件,而真正的对应定义在系统启动之后,当设备被打开时时才进行的。操作blkdev_open和chrdev_open定义在文件devices.c中,它们的基本功能是当设备文件初次打开时,根据该文件的i节点信息找到设备真正的文件操作接口,然后更新原来的设备表项;最后再调用该设备的open操作。/include/linux/major.h中定义了设备表的长度。设备表中不同的表项表示不同种类的设备,也就是说,LINUX系统分别支持各128种不同的块设备和字符设备。Structfile_operations操作系统将一个字符设备当作文件来处理,内核通过file_operations结构来访问driver的功能。这也是linux的OO思想的体现之一。file_operations的定义在文件linux/fs.h中。每个字符设备都有一个file_operatioins结构。这个结构指向一组操作函数(open,read…).每个函数的定义由driver提供。当然,有些标准操作某些设备并不支持,这时,file_operatons结构中对应表项为NULL(.随着linux内核的不断升级,file_operatioins结构也不断变大。最新的版本中,甚至函数原型也发生了一些变化。当然,新版本总会向下兼容的。)下面是2.0.35中的file_operations结构定义:structfile_operations{int(*lseek)(structinode*,structfile*,off_t,int);int(*read)(structinode*,structfile*,char*,int);int(*write)(structinode*,structfile*,constchar*,int);int(*readdir)(structinode*,structfile*,void*,filldir_t);int(*select)(structinode*,structfile*,int,select_table*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structinode*,structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);void(*release)(structinode*,structfile*);int(*fsync)(structinode*,structfile*);int(*fasync)(structinode*,structfile*,int);int(*check_media_change)(kdev_tdev);int(*revalidate)(kdev_tdev);};Structinodefile_operations中的大多数操作都将inode做为第一个参数。Linux的VFS是对物理文件系统,物理设备的一个封装。Inode结构就是VFS与下层模块对话的重要结构。文件系统由子目录和文件构成。每个子目录或文件只能由唯一的inode描述。每个设备也是用inode来描述的。inode是LINUX管理文件系统的最基本单位,也是文件系统连接任何子目录、任何文件,设备的桥梁。structinode{kdev_ti_dev;/*文件所在设备的设备号,第一个IDE硬盘为0x0301*/unsignedlongi_ino;/*外存inode的节点号,(i_dev,i_ino)在VFS中是唯一的*/umode_ti_mode;/*表示文件类型以及存取权限*/nlink_ti_nlink;/*连接到该文件的link数*/uid_ti_uid;/*用户标识号*/gid_ti_gid;/*用户组标识号*/kdev_ti_rdev;/*根设备的设备号*/off_ti_size;/*文件长度*/time_ti_atime;/*文件访问时间*/time_ti_mtime;/*文件修改时间*/time_ti_ctime;/*文件创建时间*/unsignedlongi_blksize;/*以字节为单位的块大小,一般为1024字节*/unsignedlongi_blocks;/*文件块数*/unsignedlongi_version;unsignedlongi_nrpages;/*文件在内存中所占页数*/structsemaphorei_sem;/*信号量*/structinode_operations*i_op;/*指向一组针对该文件的操作函数,见fs.h*/structsuper_block*i_sb;/*指向内存中VFS的超级块*/structwait_queue*i_wait;/*在该文件上的等待队列*/structfile_lock*i_flock;structvm_area_struct*i_mmap;structpage*i_pages;/*由文件占用页面构成的单向链,通过它可访问内存中的文件数据*/structdquot*i_dquot[MAXQUOTAS];structinode*i_next,*i_prev;/*inode资源管理中使用的链表指针*/structinode*i_hash_next,*i_hash_prev;/*inodecache的链表指针*/structinode*i_bound_to,*i_bound_by;structinode*i_mount;/*指向下挂文件系统的inode的根目录*/unsignedlongi_count;/*引用记数,0表示是空闲inode*/unsignedshorti_flags;unsignedshorti_writecount;unsignedchari_lock;/*对inode加锁标志*/unsignedchari_dirt;unsignedchari_pipe;unsignedchari_sock;unsignedchari_seek;unsignedchari_update;unsignedchari_condemned;union{/*各类文件系统inode的特定信息*/............structext2_inode_infoext2_i;............}u;};对于设备管理而言,主要用到的是inode结构的kdevI_rdev字段。Structkdev_t文件系统中,字符设备是通过名字来访问的。通常字符设备都在/dev下。在系统内部,每个字符设备都用设备号来表示。设备号由主,副设备号来表示。主设备号表示与设备对应的设备驱动程序。如:设备/dev/zero和/dev/null都用1作为主设备号,表示他们使用相同的设备驱动程序。副设备号只供驱动程序使用。通常一个驱动程序控制几个设备,它通过副设备号来辨别他们。与设备号相关的数据结构为kdev_tKdev_t结构主要用来表示一个设备的主设备号和副设备号。它其实只是一个短的非负整数:typedefunsignedshortkdev_t;但是,我们可以把它看作:typedefstruct{unsignedshortmajor,minor;}kdev_t;内核提供了一组宏来操作kdev_t结构:MAJOR(kdev_tdev):extractthemajornumberfromakdev_tstructureMINOR(kdev_tdev):extracttheminornumberfromakdev_tstructureMKDEV(intma,intmi):returnakdev_tbuiltfrommajorandminornumbers旧版本的内核dev_t对应kdev_tStructfilelinux/fs.h中定义的file结构是设备驱动程序中用到的仅次于file_operations的数据结构。这里的file结构和用户程序用到的FILE结构不同。它们定义在不同的空间。一个file结构代表一个”打开的文件”,它在文件被打开时被创建,然后被传给要对文件进行操作的函数,知道文件被关闭。一个打开的文件和一个磁盘上的文件不同,磁盘文件用inode表示。下面讨论一些重要的域:mode_tf_mode;文件的读写模式由f_mode的FMODE_READ和FMODE_WRITE控制。在ioctl函数中需要通过此位控制文件的读写权限。当然,read,write函数不需要检查f_mode因为读写权限的检查是由内核在调用他们之前进行的。loff_tf_pos当前的读写位置。Loff_t是一个64位的值。unsignedshortf_flags;也是文件的一些标志。如:O_RDONLY,O_NONBLOCK和O_SYNC.驱动程序必须检查f_flags以进行非阻塞操作。读写操作控制