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} |
函数首先根据传递进来的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} |
建立dentry和dentry_hashtable,但是由于前面先建立了一个dentry_hashtable,所以这里就只建立dentry的slab缓存。继续走,vfs_caches_init的2369行,来自fs/inode.c的inode_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跟前面差不多,建立inode和inode_hashtable,但是由于前面先建立了一个inode_hashtable,所以这里就只建立inode的slab缓存。vfs_caches_init的2370行,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先建立file的slab缓存,然后根据可用页面数量初始化全局files_stat_struct类型变量files_stat。继续走,vfs_caches_init的2371行,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行,建立vfsmount的slab缓存,初始化全局mount_hashtable散列,初始化sysfs合rootfs文件系统。rootfs文件系统在初始化阶段很重要,是第一个文件系统,根文件系统,所以接下来我们就来详细看看2348行和2349行两个函数都干了些什么。
在安装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_rootfs的312行调用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_rootfs的316行调用register_filesystem(&rootfs_fs_type)将rootfs的file_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_type的get_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初始化rootfs的super_block结构,随后会调用传递进来的ramfs_fill_super函数参数。这个函数继续初始化rootfs的super_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_tree中,2307行,为进程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中的一个最小的文件系统作为根安装。接下来,在这个初始根文件系统中的程序探测系统的硬件(例如,它们判断硬盘是否是EIDE、SCSI等等),装入所有必需的内核模块,并从物理块设备重新安装根文件系统。
来看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文件系统中注册,我就不详细分析了。
回到start_kernel 的679行,signals_init函数:
void __init signals_init(void) { sigqueue_cachep = KMEM_CACHE(sigqueue, SLAB_PANIC); } |
很简单,建立数据结构sigqueue的slab缓存。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_sb会proc_fill_super函数,把/proc作为文件系统的跟目录赋给其super_block的s_root字段。然后proc_root_init会在/proc目录下建立一个符号链接文件和若干个子目录,具体的代码就不去管它了。
回到start_kernel中,CONFIG_CGROUPS没有被我们配置,所以685行的cgroup_init函数是一个空函数。同样,也没有配置CONFIG_CPUSETS,686行的cpuset_init也是空函数。start_kernel的随后三个函数,taskstats_init_early、delayacct_init和check_bugs都是跟系统性能和缺陷测试相关的函数,我们这里略过。
看到692行,这里有个acpi_early_init函数,用于高级配置和电源管理接口(Advanced Configuration and Power Management Interface,ACPI)的初始化。如果对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_SFI和CONFIG_FTRACE_MCOUNT_RECORD,所以随后两行的sfi_init和ftrace_init是两个空函数。