Linux2.6启动3--start_kernel篇 当内核与体系架构相关的汇编代码执行完毕,即跳入start_kernel。这个函数在kernel/init/main.c中。由于这部分涉及linux众多数据结构的初始化,包括内核命令行解析,内存缓冲区建立初始化,页面分配和初始化,虚拟文件系统建立,根文件系统挂载,驱动文件挂载,二进制程序文件的执行等,限于篇幅和理解水平,只能流程上的大致梳理,以上提及方面后期再做详细分析。为保证准确性,参考了一部分书籍和网上技术文档,如有疑问请及时提出,共同学习探讨。
asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __stop___param[]; //这里引用两个符号,是内核编译脚本定位的内核参数起始地址 smp_setup_processor_id();//多CPU架构的初始化,目前我们的高通linux侧是单核的,此多核不做分析 unwind_init();//本架构中没有用 lockdep_init();//本架构为空 debug_objects_early_init(); cgroup_init_early();
local_irq_disable(); early_boot_irqs_off(); early_init_irq_lock_class();
lock_kernel();//本架构为空函数 tick_init(); //时钟中断初始化函数,调用 clockevents_register_notifier 函数向 clockevents_chain 时钟事件链注册时钟控制函数 tick_notifier。这是个回调函数,指明了当时钟事件发生变化时应该执行的哪些操作,比如时钟的挂起操作等 boot_cpu_init();//用于多核CPU的初始化 page_address_init();//用于高地址内存,我们都用32位CPU,此函数为空 printk(KERN_NOTICE); printk(linux_banner); setup_arch(&command_line); //具体看一下这个架构初始化函数完成哪些功能 void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags;//定义了一个默认的内核参数列表 struct machine_desc *mdesc; char *from = default_command_line;
setup_processor();//汇编的CPU初始化部分已讲过,不再讨论 mdesc = setup_machine(machine_arch_type); machine_name = mdesc->name;
if (mdesc->soft_reboot) reboot_setup("s");
if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); //由于MMU单元已打开,此处需要而boot_params是物理地址,需要转换成虚拟地址才能访问,因为此时CPU访问的都是虚拟地址 /* * If we have the old style parameters, convert them to * a tag list. */ //内核参数列表第一项必须是ATAG_CORE类型 if (tags->hdr.tag != ATAG_CORE)//如果不是,则需要转换成新的内核参数类型,新的内核参数类型用下面struct tag结构表示 convert_to_tag_list(tags);//此函数完成新旧参数结构转换 struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; } u; }; //旧的内核参数列表用下面结构表示 struct param_struct { union { struct { unsigned long page_size; /* 0 */ unsigned long nr_pages; /* 4 */ unsigned long ramdisk_size; /* 8 */ unsigned long flags; /* 12 */ 。。。。。。。。。。。。//较长,省略 }
if (tags->hdr.tag != ATAG_CORE)//如果没有内核参数 tags = (struct tag *)&init_tags;//则选用默认的内核参数
if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo);//用内核参数列表填充meminfo
if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags);//解析内核参数列表,然后调用内核参数列表的处理函数对这些参数进行处理。比如,如果列表为命令行,则最终会用parse_tag_cmdlin函数进行解析,这个函数用_tagtable编译连接到了内核里 __tagtable(ATAG_CMDLINE, parse_tag_cmdline); } //下面是记录内核代码的起始,结束虚拟地址 init_mm.start_code = (unsigned long) &_text; init_mm.end_code = (unsigned long) &_etext; init_mm.end_data = (unsigned long) &_edata; init_mm.brk = (unsigned long) &_end; //下面是对命令行的处理,刚才在参数列表处理parse_tag_cmdline函数已把命令行拷贝到了from空间 memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '/0'; parse_cmdline(cmdline_p, from);//解析出命令行,命令行解析出以后,同样会调用相关处理函数进行处理。系统用__early_param宏在编译阶段把处理函数编译进内核。
paging_init(&meminfo, mdesc); //这个函数完成页表初始化,具体的方法为建立线性地址划分后每个地址空间的标志;清除在boot阶段建立的内核映射空间,也即把页表项全部清零;调用bootmem_init,禁止无效的内存节点,由于我们的物理内存都是连续的空间,因此,内存节点为1个。接下来判断INITRD映像是否存在,若存在则检查其所在的地址是否在一个有效的地址内,然后返回此内存节点号。 先看两个数据结构。 struct meminfo表示内存的划分情况。Linux的内存划分为bank。每个bank用 struct membank表示,start表示起始地址,这里是物理地址,size表示大小,node表示此bank所在的节点号,对于只有一个节点的内存,所有bank节点都相等 struct membank { unsigned long start; unsigned long size; int node; };
struct meminfo { int nr_banks; struct membank bank[NR_BANKS]; };
//在page_init函数中比较重要的是bootmem_init函数,此函数在完成原来映射页表的清除后,最终调用bootmem_init_node如下: bootmem_init_node(int node, int initrd_node, struct meminfo *mi) { unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES]; unsigned long start_pfn, end_pfn, boot_pfn; unsigned int boot_pages; pg_data_t *pgdat;// 每个节点用pg_data_t描述,这个结构用在非一致性内存中,我们的内存只有一个,地址是连续的 int i;
start_pfn = -1UL; end_pfn = 0; for_each_nodebank(i, mi, node) { struct membank *bank = &mi->bank[i]; unsigned long start, end;
start = bank->start >> PAGE_SHIFT;//计算出页表号,实际也表示第几个物理页号 end = (bank->start + bank->size) >> PAGE_SHIFT;
if (start_pfn > start) start_pfn = start; if (end_pfn < end) end_pfn = end;
map_memory_bank(bank);//将每个节点的每个bank重新映射,比如重新映射内核空间 } if (end_pfn == 0) return end_pfn; //一个字节代表8个页,因此找到一个 //可放置这些所有自己的页面即可。用一个bit位表示一个页是否已占用,那么一个字节为8个页,比如4096个页需要4096/8=512字节,容纳这个位图需要一个页 boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn); boot_pfn = find_bootmap_pfn(node, mi, boot_pages);//在node节点内存的bank中找到一个可以放置位图的页面的页面序列,然后返回这个页面序列的首个页面号 node_set_online(node);//设置本节点有效 pgdat = NODE_DATA(node);//获取节点描述符pgdat init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);//设置本节点内所有映射页的位图,即每个字节全部置为0xff,表示已经映射使用。然后填充pgdat结构
for_each_nodebank(i, mi, node) free_bootmem_node(pgdat, mi->bank[i].start, mi->bank[i].size);//设置每个映射的页面空闲,实际是对位图的操作,对每个bit清零
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT, boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT); //标示位图所占的页面被占用 if (node == 0) reserve_node_zero(pgdat);
#ifdef CONFIG_BLK_DEV_INITRD /* * If the initrd is in this node, reserve its memory. */ if (node == initrd_node) { int res = reserve_bootmem_node(pgdat, phys_initrd_start, phys_initrd_size, BOOTMEM_EXCLUSIVE); //INITRD映像占用的空间需要标示占用,INITRD是虚拟根文件系统,此时还未加载,因此挂载之前这个物理空间不能再被分配使用 if (res == 0) { initrd_start = __phys_to_virt(phys_initrd_start); initrd_end = initrd_start + phys_initrd_size; } else { printk(KERN_ERR "INITRD: 0x%08lx+0x%08lx overlaps in-use " "memory region - disabling initrd/n", phys_initrd_start, phys_initrd_size); } } #endif
/* * initialise the zones within this node. */ memset(zone_size, 0, sizeof(zone_size)); memset(zhole_size, 0, sizeof(zhole_size));
/* * The size of this node has already been determined. If we need * to do anything fancy with the allocation of this memory to the * zones, now is the time to do it. */ zone_size[0] = end_pfn - start_pfn; zhole_size[0] = zone_size[0]; for_each_nodebank(i, mi, node) zhole_size[0] -= mi->bank[i].size >> PAGE_SHIFT; //计算共有多少页空洞,注意,有些bank的起始结束地址并不是刚好4K对齐的,因此,可能存在某些空白页框。用节点总的物理页框减去每个bank页框,就得到页空洞 //这个函数里面主要完成zone区的初始化,linux内存管理将内存节点又分为ZONE区管理,比如ZONE_DMA和ZONE_NORMAL等,因此需要初始化。由于平台只针对一致性内存管理,即物理内存空间只包含DDR部分,此处很多函数是空的,再次略过 arch_adjust_zones(node, zone_size, zhole_size);
free_area_init_node(node, zone_size, start_pfn, zhole_size);
return end_pfn; } //在page_init的最后完成devicemaps_init初始化,比如中断向量的映射。映射的大致过程是,申请一个物理框,然后调用creat_map将此物理页框映射到0xffff0000.最后再调用struct machine_desc的map_io完成IO设备的映射 //在完成内存页映射后即进入request_standard_resources,这个函数比较简单,主要完成从iomem_resource空间申请所需的内存资源,比如内核代码和视频所需的资源等 request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP smp_init_cpus(); #endif
cpu_init();//此函数为空 init_arch_irq = mdesc->init_irq;//初始化与硬件体系相关的指针 system_timer = mdesc->timer; init_machine = mdesc->init_machine;
#ifdef CONFIG_VT #if defined(CONFIG_VGA_CONSOLE) conswitchp = &vga_con; #elif defined(CONFIG_DUMMY_CONSOLE) conswitchp = &dummy_con; #endif #endif early_trap_init();//重定位中断向量,将中断向量代码拷贝到中断向量页,并把信号处理代码指令拷贝到向量页中 } mm_init_owner(&init_mm, &init_task);//空函数 setup_command_line(command_line);//保存命令行,以备后用,此保存空间需申请 //这个函数调用完了,就开始执行下面初始化函数 unwind_setup();//空函数 setup_per_cpu_areas();//设置每个CPU信息,单核CPU为空函数 setup_nr_cpu_ids();//空函数 smp_prepare_boot_cpu(); //设置启动的CPU为在线状态.在多CPU架构下 //第一个启动的cpu启动到一定阶段后,开始启动其它的cpu,它会为每个后来启动的cpu创建一个0号进程,而这些0号进程的堆栈的thread_info结构中的cpu成员变量则依次被分配出来(利用alloc_cpu_id()函数)并设置好,这样当这些cpu开始运行的时候就有了自己的逻辑cpu号。 sched_init();//初始化调度器,对调度机制进行初始化,对每个CPU的运行队列
preempt_disable();//启动阶段系统比较脆弱,禁止进程调度 build_all_zonelists();//建立内存区域链表 page_alloc_init();//内存页初始化,此处无执行 printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); //执行命令行解析,若参数不存在,则调用unknown_bootoption if (!irqs_disabled()) { printk(KERN_WARNING "start_kernel(): bug: interrupts were " "enabled *very* early, fixing it/n"); local_irq_disable(); } sort_main_extable();//对异常处理函数进行排序 trap_init();//空函数 rcu_init();//linux2.6的一种互斥访问机制 init_IRQ();//中断向量初始化 pidhash_init();//进程嘻哈表初始化 init_timers();//定时器初始化 hrtimers_init();//高精度时钟初始化 softirq_init();//软中断初始化 timekeeping_init();//系统时间初始化 time_init(); sched_clock_init(); profile_init();//空函数 if (!irqs_disabled()) printk("start_kernel(): bug: interrupts were enabled early/n"); early_boot_irqs_on(); local_irq_enable(); console_init();//打印终端初始化 if (panic_later) panic(panic_later, panic_param);
lockdep_info(); locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && !initrd_below_start_ok && page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " "disabling it./n", page_to_pfn(virt_to_page((void *)initrd_start)), min_low_pfn); initrd_start = 0; } #endif vfs_caches_init_early();//建立节点嘻哈表和数据缓冲嘻哈表 cpuset_init_early();//空函数 mem_init();//对全局的物理页变量初始化,对没有分配的页面初始化 enable_debug_pagealloc(); cpu_hotplug_init();//没有热插拔CPU,此函数为空 kmem_cache_init();//内核内存缓冲区初始化 debug_objects_mem_init(); idr_init_cache();//创建idr缓冲区 setup_per_cpu_pageset();//采用的是一致性内存,此函数为空 numa_policy_init();//采用的是一致性内存,此函数为空 if (late_time_init) late_time_init(); calibrate_delay();//校准延时函数的精确度,实际上是校准loops_per_jiffy全局变量,即每个时钟滴答内CPU执行的指令数 pidmap_init();//进程号位图初始化,一般用一个page来指示所有的进程PID占用情况 pgtable_cache_init();//空函数 prio_tree_init();//初始化优先级数组 anon_vma_init();//空函数 #ifdef CONFIG_X86 if (efi_enabled) efi_enter_virtual_mode(); #endif thread_info_cache_init();//空函数 fork_init(num_physpages);//初始化kernel的fork()环境。Linux下应用程序执行是靠系统调用fork()完成,fork_init所完成的工作就是确定可以fork()的线程的数量,然后是初始化init_task进程 proc_caches_init();//为proc文件系统创建高速缓存 buffer_init();//空函数 unnamed_dev_init();//初始化一个虚拟文件系统使用的哑文件 key_init();//没有键盘则为空,如果有键盘,则为键盘分配一个高速缓存 security_init();//空函数 vfs_caches_init(num_physpages);//虚拟文件系统挂载,这个函数的详细说明如下
void __init vfs_caches_init(unsigned long mempages)//参数说明系统内存的物理页数 { unsigned long reserve;
reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1); mempages -= reserve; //创建一个高速缓存 names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
dcache_init();//在高速缓存中分配一个目录项,并初始化 inode_init();//在高速缓存中分配一个inode节点,并初始化 files_init(mempages);//初始化文件描述符,初始化全局文件状态变量 mnt_init(); bdev_cache_init();//如果编译阶段设置了块设备,则注册一个块设备文件系统 chrdev_init();//初始化字符设备管理数组cdev_map } // mnt_init()是创建根文件系统的关键,解释如下 void __init mnt_init(void) { unsigned u; int err;
init_rwsem(&namespace_sem); //创建一个虚拟文件系统的vfsmount结构缓存。每个挂载的文件系统都有一个 struct vfsmoun结构 mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount), 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL); //创建文件系统挂载嘻哈表 mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
if (!mount_hashtable) panic("Failed to allocate mount hash table/n");
printk("Mount-cache hash table entries: %lu/n", HASH_SIZE); //初始化嘻哈表 for (u = 0; u < HASH_SIZE; u++) INIT_LIST_HEAD(&mount_hashtable[u]);
err = sysfs_init();//创建一个sysfs虚拟文件系统,并挂载为根文件系统。如果系统不指定sysfs,则此函数为空 if (err) printk(KERN_WARNING "%s: sysfs_init error: %d/n", __func__, err); fs_kobj = kobject_create_and_add("fs", NULL);//创建一个对象文件,加到文件系统中 if (!fs_kobj) printk(KERN_WARNING "%s: kobj create error/n", __func__); init_rootfs();//注册一个rootfs文件系统 init_mount_tree();//将上面创建的rootfs文件系统挂载为根文件系统。这只是个虚拟的文件系统,就好比只是创建了一个/目录。最后,这个函数会为系统最开始的进程(即 init_task 进程)准备他的进程数据块中的namespace 域,主要目的是将 do_kern_mount() 函数中建立的 mnt 和 dentry 信息记录在了 init_task 进程的进程数据块中,这样任何以后从 init_task 进程 fork 出来的进程也都先天地继承了这一信息。 //下面是进行radix树初始化。这个是linux2.6引入的为各种页面操作的重要结构之一struct radix_tree_node。这种数据结构将指针与一个long型键值关联起来,提供高效快速查找。具体不再分析 radix_tree_init(); signals_init();//创建并初始化信号队列 /* rootfs populating might need page-writeback */ page_writeback_init();//CPU在内存中开辟高速缓存,CPU直接访问高速缓存提以高速度。当cpu更新了高速缓存的数据后,需要定期将高速缓存的数据写回到存储介质中,比如磁盘和flash等。这个函数初始化写回的周期 #ifdef CONFIG_PROC_FS proc_root_init();//如果配置了proc文件系统,则需初始化并加载proc文件系统。在根目录的proc文件夹就是proc文件系统,这个文件系统是ram类型的,记录系统的临时数据,系统关机后不会写回到flash中 #endif cgroup_init();//没有配置cgroup,此函数为空 cpuset_init();//单CPU,此函数为空 taskstats_init_early();//进程状态初始化,实际上就是分配了一个存储线程状态的高速缓存 delayacct_init();//空函数
check_bugs();//空函数
acpi_early_init();//空函数 rest_init();//start_kernel启动的最后一个函数,进入这个函数,完成剩余启动的初始化 }
// rest_init()大致解释如下: static void noinline __init_refok rest_init(void) __releases(kernel_lock) { int pid; //创建内核线程。Kernel_thread运用系统调用do_fork()产生新的子线程,子线程就调用传入的调用函数执行之,此处函数就是kernel_init. kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); numa_default_policy();//空函数 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); unlock_kernel();
/* * The boot idle thread must execute schedule() * at least once to get things moving: */ init_idle_bootup_task(current); preempt_enable_no_resched(); schedule(); preempt_disable();
/* Call into cpu_idle with preempt disabled */ cpu_idle(); } // kernel_init通过调用do_basic_setup完成编译阶段注册的设备驱动程序初始化。 //这个函数又调用了一个很重要的初始化函数Do_initcalls()。它用来启动所有在__initcall_start和__initcall_end段的函数,而静态编译进内核的modules也会将其入口放置在这段区间里。和根文件系统相关的初始化函数都会由rootfs_initcall()所引用。rootfs_initcall(populate_rootfs); 也就是说会在系统初始化的时候会调用populate_rootfs进行初始化。代码如下:
static int __init populate_rootfs(void) { char *err = unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start, 0); if (err) panic(err); if (initrd_start) { #ifdef CONFIG_BLK_DEV_RAM int fd; printk(KERN_INFO "checking if image is initramfs..."); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 1); if (!err) { printk(" it is/n"); unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 0); free_initrd(); return 0; } printk("it isn't (%s); looks like an initrd/n", err); fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); if (fd >= 0) { sys_write(fd, (char *)initrd_start, initrd_end - initrd_start); sys_close(fd); free_initrd(); } #else printk(KERN_INFO "Unpacking initramfs..."); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 0); if (err) panic(err); printk(" done/n"); free_initrd(); #endif } return 0; } //unpack_to_rootfs就是解压包,所解得包就是usr/initramfs_data.cpio.gz下的文件系统。然后将其释放至上面创建的rootfs。注意这个文件系统已经在编译的时候用build_in.O的方式一种是跟kernel融为一体了所在的段就是__initramfs_start至__initramfs_end的区域。这种情况下,直接调用unpack_to_rootfs将其释放到根目录.如果不是属于这种形式的。也就是由内核参数指定的文件系统,即image-initrd文件系统。如果配制CONFIG_BLK_DEV_RAM才会支持image-initrd。否则全当成cpio-initrd的形式处理。 对于是cpio-initrd的情况。直接将其释放到根目录。对于是image-initrd的情况。在根目录下建立/initrd.image文件,然后将INITRD写到这个文件中,并释放INITRD占用的空间。。 接下来,就开始具体的挂载操作,在kernel_init函数中完成
static int __init kernel_init(void * unused) { lock_kernel(); /* * init can run on any cpu. */ set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR); /* * Tell the world that we're going to be the grim * reaper of innocent orphaned children. * * We don't want people to have to make incorrect * assumptions about where in the task array this * can be found. */ init_pid_ns.child_reaper = current;
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
smp_init(); sched_init_smp();
cpuset_init_smp();
do_basic_setup();
if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; //如果存在指定的INITRD命令行参数,则执行命令行参数指定的init文件,如果不存在,则制定执行的命令为根目录下的init文件。如果用户指定的文件系统存在,则调用prepare_namespace();进行文件系统挂载的预操作 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; //在prepare_namespace()中首先会轮训检测块设备,若检测到则创建一个设备节点,然后分配一个设备号。如果saved_root_name不为空,则说明内核有指定设备作为根文件系统,则通过mount_block_root挂载根文件系统,然后退出即可。 比如有时指定了内核参数root=/dev/ram,则直接从这个位置进行挂载。 如果没有指定的块设备作为根文件系统,而是指明了INITRD映像,则调用initrd_load 函数挂载initram文件系统。这个函数首先创建/dev/ram设备节点,然后把映像拷贝到这个设备文件中,接着调用handle_initrd对INITRD进行处理。 在prepare_namespace()执行最后调用mount_root();将指定的文件系统挂接到/root下,然后切换当前目录到root下。再者,还需调用sys_mount(".", "/", NULL, MS_MOVE, NULL);将当前目录挂接为/根目录。
prepare_namespace(); } init_post(); return 0; }
static void __init handle_initrd(void) { int error; int pid;
real_root_dev = new_encode_dev(ROOT_DEV);//真正的根文件节点 create_dev("/dev/root.old", Root_RAM0);//创建一个设备节点,设备号是Root_RAM0,因此这个节点对应是/dev/ram的INITRD /* mount initrd on rootfs' /root */ //将此设备挂接到/root下 mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY); sys_mkdir("/old", 0700); root_fd = sys_open("/", 0, 0);//记录根目录的文件描述符 old_fd = sys_open("/old", 0, 0);//记录old目录的文件描述符 /* move initrd over / and chdir/chroot in initrd root */ sys_chdir("/root");//切换至root目录,刚才已经挂载了/dev/root.old sys_mount(".", "/", NULL, MS_MOVE, NULL);//将当前目录挂载为根文件系统,也就是/dev/root.old变成现在的根文件系统 sys_chroot(".");//切换到当前文件系统的根目录 current->flags |= PF_FREEZER_SKIP; //创建一个进程,运行目前文件系统下的/linuxrc文件 pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); if (pid > 0) while (pid != sys_wait4(-1, NULL, 0, NULL)) yield();
current->flags &= ~PF_FREEZER_SKIP;
/* move initrd to rootfs' /old */ sys_fchdir(old_fd); sys_mount("/", ".", NULL, MS_MOVE, NULL); //处理完上面的文件,则initrd处理完之后,重新chroot进入rootfs /* switch root and cwd back to / of rootfs */ sys_fchdir(root_fd); sys_chroot("."); sys_close(old_fd); sys_close(root_fd); //如果real_root_dev在 linuxrc中重新设成Root_RAM0,则initrd就是最终的realfs了,改变当前目录到initrd中,不作后续处理直接返回。 if (new_decode_dev(real_root_dev) == Root_RAM0) { sys_chdir("/old"); return; } //否则需要重新挂载上面linuxRC文件执行时指定的根文件系统 ROOT_DEV = new_decode_dev(real_root_dev); mount_root();
printk(KERN_NOTICE "Trying to move old root to /initrd ... "); error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL); if (!error) printk("okay/n"); else { int fd = sys_open("/dev/root.old", O_RDWR, 0); if (error == -ENOENT) printk("/initrd does not exist. Ignored./n"); else printk("failed/n"); printk(KERN_NOTICE "Unmounting old root/n"); sys_umount("/old", MNT_DETACH); printk(KERN_NOTICE "Trying to free ramdisk memory ... "); if (fd < 0) { error = fd; } else { error = sys_ioctl(fd, BLKFLSBUF, 0); sys_close(fd); } printk(!error ? "okay/n" : "failed/n"); } } static int noinline init_post(void) { free_initmem(); unlock_kernel(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console./n");
(void) sys_dup(0); (void) sys_dup(0);
current->signal->flags |= SIGNAL_UNKILLABLE; //刚才上面已经初始化了ramdisk_execute_command,此处可直接运行之。 如果不存在则运行下面程序,如果都不存在,则退出。 下面的/sbin/init就是上述挂载的根文件系统下的文件。 if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s/n", ramdisk_execute_command); } if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults.../n", execute_command); } run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel."); } //当没有找到init程序后,则退出,进行进程调度,进入cpu_idle()进程 |
|