Linux块设备子系统的初始化
每个块设备用一个块设备结构体进行描述,即block_device结构,它描述一个逻辑上的块设备,可以是一整个磁盘,也可以仅仅是磁盘上的一个分区。其定义为:
---------------------------------------------------------------------
include/linux/fs.h
647
struct block_device {
648
dev_tbd_dev;/* not a kdev_t - it's a search key */
649struct inode *bd_inode;/* will die */
650struct super_block *bd_super;
651intbd_openers;
652
struct mutexbd_mutex;/* open/close mutex */
653struct list_headbd_inodes;
654void *bd_holder;
655intbd_holders;
656
#ifdef CONFIG_SYSFS
657struct list_headbd_holder_list;
658
#endif
659struct block_device *bd_contains;
660unsignedbd_block_size;
661struct hd_struct *bd_part;
662/* number of times partitions within
this device have been opened. */
663unsignedbd_part_count;
664intbd_invalidated;
665struct gendisk *bd_disk;
666struct list_headbd_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*/
673unsigned longbd_private;
674
675/* The counter of freeze processes */
676intbd_fsfreeze_count;
677/* Mutex for freeze */
678struct mutexbd_fsfreeze_mutex;
679
};
---------------------------------------------------------------------
块设备的管理是以一个块设备伪文件系统组织的,每个块设备是这个文件系统上的一个块设备文件,其对应的文件索引节点结构为bdev_inode,其定义为:
---------------------------------------------------------------------
fs/block_dev.c
32 struct bdev_inode {
33struct block_device bdev;
34struct 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
{
512int err;
513struct vfsmount *bd_mnt;
514
515bdev_cachep =
kmem_cache_create("bdev_cache",
516sizeof(struct bdev_inode),0, (SLAB_HWCACHE_ALIGN|
517SLAB_RECLAIM_ACCOUNT| SLAB_MEM_SPREAD|SLAB_PANIC),
518init_once);
519err =
register_filesystem(&bd_type);
520if (err)
521panic("Cannot register
bdev pseudo-fs");
522bd_mnt = kern_mount(&bd_type);
523if (IS_ERR(bd_mnt))
524panic("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*/
529kmemleak_not_leak(bd_mnt);
530blockdev_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
{
454struct bdev_inode *ei = (struct
bdev_inode *) foo;
455struct block_device *bdev =
&ei->bdev;
456
457memset(bdev, 0, sizeof(*bdev));
458mutex_init(&bdev->bd_mutex);
459INIT_LIST_HEAD(&bdev->bd_inodes);
460INIT_LIST_HEAD(&bdev->bd_list);
461
#ifdef CONFIG_SYSFS
462INIT_LIST_HEAD(&bdev->bd_holder_list);
463
#endif
464inode_init_once(&ei->vfs_inode);
465/* Initialize mutex for freeze. */
466mutex_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,
497int flags, const char *dev_name, void
*data, struct vfsmount *mnt)
498
{
499return 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,
209const struct super_operations *ops,
unsigned long magic,
210struct vfsmount *mnt)
211
{
212struct super_block *s = sget(fs_type,
NULL, set_anon_super, NULL);
213struct dentry *dentry;
214struct inode *root;
215struct qstr d_name = {.name = name,
.len = strlen(name)};
216
217if (IS_ERR(s))
218return PTR_ERR(s);
219
220s->s_flags = MS_NOUSER;
221s->s_maxbytes = MAX_LFS_FILESIZE;
222s->s_blocksize = PAGE_SIZE;
223s->s_blocksize_bits = PAGE_SHIFT;
224s->s_magic = magic;
225s->s_op = ops ? ops :
&simple_super_operations;
226s->s_time_gran = 1;
227root = new_inode(s);
228if (!root)
229goto 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*/
235root->i_ino = 1;
236root->i_mode = S_IFDIR | S_IRUSR |
S_IWUSR;
237root->i_atime = root->i_mtime =
root->i_ctime = CURRENT_TIME;
238dentry = d_alloc(NULL, &d_name);
239if (!dentry) {
240iput(root);
241goto Enomem;
242}
243dentry->d_sb = s;
244dentry->d_parent = dentry;
245d_instantiate(dentry, root);
246s->s_root = dentry;
247s->s_flags |= MS_ACTIVE;
248simple_set_mnt(mnt, s);
249return 0;
250
251
Enomem:
252deactivate_locked_super(s);
253return -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*/
654static unsigned int last_ino;
655struct inode *inode;
656
657spin_lock_prefetch(&inode_lock);
658
659inode = alloc_inode(sb);
660if (inode) {
661spin_lock(&inode_lock);
662__inode_add_to_lists(sb, NULL,
inode);
663inode->i_ino = ++last_ino;
664inode->i_state = 0;
665spin_unlock(&inode_lock);
666}
667return 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 {
25return 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
{
794int error;
795
796block_class.dev_kobj =
sysfs_dev_block_kobj;
797error = class_register(&block_class);
798if (unlikely(error))
799return error;
800bdev_map = kobj_map_init(base_probe,
&block_class_lock);
801blk_dev_init();
802
803register_blkdev(BLOCK_EXT_MAJOR,
"blkext");
804
805
#ifndef CONFIG_SYSFS_DEPRECATED
806/* create top-level block dir */
807block_depr =
kobject_create_and_add("block", NULL);
808
#endif
809return 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
({\
226static 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 {
56struct kset class_subsys;
57struct klist class_devices;
58struct list_head class_interfaces;
59struct kset class_dirs;
60struct mutex class_mutex;
61struct 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 {
88INIT_LIST_HEAD(&k->k_list);
89spin_lock_init(&k->k_lock);
90k->get = get;
91k->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 {
52struct
class_private *cp = to_class(kobj);
53struct
class *class = cp->class;
54
55pr_debug("class
'%s': release.\n", class->name);
56
57if
(class->class_release)
58class->class_release(class);
59else
60pr_debug("class '%s' does not have a release() function, "
61"be
careful\n", class->name);
62
63kfree(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
{
2505BUILD_BUG_ON(__REQ_NR_BITS > 8 *
2506sizeof(((struct request
*)0)->cmd_flags));
2507
2508kblockd_workqueue =
create_workqueue("kblockd");
2509if (!kblockd_workqueue)
2510panic("Failed to create
kblockd\n");
2511
2512request_cachep =
kmem_cache_create("blkdev_requests",
2513sizeof(struct request), 0,
SLAB_PANIC, NULL);
2514
2515blk_requestq_cachep =
kmem_cache_create("blkdev_queue",
2516sizeof(struct request_queue), 0,
SLAB_PANIC, NULL);
2517
2518return 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
{
281struct blk_major_name **n, *p;
282int index, ret = 0;
283
284mutex_lock(&block_class_lock);
285
286/* temporary */
287if (major == 0) {
288for (index = ARRAY_SIZE(major_names)-1;
index > 0; index--) {
289if (major_names[index] == NULL)
290
break;
291}
292
293if (index == 0) {
294printk("register_blkdev:
failed to get major for %s\n",
295name);
296ret = -EBUSY;
297goto out;
298}
299major = index;
300ret = major;
301}
302
303p = kmalloc(sizeof(struct blk_major_name),
GFP_KERNEL);
304if (p == NULL) {
305ret = -ENOMEM;
306goto out;
307}
308
309p->major = major;
310strlcpy(p->name, name,
sizeof(p->name));
311p->next = NULL;
312index = major_to_index(major);
313
314for (n = &major_names[index]; *n; n =
&(*n)->next) {
315if ((*n)->major == major)
316break;
317}
318if (!*n)
319*n = p;
320else
321ret = -EBUSY;
322
323if (ret < 0) {
324printk("register_blkdev:
cannot get major %d for %s\n",
325major, name);
326kfree(p);
327}
328
out:
329mutex_unlock(&block_class_lock);
330return ret;
331
}
---------------------------------------------------------------------
这里主要来看major_names的定义:
---------------------------------------------------------------------
block/genhd.c
237
static struct blk_major_name {
238struct blk_major_name *next;
239int major;
240char name[16];
241
} *major_names[BLKDEV_MAJOR_HASH_SIZE];
---------------------------------------------------------------------
看到这儿,应该就全明白了,register_blkdev()和我们在“Linux字符设备管理”中介绍的几个“register”的函数其实差不多,大同小异而已,也同样是申请设备号。
6、如果没有配置CONFIG_SYSFS_DEPRECATED选项的话,会在sysfs顶层创建“block”目录。配置的话,前面我们看到,则是在调用函数class_register(&block_class),即向系统注册“block”类的时候来创建。