安装根文件系统

5.12 安装根文件系统

start_kernel下步是另一个重要的函数,678行的vfs_caches_init,用于初始化VFS那些数据结构的slab缓存,来自fs/dcache.c

 

2355void __init vfs_caches_init(unsigned long mempages)

2356{

2357        unsigned long reserve;

2358

2359        /* Base hash sizes on available memory, with a reserve equal to

2360           150% of current kernel size */

2361

2362        reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);

2363        mempages -= reserve;

2364

2365        names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,

2366                        SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);

2367

2368        dcache_init();

2369        inode_init();

2370        files_init(mempages);

2371        mnt_init();

2372        bdev_cache_init();

2373        chrdev_init();

2374}

 

5.12.1 创建VFS相关slab缓存

函数首先根据传递进来的totalram_pages参数计算还可以作为缓存的页面数量,然后建立一个存放文件名称的slab缓存,PATH_MAX的大小为4096。随后调用dcache_init,来自同一文件:

 

2311static void __init dcache_init(void)

2312{

2313        int loop;

2314

2315        /*

2316         * A constructor could be added for stable state like the lists,

2317         * but it is probably not worth it because of the cache nature

2318         * of the dcache.

2319         */

2320        dentry_cache = KMEM_CACHE(dentry,

2321                SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD);

2322       

2323        register_shrinker(&dcache_shrinker);

2324

2325        /* Hash may have been set up in dcache_init_early */

2326        if (!hashdist)

2327                return;

2328

2329        dentry_hashtable =

2330                alloc_large_system_hash("Dentry cache",

2331                                        sizeof(struct hlist_head),

2332                                        dhash_entries,

2333                                        13,

2334                                        0,

2335                                        &d_hash_shift,

2336                                        &d_hash_mask,

2337                                        0);

2338

2339        for (loop = 0; loop < (1 << d_hash_shift); loop++)

2340                INIT_HLIST_HEAD(&dentry_hashtable[loop]);

2341}

 

建立dentrydentry_hashtable,但是由于前面先建立了一个dentry_hashtable,所以这里就只建立dentryslab缓存。继续走,vfs_caches_init2369行,来自fs/inode.cinode_init函数:

 

1563void __init inode_init(void)

1564{

1565        int loop;

1566

1567        /* inode slab cache */

1568        inode_cachep = kmem_cache_create("inode_cache",

1569                                         sizeof(struct inode),

1570                                         0,

1571                                         (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|

1572                                         SLAB_MEM_SPREAD),

1573                                         init_once);

1574        register_shrinker(&icache_shrinker);

1575

1576        /* Hash may have been set up in inode_init_early */

1577        if (!hashdist)

1578                return;

1579

1580        inode_hashtable =

1581                alloc_large_system_hash("Inode-cache",

1582                                        sizeof(struct hlist_head),

1583                                        ihash_entries,

1584                                        14,

1585                                        0,

1586                                        &i_hash_shift,

1587                                        &i_hash_mask,

1588                                        0);

1589

1590        for (loop = 0; loop < (1 << i_hash_shift); loop++)

1591                INIT_HLIST_HEAD(&inode_hashtable[loop]);

1592}

 

inode_init跟前面差不多,建立inodeinode_hashtable,但是由于前面先建立了一个inode_hashtable,所以这里就只建立inodeslab缓存。vfs_caches_init2370行,files_init,来自fs/file_table.c

 

415void __init files_init(unsigned long mempages)

 416{

 417        int n;

 418

 419        filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,

 420                        SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

 421

 422        /*

 423         * One file with associated inode and dcache is very roughly 1K.

 424         * Per default don't use more than 10% of our memory for files.

 425         */

 426

 427        n = (mempages * (PAGE_SIZE / 1024)) / 10;

 428        files_stat.max_files = n;

 429        if (files_stat.max_files < NR_FILE)

 430                files_stat.max_files = NR_FILE;

 431        files_defer_init();

 432        percpu_counter_init(&nr_files, 0);

 433}

 

files_init先建立fileslab缓存然后根据可用页面数量初始化全局files_stat_struct类型变量files_stat。继续走,vfs_caches_init2371行,mnt_init函数,来自fs/namespace.c

 

2321void __init mnt_init(void)

2322{

2323        unsigned u;

2324        int err;

2325

2326        init_rwsem(&namespace_sem);

2327

2328        mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),

2329                        0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

2330

2331        mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);

2332

2333        if (!mount_hashtable)

2334                panic("Failed to allocate mount hash table/n");

2335

2336        printk("Mount-cache hash table entries: %lu/n", HASH_SIZE);

