linux设备驱动之块设备驱动程序的设计
文章目录
块设备
块设备将数据存储在固定的块中,每个块的大小通常在512字节到32768字节之间。磁盘、SD卡都是常见的块设备。
块设备与字符设备的区别:
- 在与读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个 sector ,而字符设备的基本单元为字节。
- 块设备能够随机访问,而字符设备则只能顺序访问。
块设备体系结构
体系结构层次
-
虚拟文件系统VFS
VFS是对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。
-
Disk Cache
当用户发起文件访问请求的时候,首先会到Disk Cache 中寻找文件是否被缓存了,如果在cache中,则直接从 cache 中读取。如果数据不在缓存中,就必须到具体的文件系统中读取数据了。 -
Mapping layer
(1)首先确定文件系统的 block size ,然后计算所请求的数据包含多少个block
(2)调用具体文件系统的函数来访问文件的 inode,确定所请求的数据在磁盘上的逻辑块地址。 -
Generic Block Layer
Linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据空间。
上层的读写请求在通用块层(Generic Block Layer)被构造成一个或多个bio结构。 -
I/O Secheduler Layer
I/O调度层负责将I/O操作排序,采用某种算法(如:电梯调度算法)来高效地处理操作。
电梯调度算法:
-
Block Device Driver
块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输
块设备驱动程序设计
块设备描述
Linux内核使用 struct gendisk (定义于 <linux/genhd.h>)来描述块设备。
struct gendisk { int major; /* 主设备号 */ int first_minor; /* 次设备号*/ int minors; /* maximum number of minors, =1 for * disks that can't be partitioned. */ char disk_name[DISK_NAME_LEN]; /* name of major driver */ char *(*devnode)(struct gendisk *gd, mode_t *mode); unsigned int events; /* supported events */ unsigned int async_events; /* async events, subset of all */ /* Array of pointers to partitions indexed by partno. * Protected with matching bdev lock but stat and other * non-critical accesses use RCU. Always access through * helpers. */ struct disk_part_tbl __rcu *part_tbl; struct hd_struct part0; const struct block_device_operations *fops; struct request_queue *queue; //请求队列 void *private_data; int flags; struct device *driverfs_dev; // FIXME: remove struct kobject *slave_dir; struct timer_rand_state *random; atomic_t sync_io; /* RAID */ struct disk_events *ev; #ifdef CONFIG_BLK_DEV_INTEGRITY struct blk_integrity *integrity; #endif int node_id; };
设备注册
Linux 内核使用 add_disk 函数向内核注册块设备驱动。
void add_disk(struct gendisk *gd)
设备操作
字符 设备通过file_operations 结构来定义使它所支持的操作,块设备使用一个类似的结构:struct block_device_operations.(<linux/blkdev.h>)
struct block_device_operations { int (*open) (struct block_device *, fmode_t); int (*release) (struct gendisk *, fmode_t); int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, void **, unsigned long *); int (*media_changed) (struct gendisk *); unsigned long long (*set_capacity) (struct gendisk *, unsigned long long); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); struct module *owner; };
I/O请求
在Linux内核中,使用 struct request 来表示等待处理的块设备I/O请求。
struct request { struct list_head queuelist; //链表结构 sector_t sector; //要操作的首个扇区 unsigned long nr_sectors; //要操作的扇区数目 struct bio *bio; //请求的bio结构体的链表 struct bio *biotail; //请求的bio结构体的链表尾 ...... };
请求队列
请求队列就是 I/O请求 request 所形成的队列,在Linux内核中 struct request_queue 描述。
内核中提供了一系列函数用来操作请求队列:
- 初始化请求队列,一般在块设备驱动的模块加载函数中调用。
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
- 清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。
void blk_cleanup_queue(request_queue_t *q)
- 返回下一个要处理的请求(由I/O调度决定),如果没有请求则返回NULL。elv_next_request() 不会清除请求,它仍然将这个请求保留在队列上,因此连续调用它2次,2次返回同一个请求结构体。
struct request *elv_next_request (request_queue_t *queue)
- 从队列中删除1个请求
void blkdev_dequeue_request (struct request *req)
块设备完整驱动程序
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/timer.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/hdreg.h> //#include <linux/kdev.h> #include <linux/genhd.h> #include <linux/vmalloc.h> #include <linux/blkdev.h> #include <linux/buffer_head.h> #include <linux/bio.h> #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR #define SIMP_BLKDEV_DISKNAME "simp_blkdev" #define SIMP_BLKDEV_BYTES (16*1024*1024) static struct request_queue *simp_blkdev_queue; static struct gendisk *simp_blkdev_disk; unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES]; static void simp_blk_dev_do_request(struct request_queue *q) { struct request *req; while ((req = blk_fetch_request(q)) != NULL) { if((blk_rq_pos(req) +blk_rq_cur_sectors(req)) << 9 > SIMP_BLKDEV_BYTES) { printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad reques 20000 t:block=%llu,count=%u\n",(unsigned long long)blk_rq_pos(req),blk_rq_cur_sectors(req)); blk_end_request_all(req, 0); continue; } switch(rq_data_dir(req)) { case READ: memcpy(req->buffer,simp_blkdev_data + (blk_rq_pos(req) << 9),blk_rq_cur_sectors(req) << 9); blk_end_request_all(req, 1); break; case WRITE: memcpy(simp_blkdev_data + (blk_rq_pos(req) << 9),req->buffer,blk_rq_cur_sectors(req) << 9); blk_end_request_all(req, 1); break; default: break; } } } struct block_device_operations simp_blkdev_fops = { .owner = THIS_MODULE, }; static int __init simp_blkdev_init(void) { int ret; simp_blkdev_queue = blk_init_queue(simp_blk_dev_do_request,NULL); if(!simp_blkdev_queue) { ret = -ENOMEM; goto err_init_queue; } simp_blkdev_disk = alloc_disk(1); if(!simp_blkdev_disk) { ret = -ENOMEM; goto err_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); 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); } module_init(simp_blkdev_init); module_exit(simp_blkdev_exit);
linux2.6.38 内核struct request成员中少了好多成员(或者是改成其他名字了),比如
request -> sectors 变为 blk_rq_pos(request)
request -> current_nr_sectors 变为 blk_rq_nr_sectors(request)
__elev_next_request(request) 变为 blk_fetch_request(request_queue)
end_request(request, error) 变为 blk_end_request_all(request, error)
块设备驱动测试
数据访问流程
BIO
一个struct bio 代表一个块设备I/O请求,I/O调度器可将连续的bio合并成一个请求 struct request
struct bio { sector_t bi_sector; //要访问的第一个扇区 unsigned int bi_size; //以字节为单位所需传输的数据大小 struct bio_vec *bi_io_vec; //实际的vec列表 };
struct bio_vec { struct page *bv_page; //页指针 unsigned int bv_len; //传输的数据长度 unsigned int bv_offset; //偏移量 };
__make_request
根据以上陈述,在不使用IO调度情况下的块设备驱动程序设计代码如下:
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/timer.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/hdreg.h> //#include <linux/kdev.h> #include <linux/genhd.h> #include <linux/vmalloc.h> #include <linux/blkdev.h> #include <linux/buffer_head.h> #include <linux/bio.h> #include <linux/version.h> #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR #define SIMP_BLKDEV_DISKNAME "simp_blkdev" #define SIMP_BLKDEV_BYTES (16*1024*1024) MODULE_AUTHOR("silence"); MODULE_LICENSE("GPL"); static struct request_queue *simp_blkdev_queue; static struct gendisk *simp_blkdev_disk; unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES]; static int simp_blkdev_make_request(struct request_queue *q,struct bio *bio) { struct bio_vec *bvec; int i; void *dsk_mem; if((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) { printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad request:block=%llu,count=%u\n", (unsigned long long)bio->bi_sector,bio->bi_size); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) bio_endio(bio,0,-EIO); #else bio_endio(bio,-EIO); #endif return 0; } dsk_mem = simp_blkdev_data + (bio->bi_sector << 9); bio_for_each_segment(bvec, bio, i) { void *iovec_mem; switch(bio_rw(bio)) { case READ: case READA: iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; memcpy(iovec_mem, dsk_mem,bvec->bv_len); kunmap(bvec->bv_page); break; case WRITE: iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; memcpy(dsk_mem,iovec_mem,bvec->bv_len); kunmap(bvec->bv_page); break; default: printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": unknown value of bio_rw:%lu\n",bio_rw(bio)); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) bio_endio(bio,0,-EIO); #else bio_endio(bio,-EIO); #endif return 0; } dsk_mem += bvec->bv_len; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) bio_endio(bio,bio->bi_size,0); #else bio_endio(bio,0); #endif return 0; } struct block_device_operations simp_blkdev_fops = { .owner = THIS_MODULE, }; static int __init simp_blkdev_init(void) { int ret; simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL); if(!simp_blkdev_queue) { ret = -ENOMEM; goto err_alloc_queue; } blk_queue_make_request(simp_blkdev_queue,simp_blkdev_make_request); simp_blkdev_disk = alloc_disk(1); 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); return 0; err_alloc_disk: blk_cleanup_queue(simp_blkdev_queue); err_alloc_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); } module_init(simp_blkdev_init); module_exit(simp_blkdev_exit);
- 基于mini6410的linux驱动学习总结(四 设计字符设备驱动程序)
- Linux按键驱动程序设计(1)-混杂设备驱动模型
- Linux USB设备驱动程序设计 和 USB下载线驱动设计
- Linux驱动USB驱动程序之USB设备驱动程序1简单编写
- Linux驱动程序开发 - 设备驱动模型初探(二)
- Linux 设备驱动--- 内核等待队列 --- wait_queue_head --- wait_event_interruptible --- 按键驱动程序优化
- linux4.10.8 内核移植(四)---字符设备驱动_led驱动程序
- linux设备驱动之 i2c设备驱动 at24c08驱动程序分析
- linux字符设备驱动程序的设计之休眠
- Linux 设备驱动--- 内核等待队列 --- wait_queue_head --- wait_event_interruptible --- 按键驱动程序优化
- linux设备驱动——NandFlash驱动程序
- 11-S3C2440驱动学习(五)嵌入式linux-网络设备驱动(二)移植DM9000C网卡驱动程序
- Linux设备驱动之五----带poll机制的驱动程序
- 【Linux设备驱动程序(第三版)】----驱动调试Proc
- Linux设备驱动程序设计
- Linux 线程信号量,进程信号量和内核驱动程序信号量(线程同步信号量,进程同步信号量和设备驱动同步信号量)
- Linux下PCI设备驱动程序开发 --- linux 驱动框架(二)
- Linux字符设备驱动程序开发(3)-LED驱动程序设计
- linux驱动-USB驱动程序之USB设备驱动程序2鼠标用作键盘
- 基于mini6410的linux驱动学习总结(五 字符设备驱动程序实例分析(虚拟设备驱动))