Chinaunix首页 | 论坛 | 博客
  • 博客访问: 108883
  • 博文数量: 58
  • 博客积分: 67
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-21 13:20
文章分类

全部博文(58)

文章存档

2014年(4)

2013年(54)

我的朋友

分类: LINUX

2013-08-21 08:54:07

Linux块设备子系统的初始化


每个块设备用一个块设备结构体进行描述,即block_device结构,它描述一个逻辑上的块设备,可以是一整个磁盘,也可以仅仅是磁盘上的一个分区。其定义为:

---------------------------------------------------------------------

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对象,初始化了vfsmountmnt_devname字段,对于bdev来说,也就是"bdev"了,初始化了mnt_flagsMNT_INTERNAL,还有其他各种字段。然后就是调用type->get_sb(type, flags, name, data, mnt)来创建super_block对象,其实也就是bd_get_sb()函数,对于bdev来说参数typebd_typeflagsMS_KERNMOUNTname"bdev"dataNULLmnt则为刚刚创建的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_blocks_id字段,将超级块对象添加进系统超级块对象链表super_blocks和文件系统超级快链表type->fs_supers中。用合适的方式设置超级块的s_dev字段:主设备号为0次设备号不同于其他已挂载的的特殊文件系统的次设备号。

 

b.设置超级块s_flagsMS_NOUSER,说明文件系统不会被安装;

设置s_maxbytes字段为MAX_LFS_FILESIZE,最大文件的大小;

设置s_blocksize,也就是块大小,为PAGE_SIZE,一个页的大小;

设置s_blocksize_bitsPAGE_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_inolast_ino是一个静态变量,所有调用new_inode()的函数共用同一个inode号空间,所以这个分配肯定是无效的。设置inode->i_state0

 

(3).返回inode地址。

 

d.设置root->i_ino1,即跟inode的索引节点号为1。设置root->i_modeS_IFDIR | S_IRUSR | S_IWUSR,更新root->i_atimeroot->i_mtimeroot->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字段)添加进inodedentry链表inode->i_dentry,并使dentryd_inode字段指向inode

 

h.设置超级块的s_root指向dentry。设置s_flagsMS_ACTIVE表示,超级块为活跃的。

 

i.调用simple_set_mnt(mnt, s)vfsmountsuper_block联系起来,也就是使mnt->mnt_sb指向sb,使mnt->mnt_root指向sb->s_root

 

4vfsmount结构仅仅被用来获得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 556bdget(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_classdev_kobjsysfs_dev_block_kobj,在前面初始化Driver Modeldriver_init()函数里,我们看到它调用了devices_init(),而正是在devices_init()创建了一个以dev_kobj为父kobjectkobject

 

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 – 定义这个classkset 结构体,是主要的kobject

class_devices – 与这个class相联系的设备的表。

class_interfaces – 与这个class相联系的class_interfaces 链表。

class_dirs – 与这个class相关的用于虚拟设备的"glue" 目录

class_mutex – 用于保护chidrendevices,和接口链表的互斥体

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_kobjNULL的情况下,会被设置为sysfs_dev_char_kobj

如果cls不为block_class,设置cp->class_subsys.kobj.ksetclass_kset,在函数driver_init曾调用classes_init()函数来创建kset,并由class_kset来引用这个kset

若为block_class,则使cp->class_subsys.kobj.kset为空,也就意味着classkobject的父kobjectkset均为空,在这种情况下,我们知道,相关的函数是会以sysfs的根sysfs_dirent结构sysfs_root为父sysfs_dirent来为其创建sysfs_dirent的,也就是在sysfs文件系统的根目录下创建目录。

设置cp->class_subsys.kobj.ktypeclass_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对象clscls->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”类的时候来创建。

阅读(678) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~