在Linux移植之tag参数列表解析过程分析中已经将内核传入的各个参数的处理过程大概的讲述了。root=/dev/mtdblock3参数的解析分析到最后发现它被存储在了saved_root_name中,最后发现是prepare_namespace函数调用了它。调用它的作用是为了挂接根文件系统,什么是挂接根文件系统?这个需要深入了解文件系统相关代码才能知道,我的简单理解是:内核一开始是在RAM中运行的,内核为了调用存储在flash中的程序而需要将flash的内容以某种格式读出来,存放到RAM中运行。所以需要指定flash中程序的地址,这个地址就是根文件系统存放的地址。这就是需要挂接根文件系统的原因。
这一篇主要就是讲述prepare_namespace怎么样挂接根文件系统。先看下start_kernel的程序调用层次。
start_kernel setup_arch //解析UBOOT传入的启动参数 setup_command_line //解析UBOOT传入的启动参数 do_early_param //解析early参数,uboot中没传这个参数 unknown_bootoption//解析到了命令行参数,saved_root_name在这块初始化 console_init();//控制台初始化 rest_init kernel_thread kernel_init prepare_namespace //解析命令行参数解析成功挂接在哪个分区 mount_root//挂接根文件系统 init_post //执行应用程序
进入rest_init函数分析,它位于init\main.c下:
426 static void noinline __init_refok rest_init(void) 427 __releases(kernel_lock) 428 { 429 int pid; 430 431 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);//创建kernel_init内核进程 pid=1 432 numa_default_policy(); 433 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd内核进程 pid=2 434 kthreadd_task = find_task_by_pid(pid); 435 unlock_kernel(); 436 437 /* 438 * The boot idle thread must execute schedule() 439 * at least one to get things moving: 440 */ 441 preempt_enable_no_resched(); 442 schedule(); 443 preempt_disable(); 444 445 /* Call into cpu_idle with preempt disabled */ 446 cpu_idle(); 447 }
可以看到rest_init函数首先创建了一个kernel_init线程,进入kernel_init线程函数,它位于init\main.c中,截取部分内容
787 static int __init kernel_init(void * unused) 788 { ... ... ... 826 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { 827 ramdisk_execute_command = NULL; 828 prepare_namespace();//挂接文件系统 829 } ... ... ... 838 }
直接看到prepare_namespace函数,它的字面意思是准备名字空间,其实它的功能就是挂接文件系统,它位于init\do_mounts.c中
414 void __init prepare_namespace(void) 415 { 416 int is_floppy; 417 418 if (root_delay) { 419 printk(KERN_INFO "Waiting %dsec before mounting root device...\n", 420 root_delay); 421 ssleep(root_delay); 422 } 423 424 /* wait for the known devices to complete their probing */ 425 while (driver_probe_done() != 0) 426 msleep(100); 427 428 md_run_setup(); 429 430 if (saved_root_name[0]) {//如果saved_root_name里面有内容,里面的内容是从uboot参数传过来的 431 root_device_name = saved_root_name;//絪aved_root_name的内容拷贝到boot_device_name中 432 if (!strncmp(root_device_name, "mtd", 3)) {//如果root_device_name内容的前面三个字符是mtd 433 mount_block_root(root_device_name, root_mountflags);//挂接root_device_name区的文件系统 434 goto out;//挂接完退出 435 } 436 ROOT_DEV = name_to_dev_t(root_device_name);//将root_device_name与设备号ROOT_DEV联系起来 437 if (strncmp(root_device_name, "/dev/", 5) == 0)//如果root_device_name前5个字符是/dev/ 438 root_device_name += 5;//去掉/dev/字符,找到mtd开头的字符 439 } 440 441 is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; 442 443 if (initrd_load()) 444 goto out; 445 446 if (is_floppy && rd_doload && rd_load_disk(0)) 447 ROOT_DEV = Root_RAM0; 448 449 mount_root();//根据ROOT_DEV设备号挂接到/dev/root设备或者网络文件系统 450 out: 451 sys_mount(".", "/", NULL, MS_MOVE, NULL); 452 sys_chroot("."); 453 security_sb_post_mountroot(); 454 }
总结一下这个函数的功能就是将saved_root_name的内容处理之后得到一个ROOT_DEV设备号,然后mount_root函数根据这个设备号ROOT_DEV创建/dev/root设备然后内核挂接这个文件系统。再看一下mount_root函数,它同样位于init\do_mounts.c中
382 void __init mount_root(void) 383 { 384 #ifdef CONFIG_ROOT_NFS//挂接网络文件系统 385 if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {//如果需要挂接网络文件系统 386 if (mount_nfs_root()) 387 return; 388 389 printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n"); 390 ROOT_DEV = Root_FD0; 391 } 392 #endif 393 #ifdef CONFIG_BLK_DEV_FD 394 if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) { 395 /* rd_doload is 2 for a dual initrd/ramload setup */ 396 if (rd_doload==2) { 397 if (rd_load_disk(1)) { 398 ROOT_DEV = Root_RAM1; 399 root_device_name = NULL; 400 } 401 } else 402 change_floppy("root floppy"); 403 } 404 #endif 405 #ifdef CONFIG_BLOCK 406 create_dev("/dev/root", ROOT_DEV);//创建dev/root设备,设备号为ROOT_DEV 407 mount_block_root("/dev/root", root_mountflags);//挂接到/dev/root设备 408 #endif 409 }
这个函数可以根据ROOT_DEV的值判断是否挂接网络文件系统。若不挂接的话,那么创建/dev/root设备节点,然后将内核挂接到/dev/root设备,它指向的是mtdblock3块设备。浅尝辄止,至于设备的挂接过程与原理后面深入了解了源码再去分析。