分类: LINUX
2014-08-20 23:58:19
原文地址:Linux块设备子系统的初始化 作者:tq08g2z
Linux块设备子系统的初始化
---------------------------------------------------------------------
include/linux/fs.h
647
struct block_device {
648
dev_t bd_dev;
/* not a kdev_t - it's a search key */
649 struct inode * bd_inode; /* will die */
650 struct super_block * bd_super;
651 int bd_openers;
652
struct mutex bd_mutex; /* open/close mutex */
653 struct list_head bd_inodes;
654 void * bd_holder;
655 int bd_holders;
656
#ifdef CONFIG_SYSFS
657 struct list_head bd_holder_list;
658
#endif
659 struct block_device * bd_contains;
660 unsigned bd_block_size;
661 struct hd_struct * bd_part;
662 /* number of times partitions within
this device have been opened. */
663 unsigned bd_part_count;
664 int bd_invalidated;
665 struct gendisk * bd_disk;
666 struct list_head bd_list;
667 /*
668 * Private data. You must have bd_claim'ed the block_device
669 * to use this. NOTE:
bd_claim allows an owner to claim
670 * the same device multiple times, the
owner must take special
671 * care to not mess up bd_private for
that case.
672 */
673 unsigned long bd_private;
674
675 /* The counter of freeze processes */
676 int bd_fsfreeze_count;
677 /* Mutex for freeze */
678 struct mutex bd_fsfreeze_mutex;
679
};
---------------------------------------------------------------------
块设备的管理是以一个块设备伪文件系统组织的,每个块设备是这个文件系统上的一个块设备文件,其对应的文件索引节点结构为bdev_inode,其定义为:
---------------------------------------------------------------------
fs/block_dev.c
32 struct bdev_inode {
33
struct block_device bdev;
34
struct inode vfs_inode;
35 };
---------------------------------------------------------------------
它只是把一个block_device结构和一个虚拟文件系统索引节点结构结合在一起了而已。
块设备子系统的初始化由两个部分组成,第一个是vfs_caches_init()中调用的bdev_cache_init()函数,它初始化bdev伪文件系统。另外一个genhd_device_init()函数。
然后来看的是vfs_caches_init()中调用的bdev_cache_init()函数,它初始化bdev虚拟文件系统。它的定义为:
---------------------------------------------------------------------
fs/block_dev.c
435
static struct kmem_cache * bdev_cachep __read_mostly;
510
void __init bdev_cache_init(void)
511
{
512 int err;
513 struct vfsmount *bd_mnt;
514
515 bdev_cachep =
kmem_cache_create("bdev_cache",
516 sizeof(struct bdev_inode),0, (SLAB_HWCACHE_ALIGN|
517 SLAB_RECLAIM_ACCOUNT| SLAB_MEM_SPREAD|SLAB_PANIC),
518 init_once);
519 err =
register_filesystem(&bd_type);
520 if (err)
521 panic("Cannot register
bdev pseudo-fs");
522 bd_mnt = kern_mount(&bd_type);
523 if (IS_ERR(bd_mnt))
524 panic("Cannot create bdev
pseudo-fs");
525 /*
526 * This vfsmount structure is only
used to obtain the
527 * blockdev_superblock, so tell
kmemleak not to report it.
528 */
529 kmemleak_not_leak(bd_mnt);
530 blockdev_superblock =
bd_mnt->mnt_sb; /* For writeback */
531
}
---------------------------------------------------------------------
这个函数操作如下:
1、调用kmem_cache_create(),来创建struct bdev_inode结构类型的slab缓冲区,并由静态变量bdev_cachep来引用这个缓冲区。而这里值得注意的就是kmem_cache_create()形参ctor的实参为函数init_once()函数,这个函数用于在申请的内存返回给申请者之前来对分配的结构进行一定的初始化。这个函数的定义为:
---------------------------------------------------------------------
fs/block_dev.c
452
static void init_once(void *foo)
453
{
454 struct bdev_inode *ei = (struct
bdev_inode *) foo;
455 struct block_device *bdev =
&ei->bdev;
456
457 memset(bdev, 0, sizeof(*bdev));
458 mutex_init(&bdev->bd_mutex);
459
INIT_LIST_HEAD(&bdev->bd_inodes);
460 INIT_LIST_HEAD(&bdev->bd_list);
461
#ifdef CONFIG_SYSFS
462
INIT_LIST_HEAD(&bdev->bd_holder_list);
463
#endif
464
inode_init_once(&ei->vfs_inode);
465 /* Initialize mutex for freeze. */
466
mutex_init(&bdev->bd_fsfreeze_mutex);
467
}
---------------------------------------------------------------------
主要就是初始化struct bdev_inode结构vfs_inode成员的各个字段。
2、调用register_filesystem(&bd_type)来向系统注册bdev伪文件系统。
3、调用kern_mount(&bd_type)挂载bdev伪文件系统。bdev伪文件系统类型定义如下:
---------------------------------------------------------------------
fs/block_dev.c
502
static struct file_system_type bd_type = {
503 .name = "bdev",
504 .get_sb = bd_get_sb,
505 .kill_sb = kill_anon_super,
506
};
---------------------------------------------------------------------
一个文件系统的许许多多的特性正是通过super_block对象实例的一个个字段的特定值来向我们展示的,所以,在这里我们最为关心的还是bd_get_sb()方法,正是它为文件系统创建super_block对象的。
调用环境嘛,同样是在vfs_kern_mount()函数中,调用alloc_vfsmnt(name)分配了vfsmount对象,初始化了vfsmount的mnt_devname字段,对于bdev来说,也就是"bdev"了,初始化了mnt_flags为MNT_INTERNAL,还有其他各种字段。然后就是调用type->get_sb(type,
flags, name, data, mnt)来创建super_block对象,其实也就是bd_get_sb()函数,对于bdev来说参数type为bd_type,flags为MS_KERNMOUNT,name为"bdev",data为NULL,mnt则为刚刚创建的vfsmount对象。
接着来看bd_get_sb()函数的定义:
---------------------------------------------------------------------
fs/block_dev.c
488
static const struct super_operations bdev_sops = {
489 .statfs = simple_statfs,
490 .alloc_inode = bdev_alloc_inode,
491 .destroy_inode = bdev_destroy_inode,
492 .drop_inode = generic_delete_inode,
493 .clear_inode = bdev_clear_inode,
494
};
495
496
static int bd_get_sb(struct file_system_type *fs_type,
497 int flags, const char *dev_name, void
*data, struct vfsmount *mnt)
498
{
499 return get_sb_pseudo(fs_type,
"bdev:", &bdev_sops, 0x62646576, mnt);
500
}
---------------------------------------------------------------------
很简单,只是对于get_sb_pseudo()函数的封装而已。get_sb_pseudo()函数定义为:
---------------------------------------------------------------------
fs/libfs.c
204
/*
205 * Common helper for pseudo-filesystems
(sockfs, pipefs, bdev - stuff that
206 * will never be mountable)
207
*/
208
int get_sb_pseudo(struct file_system_type *fs_type, char *name,
209 const struct super_operations *ops,
unsigned long magic,
210 struct vfsmount *mnt)
211
{
212 struct super_block *s = sget(fs_type,
NULL, set_anon_super, NULL);
213 struct dentry *dentry;
214 struct inode *root;
215 struct qstr d_name = {.name = name,
.len = strlen(name)};
216
217 if (IS_ERR(s))
218 return PTR_ERR(s);
219
220 s->s_flags = MS_NOUSER;
221 s->s_maxbytes = MAX_LFS_FILESIZE;
222 s->s_blocksize = PAGE_SIZE;
223 s->s_blocksize_bits = PAGE_SHIFT;
224 s->s_magic = magic;
225 s->s_op = ops ? ops :
&simple_super_operations;
226 s->s_time_gran = 1;
227 root = new_inode(s);
228 if (!root)
229 goto Enomem;
230 /*
231 * since this is the first inode, make
it number 1. New inodes created
232 * after this must take care not to
collide with it (by passing
233 * max_reserved of 1 to iunique).
234 */
235 root->i_ino = 1;
236 root->i_mode = S_IFDIR | S_IRUSR |
S_IWUSR;
237 root->i_atime = root->i_mtime =
root->i_ctime = CURRENT_TIME;
238 dentry = d_alloc(NULL, &d_name);
239 if (!dentry) {
240 iput(root);
241 goto Enomem;
242 }
243 dentry->d_sb = s;
244 dentry->d_parent = dentry;
245 d_instantiate(dentry, root);
246 s->s_root = dentry;
247 s->s_flags |= MS_ACTIVE;
248 simple_set_mnt(mnt, s);
249 return 0;
250
251
Enomem:
252 deactivate_locked_super(s);
253 return -ENOMEM;
254
}
---------------------------------------------------------------------
通过这个函数,我们来看bdev这类伪文件系统的一些特性。这个函数完成如下操作:
a.调用sget(fs_type, NULL,
set_anon_super, NULL)来获得一个super_block对象。sget()函数本质上会分配一个super_block对象,然后初始化其s_type指向文件系统类型,将文件系统类型的name字段拷贝到super_block的s_id字段,将超级块对象添加进系统超级块对象链表super_blocks和文件系统超级快链表type->fs_supers中。用合适的方式设置超级块的s_dev字段:主设备号为0次设备号不同于其他已挂载的的特殊文件系统的次设备号。
b.设置超级块s_flags为MS_NOUSER,说明文件系统不会被安装;
设置s_maxbytes字段为MAX_LFS_FILESIZE,最大文件的大小;
设置s_blocksize,也就是块大小,为PAGE_SIZE,一个页的大小;
设置s_blocksize_bits为PAGE_SHIFT;
设置s_magic,文件系统对应的幻数,为0x62646576;
设置s_op,也就是super_block的操作表,为bdev_sops;
设置s_time_gran,访问时间等的粒度,以ns为单位的值,为1。
c.调用new_inode(s)来为文件系统的根创建inode节点。new_inode(s)定义为:
---------------------------------------------------------------------
fs/block_dev.c
647
struct inode *new_inode(struct super_block *sb)
648
{
649 /*
650 * On a 32bit, non LFS stat() call,
glibc will generate an
651 * EOVERFLOW error if st_ino won't fit
in target struct field.
652 * Use 32bit counter here to attempt
to avoid that.
653 */
654 static unsigned int last_ino;
655 struct inode *inode;
656
657 spin_lock_prefetch(&inode_lock);
658
659 inode = alloc_inode(sb);
660 if (inode) {
661 spin_lock(&inode_lock);
662 __inode_add_to_lists(sb, NULL,
inode);
663 inode->i_ino = ++last_ino;
664 inode->i_state = 0;
665 spin_unlock(&inode_lock);
666 }
667 return inode;
668
}
669
EXPORT_SYMBOL(new_inode);
---------------------------------------------------------------------
new_inode()函数调用完成如下操作:
(1).调用alloc_inode(sb)函数来分配inode,而它本质上会调用sb->s_op->alloc_inode(sb)方法来分配inode,也就是bdev_alloc_inode()函数。而bdev_alloc_inode()只是在bdev_cachep缓存中分配bdev_inode,并将其vfs_inode成员的地址返回而已。然后将inode初始化。
(2).调用__inode_add_to_lists(sb,
NULL, inode)将inode添加进inode_in_use和超级块的所有inode链表sb->s_inodes中。
(3).设置inode->i_ino为++last_ino,last_ino是一个静态变量,所有调用new_inode()的函数共用同一个inode号空间,所以这个分配肯定是无效的。设置inode->i_state为0。
(3).返回inode地址。
d.设置root->i_ino为1,即跟inode的索引节点号为1。设置root->i_mode为S_IFDIR | S_IRUSR | S_IWUSR,更新root->i_atime、root->i_mtime、root->i_ctime为当前时间CURRENT_TIME。
e.调用d_alloc(NULL,
&d_name),从目录项缓存中分配dentry并初始化其d_name字段。
f.初始化dentry->d_sb指向super_block对象,初始化dentry->d_parent为其自身。
g.调用d_instantiate(dentry,
root)将目录项和inode联系起来,主要就是将dentry(利用d_alias字段)添加进inode的dentry链表inode->i_dentry,并使dentry的d_inode字段指向inode。
h.设置超级块的s_root指向dentry。设置s_flags的MS_ACTIVE表示,超级块为活跃的。
i.调用simple_set_mnt(mnt,
s)将vfsmount和super_block联系起来,也就是使mnt->mnt_sb指向sb,使mnt->mnt_root指向sb->s_root。
4、vfsmount结构仅仅被用来获得blockdev_superblock,所以调用函数kmemleak_not_leak(bd_mnt)来告诉kmemleak不要报告它。
5、使全局变量blockdev_superblock指向创建的super_block对象。
整个内核里,只有两个地方使用了这个blockdev_superblock,一个是sb_is_blkdev_sb函数:
---------------------------------------------------------------------
fs/internal.h
23 static inline int
sb_is_blkdev_sb(struct super_block *sb)
24 {
25
return sb == blockdev_superblock;
26 }
---------------------------------------------------------------------
另外一个地方,便是fs/block_dev.c, line 556的bdget(dev_t dev)函数,实在是耐人回味啊。
接着来看block子系统初始化的这第二部分。genhd_device_init()函数定义为:
---------------------------------------------------------------------
block/genhd.c
792
static int __init genhd_device_init(void)
793
{
794 int error;
795
796 block_class.dev_kobj =
sysfs_dev_block_kobj;
797 error = class_register(&block_class);
798 if (unlikely(error))
799 return error;
800 bdev_map = kobj_map_init(base_probe,
&block_class_lock);
801 blk_dev_init();
802
803 register_blkdev(BLOCK_EXT_MAJOR,
"blkext");
804
805
#ifndef CONFIG_SYSFS_DEPRECATED
806 /* create top-level block dir */
807 block_depr =
kobject_create_and_add("block", NULL);
808
#endif
809 return 0;
810
}
811
812
subsys_initcall(genhd_device_init);
1009
struct class block_class = {
1010 .name = "block",
1011
};
---------------------------------------------------------------------
这个函数完成如下操作:
1、设置类block_class的dev_kobj为sysfs_dev_block_kobj,在前面初始化Driver Model的driver_init()函数里,我们看到它调用了devices_init(),而正是在devices_init()创建了一个以dev_kobj为父kobject的kobject。
2、调用class_register(&block_class)来向系统注册“block”类。来仔细的看下这个注册类的宏,以对类的本质有更多的了解。class_register()定义为:
---------------------------------------------------------------------
include/linux/device.h
224
#define class_register(class)
\
225
({
\
226 static struct lock_class_key
__key; \
227 __class_register(class,
&__key); \
228
})
---------------------------------------------------------------------
这个宏主要还是调用__class_register(class, &__key)来完成任务。而__class_register()定义为:
---------------------------------------------------------------------
driver/base/class.c
int
__class_register(struct class *cls, struct lock_class_key *key)
{
struct class_private *cp;
int error;
pr_debug("device class '%s':
registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
klist_init(&cp->class_devices,
klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->class_interfaces);
kset_init(&cp->class_dirs);
__mutex_init(&cp->class_mutex,
"struct class mutex", key);
error =
kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices
of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;
#if
defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK)
/* let the block class directory show up in
the root of sysfs */
if (cls != &block_class)
cp->class_subsys.kobj.kset =
class_kset;
#else
cp->class_subsys.kobj.kset = class_kset;
#endif
cp->class_subsys.kobj.ktype =
&class_ktype;
cp->class = cls;
cls->p = cp;
error =
kset_register(&cp->class_subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
---------------------------------------------------------------------
我们最为关心的还是,注册的类是如何同我们之前了解到的Driver Model初始化过程中初始化的那些数据结构相联系的。这个函数完成的操作如下:
a.调用kzalloc(sizeof(*cp),
GFP_KERNEL)来为一个class_private结构分配对象。这个结构定义如下:
---------------------------------------------------------------------
drivers/base/base.h
55 struct class_private {
56
struct kset class_subsys;
57
struct klist class_devices;
58
struct list_head class_interfaces;
59
struct kset class_dirs;
60
struct mutex class_mutex;
61
struct class *class;
62 };
---------------------------------------------------------------------
class结构体driver core部分用于保存私有数据的结构。各个字段的意义:
class_subsys
– 定义这个class的kset 结构体,是主要的kobject。
class_devices
– 与这个class相联系的设备的表。
class_interfaces
– 与这个class相联系的class_interfaces 链表。
class_dirs
– 与这个class相关的用于虚拟设备的"glue" 目录
class_mutex
– 用于保护chidren,devices,和接口链表的互斥体
class
– 指向这个结构相关的class的指针
b.调用klist_init(&cp->class_devices,
klist_class_dev_get, klist_class_dev_put)来初始化一个klist结构,其定义为:
---------------------------------------------------------------------
lib/klist.c
85 void klist_init(struct
klist *k, void (*get)(struct klist_node *),
86 void (*put)(struct klist_node
*))
87 {
88
INIT_LIST_HEAD(&k->k_list);
89
spin_lock_init(&k->k_lock);
90
k->get = get;
91
k->put = put;
92 }
---------------------------------------------------------------------
c.初始化cp->class_interfaces,调用kset_init(&cp->class_dirs)来初始化cp->class_dirs,初始化类互斥体cp->class_mutex,为cp->class_subsys.kobj设置名字。
d. cls->dev_kobj为NULL的情况下,会被设置为sysfs_dev_char_kobj。
如果cls不为block_class,设置cp->class_subsys.kobj.kset为class_kset,在函数driver_init曾调用classes_init()函数来创建kset,并由class_kset来引用这个kset。
若为block_class,则使cp->class_subsys.kobj.kset为空,也就意味着class的kobject的父kobject及kset均为空,在这种情况下,我们知道,相关的函数是会以sysfs的根sysfs_dirent结构sysfs_root为父sysfs_dirent来为其创建sysfs_dirent的,也就是在sysfs文件系统的根目录下创建目录。
设置cp->class_subsys.kobj.ktype为class_ktype。来看一下class_ktype,以了解class释放的时候内核都执行了哪些操作:
---------------------------------------------------------------------
drivers/base/class.c
50 static void
class_release(struct kobject *kobj)
51 {
52 struct
class_private *cp = to_class(kobj);
53 struct
class *class = cp->class;
54
55 pr_debug("class
'%s': release.\n", class->name);
56
57 if
(class->class_release)
58
class->class_release(class);
59 else
60
pr_debug("class '%s' does not have a release() function, "
61 "be
careful\n", class->name);
62
63 kfree(cp);
64 }
71 static struct kobj_type class_ktype = {
72 .sysfs_ops = &class_sysfs_ops,
73 .release = class_release,
74 };
---------------------------------------------------------------------
使cp->class指向class对象cls,cls->p指向cp。
e.调用kset_register(&cp->class_subsys)来注册kset。
f.调用add_class_attrs(class_get(cls))来为class添加属性。
这里主要处理的就是class->p->class_subsys。
3、调用kobj_map_init(base_probe,
&block_class_lock)来为块设备子系统创建kobject映射域,由静态变量bdev_map。在前面“Linux字符设备管理”博文中我们曾提到过kobject映射域,在那里,它主要用于通过设备号来来获得对应的cdev结构体。这里的用户也比较相似。
4、调用blk_dev_init()函数,其定义为:
---------------------------------------------------------------------
block/blk-core.c
2503
int __init blk_dev_init(void)
2504
{
2505 BUILD_BUG_ON(__REQ_NR_BITS > 8 *
2506 sizeof(((struct request
*)0)->cmd_flags));
2507
2508 kblockd_workqueue =
create_workqueue("kblockd");
2509 if (!kblockd_workqueue)
2510 panic("Failed to create
kblockd\n");
2511
2512 request_cachep =
kmem_cache_create("blkdev_requests",
2513 sizeof(struct request), 0,
SLAB_PANIC, NULL);
2514
2515 blk_requestq_cachep =
kmem_cache_create("blkdev_queue",
2516 sizeof(struct request_queue), 0,
SLAB_PANIC, NULL);
2517
2518 return 0;
2519
}
---------------------------------------------------------------------
这个函数完成三件事:
a.创建一个名为kblockd工作队列,由静态变量
kblockd_workqueue来引用这个工作队列。块设备子系统有许多工作,是不应该占用进程的执行时间的,比如刷新缓存中数据到磁盘,这个操作甚至会使进程进入睡眠状态,还有其他各种复杂的工作,所以要创建专门的工作队列来完成块设备子系统的某些工作。
b.调用kmem_cache_create("blkdev_requests",sizeof(struct
request), 0, SLAB_PANIC, NULL)函数来为请求结构体request创建slab缓存。后面会有对request的更详细说明。
c.调用blk_requestq_cachep
= kmem_cache_create("blkdev_queue", sizeof(struct request_queue), 0,
SLAB_PANIC, NULL)来为请求队列创建请求队列。每个磁盘,物理磁盘,不是分区,会有一个请求队列结构。后面有详细说明。
5、调用register_blkdev(BLOCK_EXT_MAJOR,
"blkext")来注册"blkext"块设备。BLOCK_EXT_MAJOR定义为259。我们来看下register_blkdev()函数,其定义为:
---------------------------------------------------------------------
block/genhd.c
279
int register_blkdev(unsigned int major, const char *name)
280
{
281 struct blk_major_name **n, *p;
282 int index, ret = 0;
283
284 mutex_lock(&block_class_lock);
285
286 /* temporary */
287 if (major == 0) {
288 for (index = ARRAY_SIZE(major_names)-1;
index > 0; index--) {
289 if (major_names[index] == NULL)
290
break;
291 }
292
293 if (index == 0) {
294 printk("register_blkdev:
failed to get major for %s\n",
295 name);
296 ret = -EBUSY;
297 goto out;
298 }
299 major = index;
300 ret = major;
301 }
302
303 p = kmalloc(sizeof(struct blk_major_name),
GFP_KERNEL);
304 if (p == NULL) {
305 ret = -ENOMEM;
306 goto out;
307 }
308
309 p->major = major;
310 strlcpy(p->name, name,
sizeof(p->name));
311 p->next = NULL;
312 index = major_to_index(major);
313
314 for (n = &major_names[index]; *n; n =
&(*n)->next) {
315 if ((*n)->major == major)
316 break;
317 }
318 if (!*n)
319 *n = p;
320 else
321 ret = -EBUSY;
322
323 if (ret < 0) {
324 printk("register_blkdev:
cannot get major %d for %s\n",
325 major, name);
326 kfree(p);
327 }
328
out:
329 mutex_unlock(&block_class_lock);
330 return ret;
331
}
---------------------------------------------------------------------
这里主要来看major_names的定义:
---------------------------------------------------------------------
block/genhd.c
237
static struct blk_major_name {
238 struct blk_major_name *next;
239 int major;
240 char name[16];
241
} *major_names[BLKDEV_MAJOR_HASH_SIZE];
---------------------------------------------------------------------
看到这儿,应该就全明白了,register_blkdev()和我们在“Linux字符设备管理”中介绍的几个“register”的函数其实差不多,大同小异而已,也同样是申请设备号。
6、如果没有配置CONFIG_SYSFS_DEPRECATED选项的话,会在sysfs顶层创建“block”目录。配置的话,前面我们看到,则是在调用函数class_register(&block_class),即向系统注册“block”类的时候来创建。