第5章 字符设备驱动
现在,你已经准备就绪了,可以尝试去写一个简单、但实用的设备驱动了。在这一章,我们将深入字符设备驱动的内幕:顺序存取设备数据的内核代码。字符设备驱动能从如下几类设备获取原始的数据:如打印机、鼠标、看门狗、键盘、内存、实时时钟等,但它不适合用于以块方式存储的、随机访问的设备,如硬盘、软盘和光盘。 |
字符设备驱动基础
让我们以自顶向下的方式开始字符设备驱动学习之旅。为了访问一个字符设备,系统用户需要调用相应的应用程序。此应用程序负责和设备交互,为了实现此目的,需要得到相应驱动的标识符。驱动通过/dev目录给用户提供接口:
bash> ls -l /dev
total 0
crw------- 1 root root 5, 1 Jul 16 10:02 console
...
lrwxrwxrwx 1 root root 3 Oct 6 10:02 cdrom -> hdc
...
brw-rw---- 1 root disk 3, 0 Oct 6 2007 hda
brw-rw---- 1 root disk 3, 1 Oct 6 2007 hda1
...
crw------- 1 root tty 4, 1 Oct 6 10:20 tty1
crw------- 1 root tty 4, 2 Oct 6 10:02 tty2
ls命令输出结果的每一行的第一个字符表示驱动的类型:c表示字符设备驱动,b代表块设备驱动,l表示符号链接。第五列的数字是主设备号,第六列是次设备号。主设备号通常标识设备对应的驱动程序,次设备号用于确定驱动程序所服务的设备。例如,IDE块存储设备驱动/dev/had的主设备号为3,负责处理系统的硬盘;当进一步指明其次设备号为1时(/dev/hda1),它指向第一个硬盘分区。字符设备驱动与块设备驱动占用不同空间,因此可以将同一个主设备号分配给字符设备和块设备驱动。
让我们进一步深入字符设备驱动。从程序结构的角度看,字符设备驱动包括如下内容:
<!--[if !supportLists]-->· <!--[endif]-->初始化例程init(),负责初始化设备并且将驱动和内核的其它部分通过注册函数实现无缝连接。
<!--[if !supportLists]-->· <!--[endif]-->入口函数(或方法)集,如open(),read(),ioctl(),llseek(),和write(),这些函数直接对应相应的的I/O系统调用,由用户应用程序通过对应的/dev节点调用。
<!--[if !supportLists]-->· <!--[endif]-->中断例程,底半部例程,定时器处理例程,内核辅助线程,以及其他的组成部分。它们大部分对用户应用程序是透明的。
从数据流的角度看,字符设备驱动包括如下关键的数据结构:
<!--[if !supportLists]-->1. <!--[endif]-->特定设备相关(per-device)的数据结构。此结构保存着驱动频繁使用的信息。
<!--[if !supportLists]-->2. <!--[endif]-->cdev结构,针对字符设备驱动的内核抽象。这个结构通常作为前面讨论过的特定设备相关(per-device)结构的成员。
<!--[if !supportLists]-->3. <!--[endif]-->file_operations结构,包括所有设备驱动入口函数的地址。
<!--[if !supportLists]-->4. <!--[endif]-->file结构,包括关联的/dev节点的信息。
让我们实现一个字符设备驱动以访问系统CMOS。在PC兼容的硬件上(见图5.1)BIOS使用CMOS存储系统信息,如启动选项,引导顺序,系统数据等,我们可以通过BIOS设置菜单对其进行配置。我们的CMOS设备驱动使你像访问普通文件一样访问两个PC CMOS块bank。应用程序可以在/dev/cmos/0和/dev/cmos/1上使用I/O系统调用从两个块bank存取数据。因为BIOS分配给CMOS域的存取粒度是比特级的,所以驱动程序能够进行比特级的访问。因此,read()可以获取指定数目的比特,并根据读取的比特数移动内部文件指针。
图 5.1. PC兼容系统的CMOS
通过两个I/O地址访问CMOS,一个索引寄存器和一个数据寄存器,如表5.1所示。你必须在索引寄存器中指定准备访问的CMOS存储器的偏移,然后通过数据寄存器来交换数据。
因为每个驱动方法都有一个对应的由应用程序使用的系统调用,我们将看看系统调用和相应的驱动方法。
init()函数是注册机制的基础。它负责完成如下工作:
- 申请分配主设备号。
- 为特定设备相关(per-device)的数据结构分配内存。
- 将入口函数(open(),read()等)和字符驱动的cdev抽象相关联。
- 将主设备号和驱动的cdev相关联。
- 在/dev 和 /sys下创建节点。如在第4章“打下基础”中所讨论的,/dev的管理经历了2.2版本内核中的静态设备节点,到2.4中的动态指定name, 再到2.6中的用户空间的守护进程(udevd)的历程。
- 初始化硬件。在本例的简单CMOS驱动中不涉及此部分。
清单5.1实现了CMOS驱动的init()函数。
清单5.1. CMOS 驱动初始化
Code View: #include <linux/fs.h>
/* Per-device (per-bank) structure */ struct cmos_dev { unsigned short current_pointer; /* Current pointer within the bank */ unsigned int size; /* Size of the bank */ int bank_number; /* CMOS bank number */ struct cdev cdev; /* The cdev structure */ char name[10]; /* Name of I/O region */ /* ... */ /* Mutexes, spinlocks, wait queues, .. */ } *cmos_devp;
/* File operations structure. Defined in linux/fs.h */ static struct file_operations cmos_fops = { .owner = THIS_MODULE, /* Owner */ .open = cmos_open, /* Open method */ .release = cmos_release, /* Release method */ .read = cmos_read, /* Read method */ .write = cmos_write, /* Write method */ .llseek = cmos_llseek, /* Seek method */ .ioctl = cmos_ioctl, /* Ioctl method */ };
static dev_t cmos_dev_number; /* Allotted device number */ struct class *cmos_class; /* Tie with the device model */
#define NUM_CMOS_BANKS 2 #define CMOS_BANK_SIZE (0xFF*8) #define DEVICE_NAME "cmos" #define CMOS_BANK0_INDEX_PORT 0x70 #define CMOS_BANK0_DATA_PORT 0x71 #define CMOS_BANK1_INDEX_PORT 0x72 #define CMOS_BANK1_DATA_PORT 0x73
unsigned char addrports[NUM_CMOS_BANKS] = {CMOS_BANK0_INDEX_PORT, CMOS_BANK1_INDEX_PORT,};
unsigned char dataports[NUM_CMOS_BANKS] = {CMOS_BANK0_DATA_PORT, CMOS_BANK1_DATA_PORT,};
/* * Driver Initialization */ int __init cmos_init(void) { int i;
/* Request dynamic allocation of a device major number */ if (alloc_chrdev_region(&cmos_dev_number, 0, NUM_CMOS_BANKS, DEVICE_NAME) < 0) { printk(KERN_DEBUG "Can't register device/n"); return -1; }
/* Populate sysfs entries */ cmos_class = class_create(THIS_MODULE, DEVICE_NAME);
for (i=0; i<NUM_CMOS_BANKS; i++) { /* Allocate memory for the per-device structure */ cmos_devp = kmalloc(sizeof(struct cmos_dev), GFP_KERNEL); if (!cmos_devp) { printk("Bad Kmalloc/n"); return 1; }
/* Request I/O region */ sprintf(cmos_devp->name, "cmos%d", i); if (!(request_region(addrports[i], 2, cmos_devp->name)) { printk("cmos: I/O port 0x%x is not free./n", addrports[i]); return –EIO; } /* Fill in the bank number to correlate this device with the corresponding CMOS bank */ cmos_devp->bank_number = i;
/* Connect the file operations with the cdev */ cdev_init(&cmos_devp->cdev, &cmos_fops); cmos_devp->cdev.owner = THIS_MODULE;
/* Connect the major/minor number to the cdev */ if (cdev_add(&cmos_devp->cdev, (dev_number + i), 1)) { printk("Bad cdev/n"); return 1; }
/* Send uevents to udev, so it'll create /dev nodes */ class_device_create(cmos_class, NULL, (dev_number + i), NULL, "cmos%d", i); }
printk("CMOS Driver Initialized./n"); return 0; } |