1、块设备的I/O操作特色
字符设备与块设备的差异:
块设备只能以块为单位承受输入和回来输出,而字符设备则以字符为单位。
块设备关于I/O恳求有对应的缓冲区,因而它们能够挑选以什么次序进行呼应,字符设备无需缓冲区且直接被读写。
字符设备只能被次序读写,而块设备能够随机读写。 可是关于磁盘等机械设备而言,次序的安排块设备的拜访能够进步功用
整体而言,块设备驱动比字符设备驱动要杂乱得多,在I/O操作上表现出极大的不同,缓冲、I/O调度、恳求行列等都是与块设备驱动相关的概念。
关于扇区1、10、3、2的恳求被调整为对扇区1、2、3、10的恳求。可是关于SD卡、RAMDISK等非机械设备来说则没必要调整
2.block_device_operations结构体
1、翻开和开释
int (*open)(struct inode *inode,struct file *filp); int(*release)(struct inode *inode,struct file *filp); //与字符设备驱动相似,当设备翻开和封闭的时分调用它们
2、io操控
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd, unsigned long arg); //上述函数是ioctl()体系调用的完结,块设备包括许多的规范恳求,这些规范恳求由Linux块设备层处理,因而大部分块设备驱动的ioctl()函数适当短。
3、介质改动
int (*media_changed)(struct gendisk *gd); /*被内核调用来查看是否驱动器中的介质现已改动。假如是,则回来一个非0值。不然回来0.这个函数仅适用于支撑可移动介质的驱动器,一般需求在驱动器中增加一个标明介质状况是否改动的标志变量,非可移动设备的驱动不需求完结这个办法。*/
4、使介质有用
int (*revalidate_disk)(struct gendisk *gd);// revalidate_disk()函数被调用来呼应一个介质改动,它给驱动一个机会来进行必要的作业以使新介质准备好。
5、取得驱动器信息
int (*getgeo)(struct block_device *,struct hd_geometry *) ;//该函数依据驱动器的几许信息填充一个hd_geometry的结构体,包括磁头、扇区、柱面等信息。
6、模块指针
struct module * owner;// 一个指向具有这个结构体的模块的指针,它一般被初始化为THIS_MODULE.
3. gendisk分析
在Linux内核中,运用gendisk(通用磁盘)结构体来标明1个独立的磁盘设备(或分区)。
int major;/* 主设备号 */int first_minor; /*第1个次设备号*/int minors: //磁盘运用这些成员描绘设备号。 //最大的次设备数,假如不能分区,则为1,一个驱动器至少运用一个次设备号。 //假如驱动器是可被分区的(大多数情况下),用户将要为每个或许的分区都分配一个次设备号。minors一般取16,他使得一个“完好的的磁盘“包括15个分区。 某些磁盘驱动程序设置每个设备可运用64个次设备号。char disk_name[32]; //设置磁盘设备姓名的成员。该姓名将显现在/proc/parTITIons和sysfs中。struct block_device_operaTIons *fops; //设置前面所述的各种设备操作;struct request_queue *queue; //内核运用该结构为设备办理i/o恳求;在”恳求进程“中详细进行论说。int flags; //用来描绘驱动器状况的标志(很少运用)。假如用户设备包括了可移动介质,其将被设置为GENHD_FL_REMOVABLE。sector_t capacity; //以512字节为一个扇区时,该驱动器可包括的扇区数。void *preivate_data; //块设备驱动程序或许运用该成员保存指向其内部数据的指针。
major、first_minor和minors一起表征了磁盘的主、次设备号,同一个磁盘的各个分区同享1个主设备号,而次设备号则不同。
fops为block_device_operaTIons,即上节描绘的块设备操作调集。queue是内核用来办理这个设备的 I/O恳求行列的指针。
capacity标明设备的容量,以512个字节为单位。private_data可用于指向磁盘的任何私有数据,用法与字符设备驱动file结构体的private_data相似。
Linux内核供给了一组函数来操作gendisk,首要包括:
• 分配gendisk
gendisk结构体是一个动态分配的结构体,它需求特别的内核操作来初始化,驱动不能自己分配这个结构体,而应该运用下列函数来分配gendisk:
struct gendisk *alloc_disk(int minors);
minors 参数是这个磁盘运用的次设备号的数量,一般也便是磁盘分区的数量,尔后minors不能被修正。
• 增加gendisk
gendisk结构体被分配之后,体系还不能运用这个磁盘,需求调用如下函数来注册这个磁盘设备:
void add_disk(struct gendisk *gd);
特别要留意的是对add_disk()的调用有必要发生在驱动程序的初始化作业完结并能呼应磁盘的恳求之后。
• 开释gendisk
当不再需求一个磁盘时,应当运用如下函数开释gendisk:
void del_gendisk(struct gendisk *gd);
• gendisk引证计数
gendisk中包括1个kobject成员,因而,它是一个可被引证计数的结构体。经过get_disk()和put_disk()函数可用来操作引证计数,这个作业一般不需求驱动亲身做。一般对 del_gendisk()的调用会去掉gendisk的终究引证计数,可是这一点并不是必定的。
因而,在del_gendisk()被调用后,这个结构体或许持续存在。
• 设置gendisk容量
void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区巨细一般是2的整数倍,最常见的巨细是512字节。扇区的巨细是设备的物理特点,扇区是一切块设备的根本单元,块设备无法比照它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。尽管大多数块设
备的扇区巨细都是512字节,不过其它巨细的扇区也很常见,比方,许多CD-ROM盘的扇区都是2K巨细。
不论物理设备的实在扇区巨细是多少,内核与块设备驱动交互的扇区都以512字节为单位。因而,set_capacity()函数也以512字节为单位。
4.块设备驱动模块加载和卸载函数模板
在块设备驱动的模块加载函数中一般需求完结如下作业:
① 分配、初始化恳求行列,绑定恳求行列和恳求函数。
② 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最终增加gendisk。
③ 注册块设备驱动。
运用blk_alloc_queue
static int __init xxx_init(void){ //分配gendisk xxx_disks = alloc_disk(1); if (!xxx_disks) { goto out; } /* 块设备驱动注册 在2.6内核中,对 register_blkdev()的调用彻底是可选的,register_blkdev()的功用已随时刻正在削减,这个调用最多只彻底2件事: ① 假如需求,分配一个动态主设备号。 ② 在/proc/devices中创立一个进口。 在将来的内核中,register_blkdev()或许会被去掉。可是现在的大部分驱动依然调用它。 */ if (register_blkdev(XXX_MAJOR, “xxx”)) { err = – EIO; goto out; } //“恳求行列”分配 xxx_queue = blk_alloc_queue(GFP_KERNEL); if (!xxx_queue) { goto out_queue; } blk_queue_make_request(xxx_queue, &xxx_make_request); //绑定“制作恳求”函数 blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺度设置 //gendisk初始化 xxx_disks->major = XXX_MAJOR; xxx_disks->first_minor = 0; xxx_disks->fops = &xxx_op; xxx_disks->queue = xxx_queue; sprintf(xxx_disks->disk_name, “xxx%d”, i); set_capacity(xxx_disks, xxx_size); //xxx_size以512bytes为单位 add_disk(xxx_disks); //增加gendisk return 0; out_queue: unregister_blkdev(XXX_MAJOR, “xxx”); out: put_disk(xxx_disks); blk_cleanup_queue(xxx_queue); return – ENOMEM;}
运用blk_init_queue
static int __init xxx_init(void){ //块设备驱动注册 if (register_blkdev(XXX_MAJOR, “xxx”)) { err = – EIO; goto out; } //恳求行列初始化 xxx_queue = blk_init_queue(xxx_request, xxx_lock); if (!xxx_queue) { goto out_queue; } blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺度设置 //gendisk初始化 xxx_disks->major = XXX_MAJOR; xxx_disks->first_minor = 0; xxx_disks->fops = &xxx_op; xxx_disks->queue = xxx_queue; sprintf(xxx_disks->disk_name, “xxx%d”, i); set_capacity(xxx_disks, xxx_size *2); add_disk(xxx_disks); //增加gendisk return 0;out_queue: unregister_blkdev(XXX_MAJOR, “xxx”);out: put_disk(xxx_disks); blk_cleanup_queue(xxx_queue); return – ENOMEM;}
每个块设备驱动程序的中心是它的恳求函数。
实践的作业,如设备的发动,都是在这个函数中完结。
驱动程序的新能,是这个操作体系功用的重要组成部分,因而内核的块设备子体系在编写的时分就十分留意功用方面的问题。
块设备驱动模块卸载函数模板
① 铲除恳求行列。
② 删去gendisk和对gendisk的引证。
③ 删去对块设备的引证,刊出块设备驱动。
static void __exit xxx_exit(void){ if (bdev) { invalidate_bdev(xxx_bdev, 1); blkdev_put(xxx_bdev); } del_gendisk(xxx_disks); //删去gendisk put_disk(xxx_disks); blk_cleanup_queue(xxx_queue[i]); //铲除恳求行列 unregister_blkdev(XXX_MAJOR, “xxx”);}
5.块设备驱动I/O
request函数介绍
原型:
void request(request_queue_t *queue);
这个函数不能由驱动自己调用,只有当内核认为是时分让驱动处理对设备的读写等操作时,它才调用这个函数。
恳求函数能够在没有完结恳求行列中的一切恳求的情况下回来,乃至它1个恳求不完结都能够回来。可是,对大部分设备而言,在恳求函数中处理完一切恳求后再回来一般是值得引荐的办法。
对request函数的调用是与用户空间进程中的动作是彻底异步的,因而不能直接对用户空间进行拜访。
块设备驱动恳求函数例程:
static void xxx_request(request_queue_t *q){ struct request *req; while ((req = elv_next_request(q)) != NULL) { struct xxx_dev *dev = req->rq_disk->private_data; if (!blk_fs_request(req)) //不是文件体系恳求 { printk(KERN_NOTICE “Skip non-fs request/n”); end_request(req, 0);//告知恳求处理失利 continue; } xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req)); //处理这个恳求 end_request(req, 1); //告知成功完结这个恳求 }}//完结详细的块设备I/O操作static void xxx_transfer(struct xxx_dev *dev, unsigned long sector, unsignedlong nsect, char *buffer, int write){ unsigned long offset = sector * KERNEL_SECTOR_SIZE; unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE; if ((offset + nbytes) > dev->size) { printk(KERN_NOTICE “Beyond-end write (%ld %ld)/n”, offset, nbytes); return ; } if (write) { write_dev(offset, buffer, nbytes); //向设备些nbytes个字节的数据 } else { read_dev(offset, buffer, nbytes); //从设备读nbytes个字节的数据 }}void end_request(struct request *req, int uptodate){ /* 当设备现已完结1个I/O恳求的部分或许悉数扇区传输后, end_that_request_first这个函数奉告块设备层,块设备驱动现已完结count个扇区的传送 回来值为0标明一切的扇区现已被传送而且这个恳求完结 */ if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) { /* 运用块 I/O 恳求的守时来给体系的随机数池奉献熵,它不影响块设备驱动。 可是,仅当磁盘的操作时刻是真实随机的时分(大部分机械设备如此),才应该调用它。 */ add_disk_randomness (req->rq_disk); blkdev_dequeue_request (req);//从行列中铲除这个恳求 end_that_request_last(req);//告知一切正在等候这个恳求完结的目标恳求现已完结并收回这个恳求结构体。 } }
恳求行列
一个块设备恳求行列是:包括块设备I/O恳求的序列。
恳求行列盯梢未完结的块设备的I/O恳求,保存了描绘设备所能处理的恳求的参数:最大尺度、在同一个恳求中所包括的独立段的数目、硬件扇区的巨细、对齐需求等。
恳求行列完结了插件接口,以便能够运用多个I/O调度器。
I/O调度器积累了许多的恳求,依据块索引号升序(或许降序)摆放他们,并依照这个次序向驱动程序发送恳求。
磁头从一个磁盘的结尾移向另一个磁盘,好像单向电梯相同,直到每个恳求都得到满意。
行列的创立与删去
一个恳求行列便是一个动态的数据结构,该结构有必要由块设备的I/O子体系创立。
request_queue_t *blk_init_queue(request_fn_proc *request,spinlock_t *lock) ;//该函数参数是处理这个行列的request指针和操控拜访行列权限的自旋锁。void blk_cleanup_queue(request_queue_t *);//删去行列
行列中的函数
回来行列中下一个要处理的恳求
struct request *elv_next_request(request_queue_t *queue);
将恳求从行列中删去
void blkdev_dequeue_request(struct request *req);
行列操控函数
驱动程序运用块设备层处处的一组函数去操控恳求行列的操作。
void blk_stop_queue(request_queue_t *queue)void blk_start_queue(request_queue_t *queue) //假如驱动程序进入不能处理更多指令的状况,就会调用blk_stop_queue以告知块设备层,以暂停调用request函数。当有才能处理更多恳求时,需求调用blk_start_queue重新开始调用。void blk_queue_bounce_limit(request_queue_t *queue,u64 dma_addr); //该函数告知内核驱动程序履行DMA所运用的最高物理内存。假如一个恳求包括了逾越边界的内存引证,将运用回弹缓冲区(bounce buffer)进行处理。
恳求进程分析
每个request结构都代表了一个块设备的I/O恳求。
一个特定的恳求能够散布在整个内存中,但多数是对相邻的扇区进行操作。
假如多个恳求都是对磁盘中的相邻扇区进行操作,则内核将对他们进行兼并。
从本质上讲,一个request结构是作为一个bio结构的链表完结的。
bio
bio结构 bio结构在中界说,包括了驱动程序作者所要运用的许多成员。
sector_t bi_sector; //该bio结构所要传输的第一个扇区(512字节)unsigned int bi_size; //以字节为单位所需传输的数据巨细。unsigned long bi_flags; //bio中一系列的标志位;假如是写恳求,最低有用位将被设置。unsigned short bio_phys_segments;unsigned short bio_hw_segments; //当DMA 映射完结时,它们别离标明bio中包括的物理段的数目和硬件所能操作的数目。
request结构成员
sector_t hard_sector;unsigned long hard_nr_sectors;unsigned int hard_cur_sectors;// 用于盯梢那些驱动程序还未完结的扇区。还未传输的第一个扇区保存在hard_sector中,等候传输扇区的总数量保存在hard_nr_sectors中,当时bio中剩下的扇区数目包括在hard_cur_sectors中。struct bio *bio;// 该恳求的bio结构链表。struct list_head queuelist;// 内核链表结构,用来把恳求连接到恳求行列中。
屏障恳求
在驱动程序收到恳求前,块设备层重新组合了恳求以进步I/O功用。出于相同的意图,驱动程序也能够重新组合恳求。
可是一些应用程序的某些操作,要写在别的一些操作之前,比方联系数据库在履行一个联系数据库内容的会话前,日志信息要写到驱动器上。
2.6内核选用屏障(barrier)恳求解决问题:假如一个恳求被设置了REQ_HARDBARRER标志,那么这以后恳求被初始化前,它有必要被写进驱动器。
不行重试恳求
当第一次恳求失利后,块设备驱动程序常常要重试恳求。这样的功用使得体系更牢靠,不会丢掉数据。
可是,内核在某些情况下符号恳求是不行重试的。这些恳求假如在第一次履行失利后,要赶快扔掉。