必赢网址-必赢437-www437com

热门关键词: 必赢网址,必赢437,www437com

Linux设备驱动

 

块设备驱动注册与注销

块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:
int register_blkdev(unsigned int major, const char *name);

major 参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被显示。 如果major为0,内核会自动分配一个新的主设备号register_blkdev()函数的返回值就是这个主设备号。如果返回1个负值,表明发生了一个错误。

与register_blkdev()对应的注销函数是unregister_blkdev(),其原型为:
int unregister_blkdev(unsigned int major, const char *name); 这里,传递给register_blkdev()的参数必须与传递给register_blkdev()的参数匹配,否则这个函数返回-EINVAL。

上回最后面介绍了相关数据结构,下面再详细介绍

前面, 我们已经讨论了内核所作的在队列中优化请求顺序的工作; 这个工作包括排列请求和, 或许, 甚至延迟队列来允许一个预期的请求到达. 这些技术在处理一个真正的旋转的磁盘驱动器时有助于系统的性能. 但是, 许多面向块的设备, 例如闪存阵列, 用于数字相机的存储卡的读取器、u盘等, 并且 RAM 盘真正地有随机存取的性能, 包含从高级的请求队列逻辑中获益. 其他设备, 例如软件 RAID 阵列或者被逻辑卷管理者创建的虚拟磁盘, 没有这个块层的请求队列被优化的性能特征. 对于这类设备, 它最好直接从块层接收请求, 并且根本不去烦请求队列.

块设备的请求队列操作

标准的请求处理程序能排序请求,并合并相邻的请求,如果一个块设备希望使用标准的请求处理程序,那它必须调用函数blk_init_queue来初始化请求队列。当处理在队列上的请求时,必须持有队列自旋锁。初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

该函数的第1个参数是请求处理函数的指针,第2个参数是控制访问队列权限的自旋锁,这个函数会发生内存分配的行为,故它可能会失败,函数调用成
功时,它返回指向初始化请求队列的指针,否则,返回NULL。这个函数一般在块设备驱动的模块加载函数中调用。清除请求队列
void blk_cleanup_queue(request_queue_t * q);

这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。

 

提取请求
struct request *elv_next_request(request_queue_t *queue); 
上述函数用于返回下一个要处理的请求(由 I/O 调度器决定),如果没有请求则返回NULL。

去除请求
void blkdev_dequeue_request(struct request *req); 
上述函数从队列中去除1个请求。如果驱动中同时从同一个队列中操作了多个请求,它必须以这样的方式将它们从队列中去除。

 

分配“请求队列”
request_queue_t *blk_alloc_queue(int gfp_mask);
对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用上述函数分配1个“请求队列”,并使用如下函数来绑定“请求队列”和“制造请求”函数。 void blk_queue_make_request(request_queue_t * q, 
make_request_fn * mfn);

void blk_queue_hardsect_size(request_queue_t *queue, 
unsigned short max); 
该函数用于告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对界。但是,内核块设备层和驱动之间的通信还是以512字节扇区为单位进行。

 

步骤:

在块设备驱动的模块加载函数中通常需要完成如下工作:
① 分配、初始化请求队列,绑定请求队列和请求函数。
② 分配、初始化gendisk,给gendisk的major、fops、queue等成
员赋值,最后添加gendisk。
③ 注册块设备驱动。
在块设备驱动的模块卸载函数中通常需要与模块加载函数相反的工作:
① 清除请求队列。
② 删除gendisk和对gendisk的引用。
③ 删除对块设备的引用,注销块设备驱动。

总结:

块设备的I/O操作方式与字符设备存在较大的不同,因而引入了
request_queue、request、bio等一系列数据结构。在整个块设备的I/O操作中,贯穿于始终的就是“请求”,字符设备的I/O操作则是直接进行不绕弯,
块设备的I/O操作会排队和整合。

驱动的任务是处理请求,对请求的排队和整合由I/O调度算法解决,因此,块设备驱动的核心就是请求处理函数或“制造请求”函数。

尽管在块设备驱动中仍然存在block_device_operations结构体及其成员函数,但其不再包含读写一类的成员函数,而只是包含打开、释放及I/O控制等
与具体读写无关的函数。块设备驱动的结构相当复杂的,但幸运的是,块设备不像字符设备那么包罗万象,它通常就是存储设备,而且驱动的主体已经
由Linux内核提供,针对一个特定的硬件系统,驱动工程师所涉及到的工作往往只是编写少量的与硬件直接交互的代码。