2337

2338        for (u = 0; u < HASH_SIZE; u++)

2339                INIT_LIST_HEAD(&mount_hashtable[u]);

2340

2341        err = sysfs_init();

2342        if (err)

2343                printk(KERN_WARNING "%s: sysfs_init error: %d/n",

2344                        __func__, err);

2345        fs_kobj = kobject_create_and_add("fs", NULL);

2346        if (!fs_kobj)

2347                printk(KERN_WARNING "%s: kobj create error/n", __func__);

2348        init_rootfs();

2349        init_mount_tree();

2350}

 

2328行,建立vfsmountslab缓存,初始化全局mount_hashtable散列,初始化sysfsrootfs文件系统。rootfs文件系统在初始化阶段很重要,是第一个文件系统,根文件系统,所以接下来我们就来详细看看2348行和2349行两个函数都干了些什么。

 

5.12.2 安装rootfs

在安装rootfs之前,首先得通过2348行的init_rootfs()文件对其进行注册,来自fs/ramfs/inode.c

 

308 int __init init_rootfs(void)

309 {

310        int err;

311

312  err = bdi_init(&ramfs_backing_dev_info);

313  if (err)

314         return err;

315

316  err = register_filesystem(&rootfs_fs_type);

317  if (err)

318       bdi_destroy(&ramfs_backing_dev_info);

319

320        return err;

321}

 

312行,一个全局backing_dev_info结构的ramfs_backing_dev_info变量,定义在同一个文件中:

static struct backing_dev_info ramfs_backing_dev_info = {

       .name             = "ramfs",

       .ra_pages = 0, /* No readahead */

       .capabilities    = BDI_CAP_NO_ACCT_AND_WRITEBACK |

                       BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY |

                       BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP,

};

 

这里又得讲一个背景知识。ramfs是什么?ramfs是一个非常简单的文件系统,它输出Linux的磁盘缓存机制(页缓存和目录缓存)作为一个大小动态的基于内存的文件系统。

 

通常,一个正常的文件系统,如ext2的所有的文件由Linux被缓存在页高速缓存中。为了提高系统性能,这些页面的前若干页和后若干页的数据总是保存的,这样可以通过预读外存(一般被挂载的是块设备文件系统)而提升性能;但是这些页面又是标记为可用(空闲)的,所以内存管理系统可以将这些页面作为别用。类似的,在数据写回后备存储时,数据一写回文件就立即被标记为可用,但周围的缓存被保留着直至MM重新分配内存。

 

ramfs并没有这样的预读和回写机制。文件写入ramfs象往常一样,来分配目录和页的缓存,但这里并没有地方可写回它们。这意味着页的数据不再标记为可用,因此当希望回收内存时,内存不能通过MM来释放。

 

实现ramfs所需的代码总量是极少的,因为所有的工作由现有的Linux缓存结构来完成。实际上,你现正在挂载磁盘缓存作为一个文件系统。据此,ramfs并不是一个可通过菜单配置项来卸载的可选组件,它可节省的空间是微不足道的。

 

rootfs是一个特定的ramfs的实例,它始终存在于2.6的系统。你不能卸载rootfs,这个理由近似于你不能杀死init进程。它小巧且简单的为内核确保某些列表不能为空,而不是拥有特定的代码来检查和处理一个空列表。

 

大多数的系统挂载另一个文件系统到rootfs并忽略它。一个空白ramfs实例的空间总量占用是极小的。因此,init_rootfs312行调用bdi_init来初始化全局变量ramfs_backing_dev_info

 

int bdi_init(struct backing_dev_info *bdi)

{

       int i, err;

 

       bdi->dev = NULL;

 

       bdi->min_ratio = 0;

       bdi->max_ratio = 100;

       bdi->max_prop_frac = PROP_FRAC_BASE;

       spin_lock_init(&bdi->wb_lock);

       INIT_RCU_HEAD(&bdi->rcu_head);

       INIT_LIST_HEAD(&bdi->bdi_list);

       INIT_LIST_HEAD(&bdi->wb_list);

       INIT_LIST_HEAD(&bdi->work_list);

 

       bdi_wb_init(&bdi->wb, bdi);

       bdi->wb_mask = 1;

       bdi->wb_cnt = 1;

 

       for (i = 0; i < NR_BDI_STAT_ITEMS; i++) {

              err = percpu_counter_init(&bdi->bdi_stat[i], 0);

              if (err)

                     goto err;

       }

 

       bdi->dirty_exceeded = 0;

       err = prop_local_init_percpu(&bdi->completions);

 

       if (err) {

err:

              while (i--)

                     percpu_counter_destroy(&bdi->bdi_stat[i]);

       }

 

       return err;

}

 

