块设备文件通常指一些需要以块(如512字节)的方式写入的设备,如IDE硬盘、SCSI硬盘、光驱等。它的驱动程序的编写过程与字符型设备驱动程序的编写有很大的区别。
为了把各种块设备的操作请求队列有效地组织起来,内核中设置了一个结构数组blk_dev,该数组中的元素类型是blk_dev_struct结构。这个 结构由三个成分组成,其主体是执行操作的请求队列request_queue,还有一个函数指针queue。当这个指针不为0时,就调用这个函数来找到具 体设备的请求队列。块设备驱动程序描述符是一个包含在<linux/blkdev.h>中的blk_dev_struct类型的数据结构,其 定义如下所示:
struct blk_dev_struct {
request_queue_t request_queue;
queue_proc *queue;
void *date;
};
在这个结构中,请求队列request_queue是主体,包含了初始化之后的I/O 请求队列。
所有块设备的描述符都存放在blk_dev表struct blk_dev_structblk_dev[MAX_BLKDEV]中;每个块设备都对应着数组中的一项,可以使用主设备号进行检索。每当用户进程对一 个块设备发出一个读写请求时,首先调用块设备所公用的函数generic_file_read(),generic_file_write()。如果数据 存在在缓冲区中或缓冲区还可以存放数据,那么就同缓冲区进行数据交换。否则,系统会将相应的请求队列结构添加到其对应项的blk_dev_struct 中,如下图所示:
10.1块设备驱动编写流程
块设备驱动程序的编写流程同字符设备驱动程序的编写流程很类似,也包括了注册和使
用两部分。但与字符驱动设备所不同的是,块设备驱动程序包括一个request请求队列。它是
当内核安排一次数据传输时在列表中的一个请求队列,用以最大化系统性能为原则进行排序。
块设备驱动程序流程图
10.2重要数据结构
Linux系统中有一个名为blkdevs的结构数组,它描述了一系列在系统中登记的块设备。数组blkdevs也使用设备的主设备号作为索引,其元素类 型是device_struct结构。该结构中包括指向已登记的设备驱动程序名的指针和指向block_device_operations结构的指针。 在block_device_operations结构中包含指向有关操作的函数指针。所以,该结构就是连接抽象的块设备操作与具体块设备类型的操作之间 的枢纽。
10.2.1 struct bio
一个bio结构体是在通用块层或更底层对块设备i/o操作的的表示单位。通常1个bio对应1个I/O请求.
- struct bio {
- sector_t bi_sector; /* device address in 512 byte
- sectors */
- structbio *bi_next; /*request queue link */
- structblock_device *bi_bdev;
- unsignedlong bi_flags; /* status, command, etc */
- unsignedlong bi_rw; /* bottom bits READ/WRITE,
- * top bits priority
- */
- unsignedshort bi_vcnt; /*how many bio_vec's */
- unsignedshort bi_idx; /* current index into bvl_vec */
- /* Numberofsegments in this BIO after
- *physical address coalescing is performed.
- */
- unsignedint bi_phys_segments;
- unsignedint bi_size; /*residual I/O count */
- /*
- * Tokeep track of the max segment size, weaccount for the
- *sizes of the first and last mergeablesegments in this bio.
- */
- unsignedint bi_seg_front_size;
- unsignedint bi_seg_back_size;
- unsignedint bi_max_vecs; /* max bvl_vecs we can hold */
- unsignedint bi_comp_cpu; /* completion CPU */
- atomic_t bi_cnt; /*pin count*/
- structbio_vec *bi_io_vec; /* the actual vec list */
- bio_end_io_t *bi_end_io;
- void *bi_private;
- #if defined(CONFIG_BLK_DEV_INTEGRITY)
- structbio_integrity_payload *bi_integrity; /*data integrity */
- #endif
- bio_destructor_t *bi_destructor; /* destructor */
- /*
- * Wecan inline a number of vecs at the end ofthe bio, to avoid
- *double allocations for a small number ofbio_vecs. This member
- * MUSTobviously be kept at the very end ofthe bio.
- */
- structbio_vec bi_inline_vecs[0];
- };
10.2.2 struct gendisk
- struct gendisk { //表示一个独立的磁盘设备或分区
- intmajor; /* major number of driver */
- intfirst_minor; /*starting minor number*/
- intminors; /* maximumnumber ofminors, =1 for
- *disks that can't be partitioned. 每一个分区都有一个minor号*/
- chardisk_name[DISK_NAME_LEN]; /* name ofmajor driver */
- structdisk_part_tbl *part_tbl;
- structhd_struct part0;
- structblock_device_operations *fops;
- structrequest_queue*queue;
- void*private_data;
- int flags;
- struct device*driverfs_dev; // FIXME: remove
- struct kobject*slave_dir;
- structtimer_rand_state *random;
- atomic_tsync_io; /* RAID */
- structwork_struct async_notify;
- #ifdef CONFIG_BLK_DEV_INTEGRITY
- structblk_integrity *integrity;
- #endif
- int node_id;
- };
- struct device_struct {
- const char *name;
- struct file_operations *chops;
- };
- static struct device_structblkdevs[MAX_BLKDEV];
- struct sbull_dev {
- void **data;
- int quantum;// thecurrent quantum size
- int qset;// the current array size
- unsigned long size;
- unsigned int access_key;// used by sbulluid and sbullpriv
- unsigned int usage;// lock the device while using it
- unsigned int new_msg;
- struct sbull_dev *next;// next listitem
- };
- 与字符设备驱动程序一样,块设备驱动程序也包含一个file_operation结构,其结构定义一般如下所示:
- struct file_operation blk_fops = {
- NULL,//seek
- block_read,//内核函数
- block_write,//内核函数
- NULL,//readdir
- NULL,//poll
- sbull_ioctl,// ioctl
- NULL,//mmap
- sbull_open,//open
- NULL,//flush
- sbull_release,//release
- block_fsync,//内核函数
- NULL,//fasync
- sbull_check_media_change,//check media change
- NULL,//revalidate
- NULL,//lock
- };
所有的块驱动程序都调用内核函数block_read()、block_write(),block_fsync()函数,所以在块设备驱动程序入口中不包含这些函数,只需包括ioctl()、open()
和release()函数即可。
(1)设备初始化
块设备的初始化过程要比字符设备复杂,它既需要像字符设备一样在引导内核时完成一定的
工作,还需要在内核编译时增加一些内容。块设备驱动程序初始化时,由驱动程序的init()完成。
块设备驱动程序初始化的工作主要包括:
· 检查硬件是否存在;
· 登记主设备号;
· 将fops结构的指针传递给内核;
· 利用register_blkdev()函数对设备进行注册:
if(register_blkdev(sbull_MAJOR,“sbull”,&sbull_fops)) {
printk(“Registering block device major:%d failed\n”,sbull_MAJOR);
return-EIO;
};
· 将request()函数的地址传递给内核:
blk_dev[sbull_MAJOR].request_fn= DEVICE_REQUEST;
· 将块设备驱动程序的数据容量传递给缓冲区:
#define sbull_HARDS_SIZE 512
#define sbull_BLOCK_SIZE 1024
static int sbull_hard = sbull_HARDS_SIZE;
static int sbull_soft = sbull_BLOCK_SIZE;
hardsect_size[sbull_MAJOR] = &sbull_hard;
blksize_size[sbull_MAJOR] = &sbull_soft;
在块设备驱动程序内核编译时,应把下列宏加到blk.h文件中:
#define MAJOR_NR sbull_MAJOR
#define DEVICE_NAME “sbull”
#define DEVICE_REQUEST sbull_request
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
(2)request操作
Request操作涉及一个重要的数据结构如下。
struct request {
kdev_t rq_dev;
int cmd; // 读或写
int errors;
unsigned long sector;
char *buffer;
struct request *next;
};
对于具体的块设备,函数指针request_fn当然是不同的。块设备的读写操作都是由request()函数完成。所有的读写请求都存储在request结构的链表中。request()函数利用CURRENT宏
检查当前的请求。request()函数从INIT_REQUEST宏命令开始(它也在blk.h中定义),它对请求队列进行检查,保证请求队列中至少有 一个请求在等待处理。如果没有请求(即CURRENT = 0),则INIT_REQUEST宏命令将使request()函数返回,任务结束。
假定队列中至少有一个请求,request()函数现在应处理队列中的第一个请求,当处理完
请求后,request()函数将调用end_request()函数。如果成功地完成了读写操作,那么应该用参数值1 调用end_request()函数;如果读写操作不成功,那么以参数值0 调用end_request()函数。如果队列中还有其他请求,那么将CURRENT 指针设为指向下一个请求。执行end_request()函数后,request()函数回到循环的起点,对下一个请求重复上面的处理过程。
(3)打开操作
(4)释放设备操作
(5)ioctl操作
10.3中断编程
很多Linux 的驱动都是通过中断的方式来进行内核和硬件的交互。
这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。
request_irq()调用的定义:
int request_irq(unsigned int irq,
void (*handler)(int irq, void*dev_id, struct pt_regs *regs),
unsigned long irqflags,const char* devname,oid *dev_id);
irq 是要申请的硬件中断号。在Intel平台,范围是0~15。handler 是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有 SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速 处理程序
被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ 属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用 dev_id找到相应的控制这个中断的设备,或者用irq2dev_map
找到中断对应的设备。void free_irq(unsigned int irq,void *dev_id);
10.4一个简单的块设备驱动
通过写一个建立在内存中的块设备驱动,来学习linux内核和相关设备驱动知识
#defineSIMP_BLKDEV_DISKNAME "simp_blkdev"
#defineSIMP_BLKDEV_BYTES (16*1024*1024)// 使用宏定义了块设备的大小,定为16M
#defineSIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
structblock_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
};// gendisk结构需要设置fops指针,虽然我们用不到,但该设还是要设的
static structgendisk *simp_blkdev_disk;
static structrequest_queue *simp_blkdev_queue;// 指向块设备需要的请求队列
unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
module_init(simp_blkdev_init);//然后申明模块的入口和出口
module_exit(simp_blkdev_exit);
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
static int __initsimp_blkdev_init(void) //在入口处添加这个设备、出口处删除这个设备
{
simp_blkdev_disk = alloc_disk(1); //在添加设备之前我们需要申请这个设备的资源,这用到了alloc_disk()函数
strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME); //设备有关的属性也是需要设置
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops =&simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
simp_blkdev_queue =blk_init_queue(simp_blkdev_do_request, NULL);//初始化请求队列
if (!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_init_queue;
}//在加载模块时用simp_blkdev_do_request()函数的地址作参数
调用blk_init_queue()初始化一个请求队列
//用来从一个请求队列中拿出一条请求(其实严格来说,拿出的可能是请求中的一段)。
随后的处理请求本质上是根据rq_data_dir(req)返回的该请求的方向(读/写),把块设备中的数据装入req->buffer、或是把req->buffer中的数据写入块设备。
- static voidsimp_blkdev_do_request(struct request_queue *q) //请求队列的处理函数。
- {
- struct request *req;
- while ((req = elv_next_request(q)) != NULL) {
- if ((req->sector +req->current_nr_sectors) << 9
- > SIMP_BLKDEV_BYTES) {
- printk(KERN_ERR SIMP_BLKDEV_DISKNAME
- ": bad request: block=%llu,count=%u\n",
- (unsigned long long)req->sector,
- req->current_nr_sectors);
- end_request(req, 0);
- continue;
- }
- switch (rq_data_dir(req)){
- case READ:
- memcpy(req->buffer,
- simp_blkdev_data + (req->sector <<9),
- req->current_nr_sectors << 9);
- end_request(req, 1);
- break;
- case WRITE:
- memcpy(simp_blkdev_data + (req->sector << 9),
- req->buffer, req->current_nr_sectors<< 9);
- end_request(req, 1);
- break;
- default:
- /* No default because rq_data_dir(req) is 1 bit */
- break;
- }
- }
- }
- return 0;
- err_alloc_disk:
- blk_cleanup_queue(simp_blkdev_queue);
- err_init_queue:
- return ret;}
- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- static void __exit simp_blkdev_exit(void)
- {
- del_gendisk(simp_blkdev_disk);
- put_disk(simp_blkdev_disk);
- blk_cleanup_queue(simp_blkdev_queue);
- }