[cpp] view plain copy

 

 print?

  1. #include <linux/init.h>    
  2. #include <linux/module.h>    
  3. #include <linux/kernel.h>    
  4. #include <linux/fs.h>  
  5. #include <asm/uaccess.h>  
  6. #include <linux/spinlock.h>  
  7. #include <linux/sched.h>  
  8. #include <linux/types.h>  
  9. #include <linux/fcntl.h>  
  10. #include <linux/hdreg.h>  
  11. #include <linux/genhd.h>  
  12. #include <linux/blkdev.h>  
  13.   
  14. #define MAXBUF 1024   
  15.   
  16.   
  17. #define BLK_MAJOR 253  
  18.   
  19. char blk_dev_name[]="blk_dev";  
  20. static char flash[1024*16];  
  21.   
  22.   
  23. int major;  
  24. spinlock_t lock;  
  25. struct gendisk *gd;  
  26.   
  27.   
  28.   
  29. /*块设备数据传输*/  
  30. static void blk_transfer(unsigned long sector, unsigned long nsect, char *buffer, int write)  
  31. {  
  32.     int read = !write;  
  33.     if(read)  
  34.     {  
  35.         memcpy(buffer, flash+sector*512, nsect*512);  
  36.     }  
  37.     else  
  38.     {  
  39.         memcpy(flash+sector*512, buffer, nsect*512);  
  40.     }  
  41. }  
  42.   
  43. /*块设备请求处理函数*/  
  44. static void blk_request_func(struct request_queue *q)  
  45. {  
  46.     struct request *req;  
  47.     while((req = elv_next_request(q)) != NULL)    
  48.     {  
  49.         if(!blk_fs_request(req))  
  50.         {  
  51.             end_request(req, 0);  
  52.             continue;  
  53.         }  
  54.           
  55.         blk_transfer(req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req));  
  56.         /*rq_data_dir从request获得数据传送的方向*/  
  57.         /*req->current_nr_sectors 在当前段中将完成的扇区数*/  
  58.         /*req->sector 将提交的下一个扇区*/  
  59.         end_request(req, 1);  
  60.     }  
  61. }  
  62.   
  63. /*strcut block_device_operations*/  
  64. static  int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg)  
  65. {  
  66.        return -ENOTTY;  
  67. }  
  68.   
  69. static int blk_open (struct block_device *dev , fmode_t no)  
  70. {  
  71.     printk("blk mount succeedn");  
  72.     return 0;  
  73. }  
  74. static int blk_release(struct gendisk *gd , fmode_t no)  
  75. {  
  76.     printk("blk umount succeedn");  
  77.     return 0;  
  78. }  
  79. struct block_device_operations blk_ops=  
  80. {  
  81.     .owner = THIS_MODULE,  
  82.     .open = blk_open,  
  83.     .release = blk_release,  
  84.     .ioctl = blk_ioctl,  
  85. };  
  86.   
  87. //-----------------------------------------------  
  88.   
  89. static int __init block_module_init(void)  
  90. {  
  91.       
  92.       
  93.     if(!register_blkdev(BLK_MAJOR, blk_dev_name)) //注册一个块设备  
  94.     {  
  95.         major = BLK_MAJOR;    
  96.         printk("regiser blk dev succeedn");  
  97.     }  
  98.     else  
  99.     {  
  100.         return -EBUSY;  
  101.     }  
  102.     gd = alloc_disk(1);  //分配一个gendisk,分区是一个  
  103.     spin_lock_init(&lock); //初始化一个自旋锁  
  104.     gd->major = major;  
  105.     gd->first_minor = 0;   //第一个次设备号  
  106.     gd->fops = &blk_ops;   //关联操作函数  
  107.   
  108.     gd->queue = blk_init_queue(blk_request_func, &lock); //初始化请求队列并关联到gendisk  
  109.   
  110.     snprintf(gd->disk_name, 32, "blk%c", 'a');    
  111.     blk_queue_hardsect_size(gd->queue, 512);  //设置扇区大小512字节  
  112.     set_capacity(gd, 32);  //设置块设备大小 512*32=16K  
  113.     add_disk(gd);  
  114.     printk("gendisk init success!n");  
  115.     return 0;  
  116. }  
  117. static void __exit block_module_exit(void)  
  118. {  
  119.     blk_cleanup_queue(gd->queue);  
  120.     del_gendisk(gd);   
  121.     unregister_blkdev(BLK_MAJOR, blk_dev_name);  
  122.     printk("block module exit succeed!n");  
  123. }  
  124.   
  125. module_init(block_module_init);  
  126. module_exit(block_module_exit);  
  127.   
  128. MODULE_LICENSE("GPL");  
  129. MODULE_AUTHOR("gec");  
  130. //------------------------------------------------------------------------------    

块设备对象结构 block_device

内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区。如果该结构代表一个分区,则其成员bd_part指向设备的分区结构。如果该结构代表设备,则其成员bd_disk指向设备的通用硬盘结构gendisk

