块设备驱动(2)

结构gendisk是通用硬盘的描述,其结构体如下:
struct gendisk {
 int major;//主设备号
 int first_minor;//第一个词设备号
 int minors;
//描述被磁盘使用的设备号的成员. 至少, 一个驱动器必须使用最少一个次编号. 如果你的驱动会是可分
//区的, 但是(并且大部分应当是), 你要分配一个次编号给每个可能的分区. 次编号的一个普通的值是
//16, 它允许"全磁盘"设备盒 15 个分区. 一些磁盘驱动使用 64 个次编号给每个设备.
 char disk_name[32];//主设备驱动的名字
 struct hd_struct **part;//以次设备号排序的分区表
 struct block_device_operations *fops;//块设备操作集
 struct request_queue *queue;//请求队列
 void *private_data;
 sector_t capacity;

 int flags;
//一套标志(很少使用), 描述驱动器的状态. 如果你的设备有可移出的介质, 你应当设置
//GENHD_FL_REMOVABLE. CD-ROM 驱动器可设置 GENHD_FL_CD. 如果, 由于某些原因, 你不需要分区信息
//出现在 /proc/partitions, 设置 GENHD_FL_SUPPRESS_PARTITIONS_INFO.
 char devfs_name[64]; /* devfs crap */
 int number; /* more of the same */
 struct device *driverfs_dev;//设备结构
 struct kobject kobj;

 struct timer_rand_state *random;
 int policy;//策略

 atomic_t sync_io; /* RAID */
 unsigned long stamp, stamp_idle;
 int in_flight;
#ifdef CONFIG_SMP
 struct disk_stats *dkstats;
#else
 struct disk_stats dkstats;
#endif
};

结构hd_struct是硬盘的分区大小结构,如下所示:
struct hd_struct {
 sector_t start_sect;//开始扇区
 sector_t nr_sects;//扇区数
 struct kobject kobj;//内核对象
 unsigned reads, read_sectors, writes, write_sectors;
 int policy, partno;//策略 分区号
};

结构partition是分区的物理结构描述,其如下:
struct partition {
       unsigned char boot_ind;
       unsigned char head;//开始磁头号
       unsigned char sector;//开始扇区号
       unsigned char cyl;//开始柱面号
       unsigned char sys_ind;//分区类型
       unsigned char end_head;//尾磁头号
       unsigned char end_sector;//尾扇区号
       unsigned char end_cyl;//尾柱面号
       __le32 start_sect;//从0开始的扇区计数
       __le32 nr_sects;//分区中的扇区数
} __attribute__((packed));

*major_names数组是块设备号和名称的保存数组。
static struct blk_major_name {
       struct blk_major_name *next;
       int major;
       char name[16];
} *major_names[MAX_PROBE_HASH]

所有的块设备组成了一个子系统,其声明如下:
static struct subsystem block_subsys;

函数device_init注册块设备子系统,初始化内核对像映射结构,初始化块设备的工作队列,申请块设备的请求结构及请求队列结构的slab缓存。
static int __init genhd_device_init(void)
{
         bdev_map = kobj_map_init(base_probe, &block_subsys_sem);
         //给内核映射对象设备基本探测函数
         blk_dev_init();//初始化设备的工作队列
         subsystem_register(&block_subsys);//注册块设备子系统
         return 0;
}

下列函数创建块设备工作队列和请求结构及请求队列结构的缓存。
int __init blk_dev_init(void)
{
        kblockd_workqueue = create_workqueue("kblockd");
//创建名为kblockd的工作队列,对应每个cpu而言将相应有后台线程调度
       if (!kblockd_workqueue)
          panic("Failed to create kblockd/n");

       //建立块设备请求结构缓存
       request_cachep = kmem_cache_create("blkdev_requests",
       sizeof(struct request), 0, SLAB_PANIC, NULL, NULL);
       //建立块设备请求队列结构缓存
       requestq_cachep = kmem_cache_create("blkdev_queue",
       sizeof(request_queue_t), 0, SLAB_PANIC, NULL, NULL);
       //建立操作块设备进程的缓存
       iocontext_cachep = kmem_cache_create("blkdev_ioc",
       sizeof(struct io_context), 0, SLAB_PANIC, NULL, NULL);

       blk_max_low_pfn = max_low_pfn;
       blk_max_pfn = max_pfn;

       return 0;
}

下列为块设备注册函数,将相应设备的主设备号和名称存放到major_names数组中,
int register_blkdev(unsigned int major, const char *name)
{
       struct blk_major_name **n, *p;
       int index, ret = 0;

       down(&block_subsys_sem);

       if (major == 0) {//若未分配主设备号则分配一个
       for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
          if (major_names[index] == NULL)
          break;
       }

       if (index == 0) {//失败
             printk("register_blkdev: failed to get major for %s/n",name);
             ret = -EBUSY;
             goto out;
           }
       major = index;//将索引值作为设备号
       ret = major;
       }

       p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);//分配一个成员
       if (p == NULL) {
            ret = -ENOMEM;
            goto out;
       }

       p->major = major;
       strlcpy(p->name, name, sizeof(p->name));
       p->next = NULL;
       index = major_to_index(major);//将传递的参数作为该成员值

       for (n = &major_names[index]; *n; n = &(*n)->next) {//将新成员添加到major_names
       if ((*n)->major == major)
            break;
       }
       if (!*n)
             *n = p;
      else
             ret = -EBUSY;

      if (ret < 0) {
             printk("register_blkdev: cannot get major %d for %s/n",major, name);
             kfree(p);
      }
out:
      up(&block_subsys_sem);
      return ret;
}
有一些函数用于对通用硬盘结构体的操作函数介绍如下:
1.注册gendisk函数add_disk
void add_disk(struct gendisk *disk)
{
       disk->flags |= GENHD_FL_UP;
       blk_register_region(MKDEV(disk->major, disk->first_minor),
       disk->minors, NULL, exact_match, exact_lock, disk);//注册设备号
       register_disk(disk);//将通用硬盘注册
       blk_register_queue(disk);//注册请求结构
}

2.释放gendisk函数del_gendisk,很复杂不分析。
3.分配gendisk函数
struct gendisk *alloc_disk(int minors)
{
       return alloc_disk_node(minors, -1);
}

struct gendisk *alloc_disk_node(int minors, int node_id)
{
       struct gendisk *disk;

       disk = kmalloc_node(sizeof(struct gendisk), GFP_KERNEL, node_id);//分配gendisk结构体
       if (disk) {
            memset(disk, 0, sizeof(struct gendisk));//成员0
            if (!init_disk_stats(disk)) {
               kfree(disk);
               return NULL;
        }
       if (minors > 1) {//若有多个分区时
              int size = (minors - 1) * sizeof(struct hd_struct *);//所有分区占有的字节数
              disk->part = kmalloc_node(size, GFP_KERNEL, node_id);//为所有分区建立分区大小结构
              if (!disk->part) {
                 kfree(disk);
              return NULL;
       }
       memset(disk->part, 0, size);
     }
       disk->minors = minors;
       kobj_set_kset_s(disk,block_subsys);//将其添加到块子系统中
       kobject_init(&disk->kobj);//初始化内核对象
       rand_initialize_disk(disk);
  }
  return disk;
}

请求及请求队列
块层make_request函数建立起来请求结构实例后,把它放在一个请求队列中,然后被传递到底层的驱动程序,触发驱动程序的request_fn函数来处理请求。
struct request {
           struct list_head queuelist;//队列链表
           unsigned long flags;//标志位

           sector_t sector;//将提交的下一个扇区
           unsigned long nr_sectors;//在提交申请中的所有扇区数
           unsigned int current_nr_sectors;//当前bio中的片段中的扇区数

           //下列变量是上面存放到块层的副本,驱动层并不操作
           sector_t hard_sector;//将完成的下个扇区
           unsigned long hard_nr_sector;//剩余扇区
           unsigned int hard_cur_sectors;//当前片段中剩余扇区

           struct bio *bio;//提交请求的bio链表
           struct bio *biotail;//链表尾
           void *elevator_private;//调度算法
           unsigned short ioprio;

