20150310 块设备驱动程序
2015-03-10 李海沿
接下来我们来实现块设备驱动程序。
一、块设备结构体
1. file_operations 结构体
和字符设备驱动中file_operations 结构体类似,块设备驱动中也有一个
block_device_operations 结构体,它的声明位于/include/linux 录下的fs.h 文件中,它是对
块操作的集合。
struct block_device_operations{ int(*open)(struct inode *, struct file*); //打开设备 int(*release)(struct inode *, struct file*); //关闭设备 //实现ioctl 系统调用 int(*ioctl)(struct inode *, struct file *, unsigned, unsigned long); long(*unlocked_ioctl)(struct file *, unsigned, unsigned long); long(*compat_ioctl)(struct file *, unsigned, unsigned long); int(*direct_access)(struct block_device *, sector_t, unsigned long*); //调用该函数用以检查用户是否更换了驱动器的介质 int(*media_changed)(struct gendisk*); int(*revalidate_disk)(struct gendisk*); //当介质被更换时,调用该函数做出响应 int(*getgeo)(struct block_device *, struct hd_geometry*);//获取驱动器信息 struct module *owner; //指向拥有这个结构体模块的指针,通常被初始化位THIS_MODULE }; |
与字符驱动不同的是在这个结构体中缺少了read()和write()函数,那是因为块设备的I/O 子系统中,这些操作都是有request 函数进行处理。
request 函数的原型如下:
void request(request_queue_t *queue);
2.gendisk结构体
gendisk 结构体的定义位于/include/linux 目录下的genhd.h 文件中,如下所示。
struct gendisk { /* *这三个成员的定义依次是:主设备号、第一个次设备号,次设备号。一个驱动中至有一个次设备号, *如果驱动器是一个可被分区,那么每一个分区都将分配一个次设号。 */ int major; int first_minor; int minors; //这个数组用以存储驱动设备的名字 char disk_name[DISK_NAME_LEN]; char *(*devnode)(struct gendisk *gd, umode_t *mode); unsigned int events; unsigned int async_events; struct disk_part_tbl __rcu *part_tbl; struct hd_struct part0; //这个结构体用以设置驱动中的各种设备操作 const struct block_device_operations *fops; //Linux 内核使用这个结构体为设备管理I/O 请求,具体详解见request_queue 结构。 struct request_queue *queue; void *private_data; int flags; struct device *driverfs_dev; struct kobject *slave_dir; struct timer_rand_state *random; atomic_t sync_io; struct disk_events *ev; #ifdef CONFIG_BLK_DEV_INTEGRITY struct blk_integrity *integrity; #endif int node_id; }; |
gendisk 结构体是是动态分配,但是驱动程序自己不能动态分配该结构,而是通过调用
alloc_disk()函数进行动态分配。
struct gendisk *alloc_disk(int minors);
其中minors 是该磁盘使用的次设备号。
但是分配了gendisk 结构并不意味着该磁盘就对系统可用,使用之前得初始化结构体并
且调用add_disk()函数。
//初始化结构体
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
//添加分区
void add_disk(struct gendisk *gd)
如果不再需要这个磁盘,则对其进行卸载。
//删除分区
Void del_gendisk(struct gendisk *gd)
void blk_cleanup_queue(struct request_queue *q)
3.bio结构体
bio 结构体的定义位于/include/linux 目录下的linux_blk_types.h 文件中。
struct bio { //需要传输的第一个(512byte)扇区 sector_t bi_sector; struct bio *bi_next; struct block_device *bi_bdev; unsigned long bi_flags; unsigned long bi_rw; unsigned short bi_vcnt; unsigned short bi_idx; //BIO 中所包含的物理段数目 unsigned int bi_phys_segments; //所传输的数据大小(以byte 为单位) unsigned int bi_size; unsigned int bi_seg_front_size; unsigned int bi_seg_back_size; unsigned int bi_max_vecs; atomic_t bi_cnt; struct bio_vec *bi_io_vec; bio_end_io_t *bi_end_io; void *bi_private; #ifdef CONFIG_BLK_CGROUP struct io_context *bi_ioc; struct cgroup_subsys_state *bi_css; #endif #if defined(CONFIG_BLK_DEV_INTEGRITY) struct bio_integrity_payload *bi_integrity; #endif bio_destructor_t *bi_destructor; struct bio_vec bi_inline_vecs[0]; }; |
bio 结构体包含了驱动程序执行请求的所有信息。既描述了磁盘的位置,又描述了内存
的位置,是上层内核与下层驱动的连接纽带。
struct bio_vec *bi_io_vec;
而bio_vec 结构体的声明为:结构bio_vec 代表了内存中的一个数据段,数据段用页、偏移和长度描述
struct bio_vec { struct page *bv_page; /*数据段所在的页*/ unsigned short bv_len; /*数据段的长度*/ unsigned short bv_offset; /*数据段页内偏移*/ }; |
4. requeset 结构体
request 结构体代表了挂起的I/O 请求,每个请求用一个结构request 实例描述,存放
在请求队列链表中,由电梯算法进行排序,每个请求包含一个或多个结构bio 实例。requeest
结构体声明位于/include/linux 目录下的blkdev.h 文件中。
struct request { struct list_head queuelist; struct call_single_data csd; struct request_queue *q; unsigned int cmd_flags; enum rq_cmd_type_bits cmd_type; unsigned long atomic_flags; int cpu; unsigned int __data_len; sector_t __sector; struct bio *bio; struct bio *biotail; struct hlist_node hash; union { struct rb_node rb_node; void *completion_data; }; union { struct { struct io_cq *icq; void *priv[2]; } elv; struct { unsigned int seq; struct list_head list; rq_end_io_fn *saved_end_io; } flush; }; struct gendisk *rq_disk; struct hd_struct *part; unsigned long start_time; #ifdef CONFIG_BLK_CGROUP struct request_list *rl; unsigned long long start_time_ns; unsigned long long io_start_time_ns; #endif unsigned short nr_phys_segments; #if defined(CONFIG_BLK_DEV_INTEGRITY) unsigned short nr_integrity_segments; #endif unsigned short ioprio; int ref_count; void *special; char *buffer; int tag; int errors; unsigned char __cmd[BLK_MAX_CDB]; unsigned char *cmd; unsigned short cmd_len; unsigned int extra_len; unsigned int sense_len; unsigned int resid_len; void *sense; unsigned long deadline; struct list_head timeout_list; unsigned int timeout; int retries; rq_end_io_fn *end_io; void *end_io_data; struct request *next_rq; }; |
5. request_queue 结构体
每个块设备都有一个请求队列,每个请求队列单独执行I/O 调度,请求队列是由请求结
构实例链接成的双向链表,链表以及整个队列的信息用request_queue 结构体描述,称为请
求队列对象结构或请求队列结构。request_queue 结构体声明位于/include/linux 目录下的
blkdev.h 文件中。
struct request_queue{ /* * Together with queue_head for cacheline sharing */ struct list_head queue_head; struct request *last_merge; struct elevator_queue *elevator; /* * the queue request freelist, one for reads and one for writes */ struct request_list rq;
request_fn_proc *request_fn; make_request_fn *make_request_fn; prep_rq_fn *prep_rq_fn; unplug_fn *unplug_fn; prepare_discard_fn *prepare_discard_fn; merge_bvec_fn *merge_bvec_fn; prepare_flush_fn *prepare_flush_fn; softirq_done_fn *softirq_done_fn; rq_timed_out_fn *rq_timed_out_fn; dma_drain_needed_fn *dma_drain_needed; lld_busy_fn *lld_busy_fn;
/* * Dispatch queue sorting */ sector_t end_sector; struct request *boundary_rq;
/* * Auto-unplugging state */ struct timer_list unplug_timer; int unplug_thresh; /* After this many requests */ unsigned long unplug_delay; /* After this many jiffies */ struct work_struct unplug_work;
struct backing_dev_info backing_dev_info;
/* * The queue owner gets to use this for whatever they like. * ll_rw_blk doesn't touch it. */ void *queuedata;
/* * queue needs bounce pages for pages above this limit */ gfp_t bounce_gfp;
/* * various queue flags, see QUEUE_* below */ unsigned long queue_flags;
/* * protects queue structures from reentrancy. ->__queue_lock should * _never_ be used directly, it is queue private. always use * ->queue_lock. */ spinlock_t __queue_lock; spinlock_t *queue_lock;
/* * queue kobject */ struct kobject kobj;
/* * queue settings */ unsigned long nr_requests; /* Max # of requests */ unsigned int nr_congestion_on; unsigned int nr_congestion_off; unsigned int nr_batching;
void *dma_drain_buffer; unsigned int dma_drain_size; unsigned int dma_pad_mask; unsigned int dma_alignment;
struct blk_queue_tag *queue_tags; struct list_head tag_busy_list;
unsigned int nr_sorted; unsigned int in_flight[2];
unsigned int rq_timeout; struct timer_list timeout; struct list_head timeout_list;
struct queue_limits limits;
/* * sg stuff */ unsigned int sg_timeout; unsigned int sg_reserved_size; int node; #ifdef CONFIG_BLK_DEV_IO_TRACE struct blk_trace *blk_trace; #endif /* * reserved for flush operations */ unsigned int ordered, next_ordered, ordseq; int orderr, ordcolor; struct request pre_flush_rq, bar_rq, post_flush_rq; struct request *orig_bar_rq;
struct mutex sysfs_lock;
#if defined(CONFIG_BLK_DEV_BSG) struct bsg_class_device bsg_dev; #endif }; |
二、程序分析
1.定义各种结构体指针
2.实现fileoperation结构体
如图所示,block_getgeo函数中的功能是伪装磁盘的磁头信息,磁头个数,柱面,容量等信息,是为了支持fdisk进行分区。
3.在初始化函数中
如图所示:
首先分配一个动态分配一个gendisk结构体,然后初始化各种设置,分配内存,最后试用add_disk注册。
4.实现读写函数
由于块设备没字符设备中所谓的read和write函数,所以我们的读写函数都是在do_block_request函数中实现的。
5.在exit函数中
最后就是在exit函数中释放我们前面的申请的内存等资源
6.编译命令
Insmod blk.ko
ll /dev/blkdev
mkfs.ext3 /dev/blkdev
fdisk /dev/blkdev |
附驱动源程序:
1 /* blkdev.c */ 2 #include <linux/genhd.h> 3 #include <linux/init.h> 4 #include <linux/module.h> 5 #include <linux/blkdev.h> 6 #include <linux/types.h> 7 #include <linux/fs.h> 8 #include <linux/hdreg.h> //geo 9 #include <linux/vmalloc.h> 10 11 #define BLKDEV_SIZE 2*1024*1024 12 13 /* 定义一个指向请求队列的结构体指针 */ 14 static struct request_queue *blkdev_queue; 15 /* 定义一个指向独立分区(磁盘)的结构体指针 */ 16 static struct gendisk *blkdev_disk; 17 /* 定义一个自旋锁 */ 18 static DEFINE_SPINLOCK(blkdev_lock); 19 /* 主设备号 */ 20 static int blkdev_major; 21 static unsigned char blkdev_data[BLKDEV_SIZE]; 22 static unsigned long lock_buf; //保存分配内存的指针 23 24 //由于没有老式的磁头等信息,但是为了支持fdisk分区工具,我们必须伪装有 25 static int block_getgeo(struct block_device *bdev, struct hd_geometry *geo){ 26 //容量: heads * cvlinders * 512 27 geo->heads = (unsigned char)2; //磁头个数 ,2面 28 geo->cylinders = (unsigned short)32; //柱面, 32环 29 geo->sectors = (unsigned char)BLKDEV_SIZE / 2 / 32 / 512; //容量 30 return 0; 31 } 32 /* 33 * 块设备操作的集合 34 */ 35 struct block_device_operations blkdev_fops = { 36 .owner = THIS_MODULE, 37 .getgeo = block_getgeo, 38 }; 39 40 static void do_block_request(struct request_queue *q){ 41 struct request *req; 42 static int r_cnt=0; 43 static int w_cnt=0; 44 45 while((req = blk_fetch_request(q)) != NULL){ 46 /*数据传输*/ 47 unsigned long offset = blk_rq_pos(req) * 512; //源 48 unsigned long len = blk_rq_sectors(req) * 512;//长度 49 /* 50 * 结束一个队列请求,第二个参数表示请求处理结果 51 * 成功的话设定为1,失败的话设定为0 或者错误号 52 */ 53 /* 54 * rq_data_dir()函数返回该请求的方向:读还是写 55 */ 56 if(rq_data_dir(req) == READ){ 57 printk(KERN_ALERT "read %dth offset=%ld len=%ld\n",r_cnt,offset,len); 58 /* 从块设备读取数据 */ 59 memcpy(req->buffer, blkdev_data+offset,len); 60 }else{ 61 printk(KERN_ALERT "write %dth offset=%ld len=%ld\n",w_cnt,offset,len); 62 /* 把缓冲区的数据写入块设备 */ 63 memcpy(blkdev_data + offset, req->buffer,len); 64 } 65 __blk_end_request_all(req, 1); 66 } 67 } 68 69 static int lhy_blkdev_init(void){ 70 71 printk("<0>lhy_blkdev_init!\n"); 72 /* 注册分区 */ 73 /*1. 分配一个gendisk结构体*/ 74 blkdev_disk = alloc_disk(16); //此设备号个数,分区个数+1 75 /*2.设置 */ 76 /*2.1 分配/设置队列: 提供读写能力 */ 77 blkdev_queue = blk_init_queue(do_block_request, &blkdev_lock); 78 blkdev_disk->queue = blkdev_queue; 79 /*2.2 其他设置 */ 80 blkdev_major = register_blkdev(0,"blkdev"); 81 blkdev_disk->major = blkdev_major; 82 blkdev_disk->first_minor = 0; 83 sprintf(blkdev_disk->disk_name, "blkdev"); 84 blkdev_disk->fops = &blkdev_fops; 85 set_capacity(blkdev_disk, BLKDEV_SIZE >> 9); 86 /*3. 硬件相关,分配内存*/ 87 lock_buf = vmalloc(BLKDEV_SIZE); 88 /*4.注册 */ 89 add_disk(blkdev_disk); 90 91 return 0; 92 } 93 94 static void lhy_blkdev_exit(void){ 95 printk("<0>lhy_blkdev_init!\n"); 96 unregister_blkdev(blkdev_major,"blkdev"); 97 /* 释放删除分区 add_disk() */ 98 del_gendisk(blkdev_disk); 99 /* add_disk() */ 100 printk("<0>putdisk\n"); 101 put_disk(blkdev_disk); 102 /* blk_init_queue() */ 103 blk_cleanup_queue(blkdev_queue); 104 printk("<0>kfree\n"); 105 if(blkdev_data) 106 vfree(blkdev_data); 107 } 108 109 module_init(lhy_blkdev_init); 110 module_exit(lhy_blkdev_exit); 111 MODULE_LICENSE("GPL");