当用户打开块设备文件时,内核创建结构block_device实例,设备驱动程序还将创建结构gendisk实例,分配请求队列并注册结构block_device实例。

块设备对象结构block_device列出如下(在include/Linux/fs.h中)

[cpp] view plain copy

 

 print?

  1. struct block_device {  
  2. dev_t bd_dev;  /* not a kdev_t - it's a search key */  
  3. struct inode * bd_inode; /* 分区节点 */  
  4. struct super_block * bd_super;  
  5. int bd_openers;  
  6. struct mutex bd_mutex;/* open/close mutex 打开与关闭的互斥量*/  
  7. struct semaphore bd_mount_sem;    /*挂载操作信号量*/   
  8. struct list_head bd_inodes;  
  9. void * bd_holder;  
  10. int bd_holders;  
  11. #ifdef CONFIG_SYSFS  
  12. struct list_head bd_holder_list;  
  13. #endif  
  14. struct block_device * bd_contains;  
  15. unsigned bd_block_size;     /*分区块大小*/  
  16. struct hd_struct * bd_part;  
  17. unsigned bd_part_count;   /*打开次数*/  
  18. int bd_invalidated;  
  19. struct gendisk * bd_disk; /*设备为硬盘时,指向通用硬盘结构*/  
  20. struct list_head bd_list;  
  21. struct backing_dev_info *bd_inode_backing_dev_info;  
  22. unsigned long bd_private;  
  23. /* The counter of freeze processes */  
  24. int bd_fsfreeze_count;  
  25. /* Mutex for freeze */  
  26. struct mutex bd_fsfreeze_mutex;  
  27. };  

这时候我们就不用内核提供的IO调度器来优化排列和合并请求,不用内核的__make_request 帮我们处理bio,而是我们自己处理bio

通用硬盘结构 gendisk

结构体gendisk代表了一个通用硬盘(generic hard disk)对象,它存储了一个硬盘的信息,包括请求队列、分区链表和块设备操作函数集等。块设备驱动程序分配结构gendisk实例,装载分区表,分配请求队列并填充结构的其他域。

支持分区的块驱动程序必须包含 <linux/genhd.h> 头文件,并声明一个结构gendisk,内核还维护该结构实例的一个全局链表gendisk_head,通过函数add_gendisk、del_gendisk和get_gendisk维护该链表。

结构gendisk列出如下(在include/linux/genhd.h中):

[cpp] view plain copy

 

 print?

  1. struct gendisk {  
  2.     int major;            /* 驱动程序的主设备号 */  
  3.     int first_minor;       /*第一个次设备号*/  
  4.     int minors;          /*次设备号的最大数量,没有分区的设备,此值为1 */  
  5.     char disk_name[32];  /* 主设备号驱动程序的名字*/  
  6.     struct hd_struct **part;   /* 分区列表,由次设备号排序 */  
  7.     struct block_device_operations *fops;  /*块设备操作函数集*/  
  8.     struct request_queue *queue;         /*请求队列*/  
  9.     struct blk_scsi_cmd_filter cmd_filter;  
  10.     void *private_data;                 /*私有数据*/  
  11.     sector_t capacity;     /* 函数set_capacity设置的容量,以扇区为单位*/  
  12.     int flags;                 /*设置驱动器状态的标志,如:可移动介质为 
  13. GENHD_FL_REMOVABLE*/  
  14.     struct device dev;                 /*从设备驱动模型基类结构device继承*/  
  15.     struct kobject *holder_dir;  
  16.     struct kobject *slave_dir;  
  17.  struct timer_rand_state *random;  
  18.     int policy;   
  19.     atomic_t sync_io;        /* RAID */  
  20.     unsigned long stamp;  
  21.     int in_flight;  
  22. #ifdef  CONFIG_SMP  
  23.     struct disk_stats *dkstats;    
  24. #else  
  25. /*硬盘统计信息,如:读或写的扇区数、融合的扇区数、在请求队列的时间等*/  
  26.     struct disk_stats dkstats;  
  27. #endif  
  28.     struct work_struct async_notify;  
  29. #ifdef  CONFIG_BLK_DEV_INTEGRITY  
  30.     struct blk_integrity *integrity;   /*用于数据完整性扩展*/  
  31. #endif  
  32. };  

Linux内核提供了一组函数来操作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容量 void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备 无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇区也很常见, 比如,很多CD-ROM盘的扇区都是2K大小。不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512字节为单位。因此,set_capacity()函数也以512字节为单位。

分区结构hd_struct代表了一个分区对象,它存储了一个硬盘的一个分区的信息,驱动程序初始化时,从硬盘的分区表中提取分区信息,存放在分区结构实例中。

本文由必赢网址发布于机械设备,转载请注明出处:Linux设备驱动

您可能还会对下面的文章感兴趣: