add_disk 函数中,有个重要的流程就是blk_register_queue。这是这篇博文的重点。为了防止出现版权或者抄袭的纠纷,本人再次声明:
本文主要是参考linux 那些事儿,本文不过是学习笔记性质的博文,荣耀属于linux那些事儿的作者。为甚么还要写这篇博文呢。敦促自己学习,驱动自己深入理解,Mark自己的成长足迹,同时想前辈大牛致敬。OK,回正题。
- int blk_register_queue(struct gendisk *disk)
-
{
-
int ret;
-
-
struct request_queue *q = disk->queue;
-
-
if (WARN_ON(!q))
-
return -ENXIO;
-
-
if (!q->request_fn)
-
return 0;
-
-
ret = kobject_add(&q->kobj, kobject_get(&disk->dev.kobj),
-
"%s", "queue");
-
if (ret < 0)
-
return ret;
-
-
kobject_uevent(&q->kobj, KOBJ_ADD);
-
-
ret = elv_register_queue(q);
-
if (ret) {
-
kobject_uevent(&q->kobj, KOBJ_REMOVE);
-
kobject_del(&q->kobj);
-
return ret;
-
}
-
-
return 0;
-
}
这个函数非常简单,基本上就是设备管理的那一套东西。但是很不幸的是出现了一个看下register_queue类型的变量。再看下elv_register_queue:
- int elv_register_queue(struct request_queue *q)
-
{
-
elevator_t *e = q->elevator;
-
int error;
-
-
error = kobject_add(&e->kobj, &q->kobj, "%s", "iosched");
-
if (!error) {
-
struct elv_fs_entry *attr = e->elevator_type->elevator_attrs;
-
if (attr) {
-
while (attr->attr.name) {
-
if (sysfs_create_file(&e->kobj, &attr->attr))
-
break;
-
attr++;
-
}
-
}
-
kobject_uevent(&e->kobj, KOBJ_ADD);
-
}
-
return error;
-
}
我们看到,前面提到的request_queue类型的变量queue中存在一个elvator_t类型的成员elevator。queue他来自何方呢,elevator我们知道是电梯的意思,为什么queue有一个成员变量要电梯这么变态的名字呢?我们再也不能装聋作哑了,我们要勇敢地解决queue的身世之谜?
首先,我们在gendisk类型中disk变量中看到了queue,那么gendisk类型中的queue是哪里来的呢?
回想前面提到的sd_probe函数中,有这么一句,但是我们无情的忽略了这条语句:
- struct scsi_disk *sdkp;
- ...
- gd->queue = sdkp->device->request_queue;
scsi_disk类型中存在一个scsi_device类型的变量device,device中存在request_queue。那么现在问题就变成了,device中的request_queue又是从哪里冒出来的呢。
- struct scsi_disk {
-
struct scsi_driver *driver; /* always &sd_template */
-
struct scsi_device *device;
-
struct device dev;
-
struct gendisk *disk;
-
unsigned int openers; /* protected by BKL for now, yuck */
-
sector_t capacity; /* size in 512-byte sectors */
-
u32 index;
-
u8 media_present;
-
u8 write_prot;
-
unsigned previous_state : 1;
-
unsigned WCE : 1; /* state of disk WCE bit */
-
unsigned RCD : 1; /* state of disk RCD bit, unused */
-
unsigned DPOFUA : 1; /* state of disk DPOFUA bit */
-
};
scsi总线扫描的时候,每当探测到一个设备的时候,就会调用scsi_alloc_sdev(),这个函数,解开了我们寻找周杰伦的大幕,我们就沿着这个函数,顺藤摸瓜,顺手牵羊的找出request_queue的前世今生。
scsi_alloc_sdev函数中存在这么一句:调用了scsi_alloc_queue获取request_queue
- struct scsi_device *sdev;
-
...
-
-
sdev->request_queue = scsi_alloc_queue(sdev);
OK ,scsi_alloc_queue函数又调用了__scsi_alloc_queue函数,如下:
- struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
-
{
- struct request_queue *q;
- q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
-
...
- return q;
-
}
长话短说吧 :
__scsi_alloc_queue -----> blk_init_queue----->blk_init_queue_node,这个函数我们需要进去看下:
- struct request_queue *
-
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
-
{
-
struct request_queue *q = blk_alloc_queue_node(GFP_KERNEL, node_id);
-
-
if (!q)
-
return NULL;
-
-
q->node = node_id;
-
if (blk_init_free_list(q)) {
-
kmem_cache_free(blk_requestq_cachep, q);
-
return NULL;
-
}
-
-
/*
-
* if caller didn't supply a lock, they get per-queue locking with
-
* our embedded lock
-
*/
-
if (!lock) {
-
spin_lock_init(&q->__queue_lock);
-
lock = &q->__queue_lock;
-
}
-
-
q->request_fn = rfn;
-
q->prep_rq_fn = NULL;
-
q->unplug_fn = generic_unplug_device;
-
q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);
-
q->queue_lock = lock;
-
-
blk_queue_segment_boundary(q, 0xffffffff);
-
-
blk_queue_make_request(q, __make_request);
-
blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
-
-
blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
-
blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
-
-
q->sg_reserved_size = INT_MAX;
-
-
/*
-
* all done
-
*/
-
if (!elevator_init(q, NULL)) {
-
blk_queue_congestion_threshold(q);
-
return q;
-
}
-
-
blk_put_queue(q);
-
return NULL;
-
}
我们重点关注绿色的两条语句:
blk_alloc_queue_node主要工作就是分配了一个request_queue类型的结构,毕竟一路下来全是指针,没有干货,干货request_queue就是这个blk_alloc_queue_node函数分配的。
另一个需要关注的是elevator_init,我们最初的疑惑,电梯从哪里来的,终于可以初见端倪了。
- int elevator_init(struct request_queue *q, char *name)
-
{
-
struct elevator_type *e = NULL;
-
struct elevator_queue *eq;
-
int ret = 0;
-
void *data;
-
-
INIT_LIST_HEAD(&q->queue_head);
-
q->last_merge = NULL;
-
q->end_sector = 0;
-
q->boundary_rq = NULL;
-
-
if (name) {
-
e = elevator_get(name);
-
if (!e)
-
return -EINVAL;
-
}
-
-
if (!e && *chosen_elevator) {
-
e = elevator_get(chosen_elevator);
-
if (!e)
-
printk(KERN_ERR "I/O scheduler %s not found\n",
-
chosen_elevator);
-
}
-
-
if (!e) {
-
e = elevator_get(CONFIG_DEFAULT_IOSCHED);
-
if (!e) {
-
printk(KERN_ERR
-
"Default I/O scheduler not found. " \
-
"Using noop.\n");
-
e = elevator_get("noop");
-
}
-
}
-
-
eq = elevator_alloc(q, e);
-
if (!eq)
-
return -ENOMEM;
-
-
data = elevator_init_queue(q, eq);
-
if (!data) {
-
kobject_put(&eq->kobj);
-
return -ENOMEM;
-
}
-
-
elevator_attach(q, eq, data);
-
return ret;
-
}
首选函数入参指定的那款“电梯”,如果入参没指定,chosen_elevator指定了,那么就通过elevator_get去寻找选定的那款电梯,chosen_elevator那款电梯获取不到,只能在退而求其次,选择
CONFIG_DEFAULT_IOSCHED类型的“电梯”,如果这款也获取不到,那就只能认命了,选noop类型的电梯。
在这里可以讲述一下所谓的电梯是什么东东。磁盘是一种物理设备,需要磁头寻道,寻道时间占磁盘读写请求需要时间的一大部分,如果让磁盘高效的工作呢。linux那些事的作者以欧洲足球为例,我是足球盲,是篮球迷,我就以CBA为例。对于CBA的球队而言,有很多之球队,如果你是一直球队的球员,你肯定不希望,今天打完广东宏远,第二天就飞到东北打辽宁,打完辽宁再打新疆,打完新疆再打福建,时间全耗在路上了。我们可能是愿意打完辽宁打北京,打完北京打山东,打完山东打江苏,打完江苏打上海,打完上海打浙江,打完浙江打福建。。。。为啥?旅途顺畅啊,从北到南一路打下来了。磁盘也是同样的道理。为了让磁盘高效的工作,我们需要调度,按照先到先服务的策略,那么就会出现打完辽宁打广东,打完广东打新疆,打完新疆打吉林,打完吉林打福建的坑爹情况。这个调度,被形象的比喻成电梯调度。想想电梯的调度,我们不可能按照先按先运送的策略服务。
elevator_init函数还没完,我们看到elevator_init调用了elevator_alloc函数,
我们进去看下:
- static elevator_t *elevator_alloc(struct request_queue *q,
-
struct elevator_type *e)
-
{
-
elevator_t *eq;
-
int i;
-
-
eq = kmalloc_node(sizeof(elevator_t), GFP_KERNEL | __GFP_ZERO, q->node);
-
if (unlikely(!eq))
-
goto err;
-
-
eq->ops = &e->ops;
-
eq->elevator_type = e;
-
kobject_init(&eq->kobj, &elv_ktype);
-
mutex_init(&eq->sysfs_lock);
-
-
eq->hash = kmalloc_node(sizeof(struct hlist_head) * ELV_HASH_ENTRIES,
-
GFP_KERNEL, q->node);
-
if (!eq->hash)
-
goto err;
-
-
for (i = 0; i < ELV_HASH_ENTRIES; i++)
-
INIT_HLIST_HEAD(&eq->hash[i]);
-
-
return eq;
-
err:
-
kfree(eq);
-
elevator_put(e);
-
return NULL;
-
}
这个函数其实是分配了一个elevator_queue类型的变量eq。 elevator_queue结构描述了该块设备驱动使用何种IO调度算法。通常每个物理块设备都自己维护一个请求队列,每个请求队列上单独执行I/O调度。在请求对列中有一个字段elevator,它的类型指向sturct elevator_queue的指针。elevator_queue结构如下:
- struct elevator_queue
-
{
-
struct elevator_ops *ops;
-
void *elevator_data;
-
struct kobject kobj;
-
struct elevator_type *elevator_type;
-
struct mutex sysfs_lock;
-
struct hlist_head *hash;
-
};
字段ops定义了调度算法的所有可能的操作:链接和断开elevator,增加和合并队列中的请求,从队列中删除请求,获得队列中下一个待处理的请求等等。
字段是elevator_type,它是指向struct elevator_type类型的指针,它描述了块设备所使用io调度算法。
字段hash包含了处理请求队列所需的所有信息,通过这个哈希表可以快速索引我们需要的某个request。
字段elevator_data用于描述IO调度程序用来处理请求的附加数据结构,如果使用Deadline算法,elevator_data指向struct deadline_data结构。
阅读(6025) | 评论(0) | 转发(1) |