           int rq_status;//请求状态
           struct gendisk *rq_disk;//该请求所操作的通用硬盘
           int errors;
           unsigned long start_time;
           //进过IO调度后物理地址合并被执行后,碎片链表DMA的个数,即新的物理片段数;其对应物理地址
           unsigned short nr_phys_segments;
           //在DMA影射后,驱动程序所使用的虚拟地址的碎片链表个数
           unsigned short nr_hw_segments;

           int tag;
           char *buffer;//相对应的块缓冲区

           int ref_count;//引用计数
           request_queue_t *q;//挂载的请求队列
           struct request_list *rl;//请求的空闲链表

           struct completion *waiting;//完成量用于唤醒进程
           void *special;

           unsigned int cmd_len;//命令长度
           unsigned char cmd[BLK_MAX_CDB];//命令块数

           unsigned int data_len;//数据长度
           void *data;//数据

           unsigned int sense_len;
           void *sense;

           unsigned int timeout;
           struct request_pm_state *pm;//电源管理请求
           rq_end_io_fn *end_io;//io结束时调用函数
           void *end_io_data;
};

其中request_list是请求空闲链表,用于将读或写的操作进程挂载到读写等待队列头链表上待到合适时机被唤醒。
struct request_list {
           int count[2];
           int starved[2];
           mempool_t *rq_pool;//内存池
           wait_queue_head_t wait[2];//读写任务等待队列
           wait_queue_head_t drain;
};

请求队列用于操作一系列的申请,其结构体如下:
struct request_queue
{
           struct list_head queue_head;//申请链表首地址
           struct request *last_merge;
           elevator_t *elevator;//调度器结构地址

           struct request_list rq;//请求空闲链表

           request_fn_proc *request_fn;//指向块设备的底层操作函数
           merge_request_fn *back_merge_fn;//向后合并请求函数
           merge_request_fn *front_merge_fn;//向前合并请求函数
           merge_requests_fn *merge_requests_fn;//合并请求函数
           make_request_fn *make_request_fn;//生成请求函数
           prep_rq_fn *prep_rq_fn;//准备请求函数
           unplug_fn *unplug_fn;//去掉队列堵塞函数
           merge_bvec_fn *merge_bvec_fn;//合并bvec函数
           activity_fn *activity_fn;
           issue_flush_fn *issue_flush_fn;
           prepare_flush_fn *prepare_flush_fn;
           end_flush_fn *end_flush_fn;
           //自动拔去状态
           struct timer_list unplug_timer;//去掉堵塞定时器
           int unplug_thresh;//去掉堵塞门限,即有多少个请求去掉堵塞
           unsigned long unplug_delay;//去掉堵塞延时时间
           struct work_struct unplug_work;//去掉堵塞工作队列
           struct backing_dev_info backing_dev_info;
           //请求队列拥有者使用下面2个数据
           void *queuedata;
           void *activity_data;
           //弹性页面
           unsigned long bounce_pfn;//弹性页面页码号
           unsigned int bounce_gfp;

           unsigned long queue_flags;//队列标志,有调度有关的特性
           spinlock_t __queue_lock;
           spinlock_t *queue_lock;

           struct kobject kobj;

           unsigned long nr_requests;//最大挂载申请数
           unsigned int nr_congestion_on;//打开堵塞
           unsigned int nr_congestion_off;//关闭堵塞
           unsigned int nr_batching;

           unsigned short max_sectors;//能处理最大尺寸
           unsigned short max_hw_sectors;//上面备份到块层
           unsigned short max_phys_segments;
           //在物理地址看,一个请求可以处理的最大物理片段数
           unsigned short max_hw_segments;
           //一个请求可以处理的DMA最大片段数
           unsigned short hardsect_size;//扇区大小
           unsigned int max_segment_size;//一个segment片段最大尺寸

           unsigned long seg_boundary_mask;//片段边界掩码值
           unsigned int dma_alignment;//dma对其数

           struct blk_queue_tag *queue_tags;//队列标签

           atomic_t refcnt;

           unsigned int in_flight;

           unsigned int sg_timeout;
           unsigned int sg_reserved_size;
           int node;

           struct list_head drain_list;

           struct request *flush_rq;
           unsigned char ordered;
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值