init_rootfs316行调用register_filesystem(&rootfs_fs_type)rootfsfile_system_type结构的全局变量rootfs_fs_type注册到内核。该变量在编译时被初始化为:

static struct file_system_type rootfs_fs_type = {

       .name             = "rootfs",

       .get_sb           = rootfs_get_sb,

       .kill_sb    = kill_litter_super,

};

 

注册文件系统的实质就是把rootfs_fs_type加到全局file_systems指针所指向的整个file_system_type链中。要详细关注register_filesystem程序,请学习博客“文件系统注册”http://blog.csdn.net/yunsongice/archive/2010/06/21/5684879.aspx

 

回到mnt_init 2349行的init_mount_tree()函数来自fs/namespace.c文件:

 

2298static void __init init_mount_tree(void)

2299{

2300        struct vfsmount *mnt;

2301        struct mnt_namespace *ns;

2302        struct path root;

2303

2304        mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);

2305        if (IS_ERR(mnt))

2306                panic("Can't create rootfs");

2307        ns = create_mnt_ns(mnt);

2308        if (IS_ERR(ns))

2309                panic("Can't allocate initial namespace");

2310

2311        init_task.nsproxy->mnt_ns = ns;

2312        get_mnt_ns(ns);

2313

2314        root.mnt = ns->root;

2315        root.dentry = ns->root->mnt_root;

2316

2317        set_fs_pwd(current->fs, &root);

2318        set_fs_root(current->fs, &root);

2319}

 

这个函数在注册好rootfs之后用于完成安装它的任务,2304行,首先调用do_kern_mount安装rootfs。具体的内容请查阅博客“文件系统安装”

http://blog.csdn.net/yunsongice/archive/2010/06/21/5685067.aspx

这里只是简单地讲一讲关键过程,do_kern_mount会调用vfs_kern_mount函数,把这个通过get_fs_type(rootfs)file_system_type链中取到的刚刚注册的rootfs_fs_type全局变量作为参数传给它。随后vfs_kern_mount函数调用rootfs_fs_typeget_sb 函数,也就是rootfs_get_sb

static int rootfs_get_sb(struct file_system_type *fs_type,

       int flags, const char *dev_name, void *data, struct vfsmount *mnt)

{

       return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super,

                         mnt);

}

get_sb_nodev初始化rootfssuper_block结构,随后会调用传递进来的ramfs_fill_super函数参数。这个函数继续初始化rootfssuper_block结构,并执行一个非常重要的d_alloc_root函数将rootfs的根目录初始化为“/”,也就是我们常说的根目录:

struct dentry * d_alloc_root(struct inode * root_inode)

{

       struct dentry *res = NULL;

       if (root_inode) {

              static const struct qstr name = { .name = "/", .len = 1 };

              res = d_alloc(NULL, &name);

              if (res) {

                     res->d_sb = root_inode->i_sb;

                     res->d_parent = res;

                     d_instantiate(res, root_inode);

              }

       }

       return res;

}

 

回到init_mount_tree2307为进程0的命名空间分配一个namespace对象并将它插入到由do_kern_mount()函数返回的已安装文件系统描述符中

    namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);

    list_add(&mnt->mnt_list, &namespace->list);

    namespace->root = mnt;

    mnt->mnt_namespace = init_task.namespace = namespace;

 

将系统中其他每个进程的namespace字段设置为namespace对象的地址;同时初始化引用计数器namespace->count(缺省情况下,所有的进程共享同一个初始namespace)。

 

最后两行,切换当前进程,也就是0号进程的根目录和当前目录为“/”。走到这里,内核第一个文件系统rootfs就算安装完毕了。那么为什么内核不怕麻烦,要在安装实际根文件系统之前安装rootfs文件系统呢?这是因为,rootfs文件系统允许内核容易地改变实际根文件系统。实际上,在大多数情况下,系统初始化是内核会逐个地安装和卸载几个根文件系统。例如,一个发布版的初始启动光盘可能把具有一组最小驱动程序的内核装人RAM中,内核把存放在ramdisk中的一个最小的文件系统作为根安装。接下来,在这个初始根文件系统中的程序探测系统的硬件(例如,它们判断硬盘是否是EIDESCSI等等),装入所有必需的内核模块,并从物理块设备重新安装根文件系统。

 

来看vfs_caches_init的最后两个函数:

 

void __init bdev_cache_init(void)

