Chinaunix首页 | 论坛 | 博客

OS

  • 博客访问: 2280470
  • 博文数量: 691
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2660
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-05 12:49
个人简介

不浮躁

文章分类

全部博文(691)

文章存档

2019年(1)

2017年(12)

2016年(99)

2015年(207)

2014年(372)

分类: LINUX

2014-11-14 20:50:37

原文地址:Linux块设备驱动 作者:printk1986


  1. (1)---块驱动中相关的结构体及其操作
  2. 1.字符设备与块设备IO操做的区别
  3. 1)块设备只能以块为单位接收输入返回输出,而字符设备则以byte为单位.大多数设备是字符设备,他们不需要缓冲并且不以固定块大小进行操作.
  4. 2)块设备对于IO请求有对应的缓冲区,所以他们可以选择以什么顺序进行响应.字符设备无须缓冲且被直接读写.
  5. 3)字符设备只能被顺序读写,块设备可以随机访问.
  6.  
  7.  
  8. 2.block_device_operations结构体
  9. block_device_operations描述了对块设备的操作的集合
  10.     struct block_device_operations {
  11.         int (*open) (struct inode *, struct file *);/*打开*/
  12.         int (*release) (struct inode *, struct file *);/*释放*/
  13.         int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
  14.         long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
  15.         long (*compat_ioctl) (struct file *, unsigned, unsigned long);
  16.         int (*direct_access) (struct block_device *, sector_t, unsigned long *);
  17.         int (*media_changed) (struct gendisk *);/*介质被改变?*/
  18.         int (*revalidate_disk) (struct gendisk *);/*使介质改变*/
  19.         int (*getgeo)(struct block_device *, struct hd_geometry *);/*填充驱动器信息*/
  20.         struct module *owner;/*模块拥有者,一般初始化为THIS_MODULE*/
  21.     };
  22. 关于block_device_operations的操作:
  23.     //open and release
  24.     int (* open)(struct inode*, struct file*);
  25.     int (* release)(struct inode*, struct file*);
  26.     //io contrl
  27.     //系统调用实现,块设备包含大量的标准请求,由设备层处理,所以此函数一般相当短
  28.     int (* ioctl)(struct inode*,struct file*,unsigned int,unsigned long);
  29.     //media changed
  30.     //如果改变返回非0值,否则返回0
  31.     int (*media_changed)(struct gendisk*);
  32.     //revalidate media
  33.     //用于响应一个介质的改变,给驱动一个机会做准备工作
  34.     int (* revalidate_disk)(struct gendisk*);
  35.     //get driver informaiton
  36.     //根据驱动器的几何信息填充hd_geometry,包含磁头,柱面,扇区等信息.
  37.     int (* getgeo)(struct block_device*, struct hd_geometry*);
  38.  
  39.  
  40.  

  41. 3.gendisk结构体
  42. 使用gendisk结果提来描述一个独立的磁盘设备或分区.
  43.     //gendisk structure
  44.     struct gendisk{
  45.         /*前三个元素共同表征了一个磁盘的主,次设备号,同一个磁盘的各个分区共享一个主设备号*/
  46.         int major;/*主设备号*/
  47.         int first_minor;/*第一个次设备号*/
  48.         int minors;/*最大的次设备数,如果不能分区,则为1*/
  49.         char disk_name[32];
  50.         struct hd_struct** part;/*磁盘上的分区信息*/
  51.         struct block_device_operations* fops;/*块设备操作,block_device_operations*/
  52.         struct request_queue* queue;/*请求队列,用于管理该设备IO请求队列的指针*/
  53.         void* private_data;/*私有数据*/
  54.         sector_t capacity;/*扇区数,512字节为1个扇区,描述设备容量*/
  55.         //......
  56.     };
  57.  关于gendisk的操作:
  58.     /*分配一个gendisk结构体,此结构体是由内核动态分配的*/
  59.     struct gendisk* alloc_disk(int minors);
  60.     /*增加gendisk,来注册该设备,此动作应该在设备驱动初始化完毕,并能响应磁盘请求之后*/
  61.     void add_disk(struct gendisk* gd);
  62.     /*释放一个不再需要的磁盘*/
  63. void del_gendisk(struct gendisk* gd);
  64.     /*gendisk引用计数*/
  65.     /**
  66.     ***gendisk引用计数器:gendisk包含一个kobject成员.通过get_disk()&put_disk()函数来操作引用
  67.     ***计数,此操作不需要驱动亲自完成.通常调用del_gendisk()会去掉gendisk的最终引用计数,但不是必
  68.     ***须的,因此在del_gendisk()后gendisk结构体可能继续存在.
  69.     **/
  70.     /*设置gendisk容量*/
  71.     void set_capacity(struct gendisk* disk, sector_t size);
  72. 块 设备中,最小的可寻址单元就扇区,常见扇区大小是512字节.扇区的大小是设备的物理属性,是所有块设备的基本单元,块设备无法对比扇区小的单元进行寻址 和操作.不过许多块设备能够一次传输多个扇区.不管物理设备的真实扇区是多少,内核与块设备交互的扇区均以512字节为单位.所以set_capcity()函数以512字节为单位.
  73.  
  74.  
  75. 4.request和bio结构体
  76. 1)请求request
  77. request和request_queue结构体:Linux块设备驱动中,使用request结构体来表征等待进行的IO请求;并用request_queue来表征一个块IO请求队列.两个结构体的定义如下:
  78. request结构体
  79.     struct request{
  80.         struct list_head queuelist;
  81.         unsigned long flags;
  82.  
  83.         sector_t sector;/*要传输的下一个扇区*/
  84.         unsigned long nr_sectors;/*要传送的扇区数目*/
  85.         unsigned int current_nr_sector;/*当前要传送的扇区*/
  86.  
  87.         sector_t hard_sector;/*要完成的下一个扇区*/
  88.         unsigned long hard_nr_sectors;/*要被完成的扇区数目*/
  89.         unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/
  90.  
  91.         struct bio* bio;/*请求的bio结构体的链表*/
  92.         struct bio* biotail;/*请求的bio结构体的链表尾*/
  93.         
  94.         /*请求在屋里内存中占据的不连续的段的数目*/
  95.         unsigned short nr_phys_segments;
  96.         unsigned short nr_hw_segments;
  97.  
  98.         int tag;
  99.         char* buffer;/*传送的缓冲区,内核的虚拟地址*/
  100.         int ref_count;/*引用计数*/
  101.         ...
  102.     };
  103. 说明:
  104. request结构体的主要成员包括:
  105.         sector_t hard_sector;/*要完成的下一个扇区*/
  106.         unsigned long hard_nr_sectors;/*要被完成的扇区数目*/
  107.         unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/
  108.         /*
  109.          * 上述三个成员依次是第一个尚未传输的扇区,尚待完成的扇区数,当前IO操作中待完成的扇区数
  110.          * 但驱动中一般不会用到他们.而是下面的一组成员.
  111.          */
  112.         sector_t sector;/*要传输的下一个扇区*/
  113.         unsigned long nr_sectors;/*要传送的扇区数目*/
  114.         unsigned int current_nr_sector;/*当前要传送的扇区*/
  115.         /*
  116.          * 这三个成员,以字节为单位.如果硬件的扇区大小不是512字节.如字节,则在开始对硬件进行操作之
  117.          *,应先用4来除起始扇区号.前三个成员,与后三个成员的关系可以理解为"副本".
  118.          */
  119. 关于unsigned short nr_phys_segments:该成员表示相邻的页被合并后,这个请求在物理内存中的段的数目.如果该设备支持SG(分散/聚合,scatter/gather),可根据该字段申请sizeof(scatterlist*) nr_phys_segments的内存,并使用下面的函数进行DMA映射:
  120. int blk_rq_map_sg(request_queue_t* q, struct request* rq, struct scatterlist *sg);
  121. 该函数与dma_map_sg()类似,返回scatterlist列表入口的数量.
  122. 关于struct list_head queuelist:该成员用于链接这个请求到请求队列的链表结构,函数blkdev_ dequeue_request()可用于从队列中移除请求.宏rq_data_dir(struct request* req)可获得数据传送方向.返回0表示从设备读取,否则表示写向设备.
  123.  
  124. 2)request_queue请求队列
  125.     struct request_queue{
  126.         ...
  127.         /*自旋锁,保护队列结构体*/
  128.         spinlock_t __queue_lock;
  129.         spinlock_t* queue_lock;
  130.         struct kobject kobj;/*队列kobject*/
  131.         /*队列设置*/
  132.         unsigned long nr_requests;/*最大的请求数量*/
  133.         unsigned int nr_congestion_on;
  134.         unsigned int nr_congestion_off;
  135.         unsigned int nr_batching;
  136.         unsigned short max_sectors;/*最大扇区数*/
  137.         unsigned short max_hw_sectors;
  138.         unsigned short max_phys_sectors;/*最大的段数*/
  139.         unsigned short max_hw_segments;
  140.         unsigned short hardsect_size;/*硬件扇区尺寸*/
  141.         unsigned int max_segment_size;/*最大的段尺寸*/
  142.         unsigned long seg_boundary_mask;/*段边界掩码*/
  143.         unsigned int dma_alignment;/*DMA传送内存对齐限制*/
  144.         struct blk_queue_tag* queue_tags;
  145.         atomic_t refcnt;/*引用计数*/
  146.         unsigned int in_flight;
  147.         unsigned int sg_timeout;
  148.         unsigned int sg_reserved_size;
  149.         int node;
  150.         struct list_head drain_list;
  151.         struct request* flush_rq;
  152.         unsigned char ordered;
  153.     };
  154. 说明:请求队列跟踪等候的块IO请求,它存储用于描述这个设备能够支持的请求的类型信息,他们的最大大小,多少不同的段可以进入一个请求,硬件扇区大小,对齐要求等参数.其结果是:如果请求队列被配置正确了,它不会交给该设备一个不能处理的请求.
  155. 请 求队列还要实现一个插入接口,这个接口允许使用多个IO调度器,IO调度器以最优性能的方式向驱动提交IO请求.大部分IO调度器是积累批量的IO请求, 并将其排列为递增/递减的块索引顺序后,提交给驱动.另外,IO调度器还负责合并邻近的请求,当一个新的IO请求被提交给调度器后,它会在队列里搜寻包含 邻近的扇区的请求.如果找到一个,并且请求合理,调度器会将这两个请求合并.
  156. Linux2.6的四个IO调度器,他们分别是No-op/Anticipatory/Deadline/CFQ IO scheduler.
  157. 关于request_queu结构体的操作:
  158.     //初始化请求队列
  159.     kernel elevator = deadline;/*给kernel添加启动参数*/
  160.     request_queue_t* blk_init_queue(request_fn_proc* rfn, spinlock_t* lock);
  161.         /*
  162.          * 两个参数分别是请求处理函数指针和控制队列访问权限的自旋锁.
  163.          * 此函数会发生内存分配的行为,需要检查其返回值.一般在加载函数中调用.
  164.          */
  165.     //清除请求队列
  166.     void blk_cleanup_queue(request_queue_t* q);
  167.         /*
  168.          * 此函数完成将请求队列返回给系统的任务,一般在卸载函数中调用.
  169.          * 此函数即bld_put_queue()的宏定义#define blk_put_queue(q) blk_cleanup_queue((q))
  170.          */
  171.     //分配"请求队列"
  172.     request_queue_t* blk_alloc_queue(int gfp_mask);
  173.     void blk_queue_make_request(request_queue_t* q, make_request_fn* mfn);
  174.         /*
  175.          * 前一个函数用于分配一个请求队列,后一个函数是将请求队列和"制造函数"进行绑定
  176.          * 但函数blk_alloc_queue实际上并不包含任何请求.
  177.          */
  178.     //提取请求
  179.     struct request* elv_next_request(request_queue_t* q);
  180.     //去除请求
  181.     void blkdev_dequeue_request(struct request* req);
  182.     void elv_requeue_request(request_queue_t* queue, struct request* req);
  183.     //启停请求
  184.     void blk_stop_queue(request_queue_t* queue);
  185.     void blk_start_queue(request_queue_t* queue);
  186.     //参数设置
  187.     void blk_queue_max_sectors(request_queue_t* q, unsigned short max);
  188.         /*请求可包含的最大扇区数.默认255*/
  189.     void blk_queue_max_phys_segments(request_queue_t* q, unsigned short max);
  190.     void blk_queue_max_hw_segments(request_queue_t* q, unsigned short max);
  191.         /*这两个函数设置一个请求可包含的最大物理段数(系统内存中不相邻的区),缺省是128*/
  192.     void blk_queue_max_segment_size(request_queue_t* q, unsigned int max);
  193.         /*告知内核请求短的最大字节数,默认2^16 = 65536*/
  194.     //通告内核
  195.     void blk_queue_bounce_limit(request_queue_t* queue, u64 dma_addr);
  196.         /*
  197.          * 此函数告知内核设备执行DMA时,可使用的最高物理地址dma_addr,常用的宏如下:
  198.          * BLK_BOUNCE_HIGH:对高端内存页使用反弹缓冲(缺省)
  199.          * BLK_BOUNCE_ISA:驱动只可以在MB的ISA区执行DMA
  200.          * BLK_BOUNCE_ANY:驱动可在任何地方执行DMA
  201.          */
  202.     blk_queue_segment_boundary(request_queue_t* queue, unsigned long mask);
  203.         /*这个函数在设备无法处理跨越一个特殊大小内存边界的请求时,告知内核这个边界.*/
  204.     void blk_queue_dma_alignment(request_queue_t* q, int mask);
  205.         /*告知内核设备加于DMA传送的内存对齐限制*/
  206.     viod blk_queue_hardsect_size(request_queue_t* q, unsigned short max);
  207.        /*此函数告知内核块设备硬件扇区大小*/
  208.  
  209. 3)块I/O
  210. 通常一个bio对应一个IO请求.IO调度算法可将连续的bio合并成一个请求.所以一个请求包含多个bio.
  211.     struct bio{
  212.         sector_t bi_sector;/*要传送的第一个扇区*/
  213.         struct bio* bi_next;/*下一个bio*/
  214.         struct block_device* bi_bdev;
  215.         unsigned long bi_flags;
  216.         /*如果是一个写请求,最低有效位被置位,可使用bio_data_dir(bio)宏来获取读写方向*/
  217.  
  218.         unsigned long bi_rw;/*地位表示R/W方向,高位表示优先级*/
  219.  
  220.         unsigned short bi_vcnt;/*bio_vec数量*/
  221.         unsigned short bi_idx; /*当前bvl_vec索引*/
  222.  
  223.         unsigned short bi_phys_segments;/*不相邻的物理段的数目*/
  224.         unsigned short bi_hw_segments;/*物理合并和DMA remap合并后不相邻的物理扇区*/
  225.  
  226.         unsigned int bi_size;
  227.         /*被传送的数据大小(byte),用bio_sector(bio)获取扇区为单位的大小*/
  228.  
  229.         /*为了明了最大的hw尺寸,考虑bio中第一个和最后一个虚拟的可合并的段的尺寸*/
  230.         unsigned int bi_hw_front_size;
  231.         unsigned int bi_hw_back_size;
  232.  
  233.         unsigned int bi_max_vecs;/*能持有的最大bvl_vecs数*/
  234.  
  235.         struct bio_vec* bio_io_vec;/*实际的vec列表*/
  236.         bio_end_io_t* bio_end_io;
  237.         atomic_t bi_cnt;
  238.         void* bi_private;
  239.         bio_destructor_t* bi_destructor;
  240.     };
  241.  
  242.     //结构体包含三个成员
  243.     struct bio_vec{
  244.         struct page* bv_page;//页指针
  245.         unsigned int bv_len;//传送的字节数
  246.         unsigned int bv_offset;//偏移位置
  247.     };
  248. /*一般不直接访问bio的bio_vec成员,而使用bio_for_each_segment()宏进行操作.
  249.  *该宏循环遍历整个bio中的每个段.
  250.  */
  251.     #define __bio_for_each_segment(bvl, bio, i, start_idx)\
  252.             for(
  253.                 bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\
  254.                 i <(bio)->bi_vcnt;\
  255.                 bvl++, i++\
  256.             )
  257.     #define bio_for_each_segment(bvl, bio, i)\
  258.               __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)
  259. 在内核中,提供了一组函数()用于操作bio:
  260.     int bio_data_dir(struct bio* bio);
  261.     该函数用于获得数据传送方向.
  262.     struct page* bio_page(struct bio* bio);
  263.     该函数用于获得目前的页指针.
  264.     int bio_offset(struct bio* bio);
  265.     该函数返回操作对应的当前页的页内偏移,通常块IO操作本身就是页对齐的.
  266.     int bio_cur_sectors(struct bio* bio);
  267.     该函数返回当前bio_vec要传输的扇区数.
  268.     char* bio_data(struct bio* bio);
  269.     该函数返回数据缓冲区的内核虚拟地址.
  270.     char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset);
  271.     该函数也返回一个内核虚拟地址此地址可用于存取被给定的bio_vec入口指向的数据缓冲区.同时会屏蔽中断并返回一个原子kmap,因此,在此函数调用之前,驱动不应该是睡眠状态.
  272.     void bvec_kunmap_irq(char* buffer, unsigned long flags);
  273.     该函数撤销函数bvec_kmap_irq()创建的内存映射.
  274.     char* bio_kmap_irq(struct bio* bio, unsigned long* flags);
  275.     该函数是对bvec_kmap_irq函数的封装,它返回给定的比偶的当前bio_vec入口的映射.
  276.     char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type);
  277.     该函数是通过kmap_atomic()获得返回给定bio的第i个缓冲区的虚拟地址.
  278.     void __bio_kunmap_atomic(char* addr, enum km_type type);
  279.     该函数返还由函数__bio_kmap_atomic()获得的内核虚拟地址给系统.
  280.     void bio_get(struct bio* bio);
  281.     void bio_put(struct bio* bio);
  282.     上面两个函数分别完成对bio的引用和引用释放.
  283. 下图可以体现出bio/request/request_queue/bio_vec四个结构体之间的关系.
  284.  

  285.  
  286.  
  287. 5.块设备驱动注册于注销

  288. 块设备驱动的第一个任务就是将他们自己注册到内核中,其函数原型如下:
  289.     int register_blkdev(unsigned int major, const char* name);
  290. major参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被现实.如果major为0,内核会自动分配一个新的主设备号,并由该函数返回.如果返回值为负值,则说明设备号分派失败.
  291. 与register_blkdev对应的注销函数是unregister_blkdev(),原型如下:
  292.     int unreister_blkdev(unsigned int major, const char* name);
  293. 这里unreister_blkdev与register_blkdev的参数必须匹配,否则这个函数会返回-EINVAL.
  294. 在Linux2.6中,对register_blkdev的调用是可选的.register_blkdev这个调用在Linux2.6中只完成了两件事情:①如果需要,分派一个主设备号;②在/proc/devices中创建一个入口.

  295. (2)---块驱动中相关相关模块模板

  296. 1.块设备驱动的模块加载与卸载
  297. 1)块设备驱动的模块加载完成的工作如下:
  298. ? 分配,初始化请求队列,绑定请求队列和请求函数
  299. ? 分配,初始化gendisk,给gendisk的major,fops,queue等成员赋值,最后添加gendisk.
  300. ? 注册块设备驱动.
  301. 代码1:使用blk_alloc_queue函数完成块设备驱动的模块加载模板
  302.     static int __init xxx_init(void){
  303.         //分配gendisk
  304.         xxx_disks = alloc_disk(1);
  305.         if(!xxx_disks){
  306.             goto out;
  307.         }
  308.         //块设备驱动注册
  309.         if(register_blkdev(xxx_MAJOR, "xxx"){
  310.             err = -EIO;
  311.             goto out;
  312.         }
  313.         //"请求队列"分配
  314.         xxx_queue = blk_alloc_queue(GFP_KERNEL);
  315.         if(!xxx_queue){
  316.             goto out_queue;
  317.         }
  318.         blk_queue_make_request(xxx_queue, &xxx_make_request);//绑定"制造请求"函数
  319.         blk_queue_hardsect_size(xxx_queue,xxx_blocksize);//告知内核硬件扇区尺寸
  320.         //gendisk初始化
  321.         xxx_disks->major = xxx_MAJOR;
  322.         xxx_disks->first_minor = 0;
  323.         xxx_disks->fops = &xxx_fop;
  324.         xxx_disks->queue = xxx_queue;
  325.         sprintf(xxx_disks->disk_name, "xxx%d", i);
  326.         set_capacity(xxx_disks, xxx_size);//设置gendisk容量为xxx_size个扇区大小
  327.         add_disk(xxx_disks);
  328.  
  329.         return 0;
  330.  
  331.         out_queue:unregister_blkdev(xxx_MAJOR, "xxx");
  332.         out:put_disk(xxx_disks);
  333.         blk_cleanup_queue(xxx_queue);
  334.  
  335.         return -ENOMEM;
  336.     }
  337. 代码2:使用blk_init_queue函数完成块设备驱动的模块加载模板
  338.     static int __init xxx_init(void){
  339.         //块设备驱动注册
  340.         if(register_blkdev(xxx_MAJOR, "xxx"){
  341.             err = -EIO;
  342.             goto out;
  343.         }
  344.         //请求队列初始化
  345.         xxx_queue = blk_init_queue(xxx_request, xxx_lock);
  346.         if(!xxx_queue){
  347.             goto out_queue;
  348.         }
  349.  
  350.         blk_queue_hardsect_size(xxx_queue, xxx_blocksize);//告知内核硬件扇区大小
  351.  
  352.         //gendisk初始化
  353.         xxx_disks->major = xxx_MAJOR;
  354.         xxx_disks->first_minor = 0;
  355.         xxx_disks->fops = &xxx_fop;
  356.         xxx_disks->queue = xxx_queue;
  357.         sprintf(xxx_disks->disk_name, "xxx%d", i);
  358.         set_capacity(xxx_disks, xxx_size*2);//设置gendisk容量为xxx_size个扇区大小
  359.         add_disk(xxx_disks);
  360.  
  361.         return 0;
  362.         out_queue:unregister_blkdev(xxx_MAJOR, "xxx");
  363.         out:put_disk(xxx_disks);
  364.         blk_cleanup_queue(xxx_queue);
  365.  
  366.         return -ENOMEM;
  367.     }
  368.  
  369. 2)块设备驱动的模块卸载完成的工作如下:
  370. ? 清除请求队列.
  371. ? 删除gendisk和gendisk的引用
  372. ? 删除对块设备的引用,注销块设备驱动.
  373.  
  374. 代码3:块设备驱动模块卸载函数模板
  375.     static void __exit xxx_exit(void){
  376.         if(bdev){
  377.             invalidate_bdev(xxx_bdev, 1);
  378.             blkdev_put(xxx_bdev);
  379.         }
  380.         del_gendisk(xxx_disks);//删除gendisk
  381.         put_disk(xxx_disks);
  382.         blk_cleanup_queue(xxx_queue[i]);//清除请求队列
  383.         unregister_blkdev(xxx_MAJOR, "xxx");
  384.     }
  385.  
  386.  
  387. 2.块设备驱动的打开与释放
  388. 块设备驱动的open()和release()函数不是必须的,一个简单的块设备驱动可以不提供open()和release()函数.
  389. 块 设备驱动的open()函数和字符设备驱动的open()和类似,都以相关inode和file结构体指针作为参数,当一个结点引用一个块设备 时,inode->i_bdev->bd_disk包含一个指向关联gendisk的结构体的指针.因此类似字符设备,可将gendisk的 private_data赋给file的private_data,private_data同样最好是指向描述该设备的设备结构体xxx_dev的指 针.如下面的代码:
  390.     static int xxx_open(struct inode* inode, struct file* file){
  391.         struct xxx_dev* dev = inode->i_bdev->db_disk->private_data;
  392.         file->private_data = dev;
  393.         ...
  394.         return 0;
  395.     }
  396. 3.块设备驱动的ioctl
  397. 块 设备可以包含一个ioctl()函数,以提供对该设备的IO控制,实际上搞成的块设备层代码处理了绝大多数ioctl(),因此具体的块设备驱动中,通常 不在需要实现很多ioctl()命令.下面的代码中只实现一个命令HDIO_GETGEO,用于获得磁盘的几何信息(geometry,指CHS,即 Cylinder, Head, Sector/Track).
  398.     static int xxx_ioctl(struct inode* inode, struct file* file,\
  399.                              unsigned int cmd, unsigned long arg){
  400.         long size;
  401.         struct hd_geometry geo;
  402.         struct xxx_dev* dev = file->private_data;
  403.  
  404.         switch(cmd){
  405.             case HDIO_GETGEO:
  406.                 size = dev->size * (hardsect_size / KERNEL_SECTOR_SIZE);
  407.                 geo.cylinders = (size & ~0x3f) >> 6;
  408.                 geo.heads = 4;
  409.                 geo.sectors = 16;
  410.                 if(copy_to_user((void __user*)arg, &geo, sizeof(geo)){
  411.                     return -EFAULT;
  412.                 }
  413.                 return 0;
  414.         }
  415.         return -ENOTTY;//未知命令
  416.     }
  417. 4.块设备驱动的I/O请求
  418. ? 使用请求队列
  419. 块设备驱动请求函数的原型为:
  420.     void request(request_queue_t* q);
  421. 这个函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才会调用这个函数.请求函数可以在没有完成请求队列中的所有请求的情况下返回,甚至它一个请求不完成都可以返回.但对大部分设备而言,一般会在请求函数中处理完所有请求后才返回.
  422.     static void xxx_request(request_queue_t* q){
  423.         struct request* req;
  424.  
  425.         //elv_next_request()用于获取队列中第一个未完成的请求
  426.         //end_request()会将请求从请求队列中剥离
  427.         while((req = elv_next_request(q)) != NULL){
  428.             struct xxx_dev* dev = req->rq_disk->private_data;
  429.             if(!blk_fs_request(req)){//如果不是文件系统请求,直接清除,调用end_request().
  430.                 printk(KERN_NOTICE "Skip non-fs request\n");
  431.                 end_request(req, 0);//通知请求处理失败.第二个参数0代表请求失败.
  432.                 continue;
  433.             }
  434.             xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,\
  435.                 rq_data_dir(req));//处理这个请求.
  436.             end_request(req, 1);//通知成功完成这个请求.1,表示请求成功.
  437.         }
  438.     }
  439.     static void xxx_transfer(struct xxx_dev* dev, unsigned long sector,\
  440.         unsigned long nsect, char* buffer, int write){
  441.             unsigned long offset = sector * KERNEL_SECTOR_SIZE;
  442.             unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;
  443.             if((offset + nbytes) > dev->size){
  444.                 printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
  445.                 return ;
  446.             }
  447.             if(write)
  448.                 write_dev(offset, buffer, nbytes);//向设备写nbytes个字节的数据.
  449.             else
  450.                 read_dev(offset, buffer, nbytes);//从设备读取nbytes个字节的数据.
  451.     }
  452. 下面是end_that_request_first()的源码和分析
  453.     //end_request()源码清单
  454.     void end_request(struct request* req, int uptodate){
  455.         //当设备完成一个IO请求的部分或全部扇区传输后,必须告知块设备层.end_that_request_first
  456.         //原型为:int end_that_request_first(struct request* req, int success, int count);
  457.         //此函数高数块设备层,已经完成count各扇区的传送.返回表示所有扇区传送完毕.
  458.         if(!end_that_request_first(req, uptodate, req->hard_cur_sectors)){
  459.             //add_disk_randomness()作用是使用块IO请求的定时来给系统的随机数池贡献熵,它不影响
  460.             //块设备,但仅当磁盘的操作时间是真正随机的时候,才调用它.
  461.             add_disk_randomness(req->rq_disk);
  462.             blkdev_dequeue_request(req);//清除此请求.
  463.             end_that_request_last(req);//通知等待此请求的对象,此请求已经完成
  464.         }
  465.     }
  466. 下面是一个更复杂的请求函数,分别遍历了request,bio,以及bio中的segment
  467.     //请求函数遍历请求,bio和段
  468.     static void xxx_full_request(request_queue_t* q){
  469.         struct request* req;
  470.         int sectors_xferred;
  471.         struct xxx_dev* dev = q->queuedata;
  472.         //XXX 遍历每个请求
  473.         while((req = elv_next_request(q)) != NULL){
  474.             if(!blk_fs_request(req)){
  475.                 printk(KERN_NOTICE "Skip non-fs request\n");
  476.                 end_request(req, 0);
  477.                 continue;
  478.             }
  479.             sectors_xferred = xxx_xfer_reqeust(dev, req);
  480.             if(!end_that_request_first(req, 1, sectors_xferred)){
  481.                 blkdev_dequeue_reqeust(req);
  482.                 end_that_request_last(req);
  483.             }
  484.         }
  485.     }
  486.     //XXX 请求处理
  487.     static int xxx_xfer_request(struct xxx_dev* dev, struct reqeust* req){
  488.         struct bio* bio;
  489.         int nsect = 0;
  490.         //遍历请求中的每个bio
  491.         rq_for_each_bio(bio, req){
  492.             xxx_xfer_bio(dev, bio);
  493.             nsect += bio->bi_size / KERNEL_SECTOR_SIZE;
  494.         }
  495.         return nsect;
  496.     }
  497.     //XXX bio处理
  498.     static int xxx_xfer_bio(struct xxx_dev* dev, struct bio* bio){
  499.         int i;
  500.         struct bio_vec* bvec;
  501.         sector_t sector = bio->bi_sector;
  502.         //遍历每一个segment
  503.         bio_for_each_segment(bvec, bio, i){
  504.             char* buffer = __bio_kmap_atomic(bio, i, KM_USER0);
  505.             xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer,\
  506.                 bio_data_dir(bio) == WRITE);
  507.             sector += bio_cur_sectors(bio);
  508.             __bio_kunmap_atomic(bio, KMUSER0);
  509.         }
  510.         return 0;
  511.     }
  512. ? 不使用请求队
  513. 对于机械的磁盘设备而言,请求队列有助于提高系统性能.但对于如SD卡,RAM盘等可随机访问的块设备,请求队列无法获益.对于这些设备,块层支持"无队列"的操作模式,驱动为此必须提供一个"制造请求"函数(注意:这不是请求函数哦),"制造请求"函数的原型为:
  514. typedef int (make_request_fn) (request_queue_t* q, struct bio* bio);
  515. 此函数的第一个参数,是一个"请求队列",但实际并不包含任何请求.所以主要参数是bio,它表示一个或多个要传送的缓冲区.此函数或直接进行传输,或将请求重定向给其他设备.在处理完成之后,应使用bio_endio()通知处理结束.bio_endio()原型如下:
  516.     void bio_endio(struct bio* bio, unsigned int byetes, int error);
  517. bytes 是已经传送的字节数(注意:bytes≤bio->bi_size),这个函数同时更新了bio的当前缓冲区指针.当设备进一步处理bio后,驱动 应再次调用bio_endio(),如不能完成请求,将错误码赋给error参数,并在函数中得以处理.此函数无论处理IO成功与否都返回0,如果返回非 零值,则bio将再次被提交:
  518.     static int xxx_make_request(request_queue_t* q, struct bio* bio){
  519.         struct xxx_dev* dev = q->queuedata;
  520.         int status = xxx_xfer_bio(dev, bio);//处理bio
  521.         bio_endio(bio, bio->bi_size, status);//报告结束
  522.         return 0;
  523.     }
  524. 说明:这里要指出,如果是无队列的IO请求处理,其加载模块应使用<<代码1:使用blk_alloc_queue函数完成块设备驱动的模块加载模板>>,否则应使用<<代码2:使用blk_init_queue函数完成块设备驱动的模块加载模板>>.

阅读(1205) | 评论(0) | 转发(0) |
0

上一篇:BIOS解析

下一篇:字符设备驱动 架构分析

给主人留下些什么吧!~~