{

       int err;

       struct vfsmount *bd_mnt;

 

       bdev_cachep = kmem_cache_create("bdev_cache", sizeof(struct bdev_inode),

                     0, (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|

                            SLAB_MEM_SPREAD|SLAB_PANIC),

                     init_once);

       err = register_filesystem(&bd_type);

       if (err)

              panic("Cannot register bdev pseudo-fs");

       bd_mnt = kern_mount(&bd_type);

       if (IS_ERR(bd_mnt))

              panic("Cannot create bdev pseudo-fs");

       kmemleak_not_leak(bd_mnt);

       blockdev_superblock = bd_mnt->mnt_sb;    /* For writeback */

}

 

bdev_cache_init函数初始化块设备驱动的bdev_inode结构的slab缓存,并且注册块设备成一个文件系统,最后把blockdev_superblock全局变量指向这个文件系统的超级块。

 

void __init chrdev_init(void)

{

       cdev_map = kobj_map_init(base_probe, &chrdevs_lock);

       bdi_init(&directly_mappable_cdev_bdi);

}

 

chrdev_init函数比较简单了,初始化字符设备驱动,也就是向sysfs文件系统中注册,我就不详细分析了。

 

5.12.3 安装proc文件系统

回到start_kernel 679行,signals_init函数:

 

void __init signals_init(void)

{

       sigqueue_cachep = KMEM_CACHE(sigqueue, SLAB_PANIC);

}

 

很简单,建立数据结构sigqueueslab缓存。681行,page_writeback_init函数,将全局变量ratelimit_pages设置成vm_total_pages / (num_online_cpus() * 32)num_online_cpus()宏返回当前CPU可用数。而全局变量vm_total_pages是在前面build_all_zonelists函数中被初始化的。

 

683行,调用proc_root_init函数,初始化proc文件系统:

 

void __init proc_root_init(void)

{

       int err;

 

       proc_init_inodecache();

       err = register_filesystem(&proc_fs_type);

       if (err)

              return;

       proc_mnt = kern_mount_data(&proc_fs_type, &init_pid_ns);

       err = PTR_ERR(proc_mnt);

       if (IS_ERR(proc_mnt)) {

              unregister_filesystem(&proc_fs_type);

              return;

       }

 

       proc_symlink("mounts", NULL, "self/mounts");

 

       proc_net_init();

 

#ifdef CONFIG_SYSVIPC

       proc_mkdir("sysvipc", NULL);

#endif

       proc_mkdir("fs", NULL);

       proc_mkdir("driver", NULL);

       proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */

#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)

       /* just give it a mountpoint */

       proc_mkdir("openprom", NULL);

#endif

       proc_tty_init();

#ifdef CONFIG_PROC_DEVICETREE

       proc_device_tree_init();

#endif

       proc_mkdir("bus", NULL);

       proc_sys_init();

}

 

这个函数也很好理解,首先调用register_filesystem注册proc文件系统:

static struct file_system_type proc_fs_type = {

       .name             = "proc",

       .get_sb           = proc_get_sb,

       .kill_sb    = proc_kill_sb,

};

rootfs一样proc文件系统的proc_get_sbproc_fill_super函数/proc作为文件系统的跟目录赋给其super_blocks_root字段。然后proc_root_init会在/proc目录下建立一个符号链接文件和若干个子目录,具体的代码就不去管它了。

 

回到start_kernel中,CONFIG_CGROUPS没有被我们配置,所以685行的cgroup_init函数是一个空函数。同样,也没有配置CONFIG_CPUSETS686行的cpuset_init也是空函数。start_kernel的随后三个函数,taskstats_init_earlydelayacct_initcheck_bugs都是跟系统性能和缺陷测试相关的函数,我们这里略过。

 

看到692行,这里有个acpi_early_init函数,用于高级配置和电源管理接口(Advanced Configuration and Power Management InterfaceACPI)的初始化。如果对ACPI(注意不是APIC喔,不要搞混淆了)想做深入了解的同学可以先回到前面的setup_arch中,分析一下它的979 行的acpi_boot_table_init()函数和1023行的acpi_boot_init函数;然后再回过头来看这里的acpi_early_init()都干了些什么:在dynamic memory中申请ACPI tables的空间,然后从别处把它们copy过来;初试化所有ACPI相关的全局变量;从DSDT/SSDTs/PSDT表获取数据,构建ACPI namespace

 

回到start_kernel中,由于没有配置CONFIG_SFICONFIG_FTRACE_MCOUNT_RECORD,所以随后两行的sfi_initftrace_init是两个空函数。

你可能感兴趣的:(疯狂内核之系统初始化,struct,cache,null,system,file,list)