例如,我电脑上的grub启动项如下(在/boot/grub/grub.lst中):
title Fedora (2.6.35.10-74.fc14.i686) root (hd0,0) kernel /vmlinuz-2.6.35.10-74.fc14.i686 ro root=/dev/mapper/VolGroup-lv_root rd_LVM_LV=VolGroup/lv_root rd_LVM_LV=VolGroup/lv_swap rd_NO_LUKS rd_NO_MD rd_NO_DM LANG=zh_CN.UTF-8 KEYBOARDTYPE=pc KEYTABLE=us rhgb quiet initrd /initramfs-2.6.35.10-74.fc14.i686.img内核的执行参数可以控制内核的行为,比如ro参数告诉内核,以只读方式挂载根分区,而quiet则告诉内核,启动的时候不要打印任何信息。这些参数不光影响内核的执行,大多数的发行版也使用这些参数控制启动完毕以后后续的动作。这些参数可以在任何时候从/proc/cmdline 这个文件中获得。现在,grub找到了内核(hd0,0)/boot/vmlinuz-2.6.35.10-74.fc14.i686,它将整个电脑的控制权交给了这个程序,内核开始进行各种初始化的动作,你可以将quiet参数去掉,以便看看内核都做了哪些事情,也可以在系统启动成功以后,使用dmesg这个命令查看内核启动的时候,都打印了哪些东西。
Offset/Size Proto Name Meaning 01F1/1 ALL(1 setup_sects The size of the setup in sectors 01F2/2 ALL root_flags If set, the root is mounted readonly 01F4/4 2.04+ syssize The size of the 32-bit code in 16-byte paras 01F8/2 ALL ram_size DO NOT USE - for bootsect.S use only 01FA/2 ALL vid_mode Video mode control 01FC/2 ALL root_dev Default root device number 01FE/2 ALL boot_flag 0xAA55 magic number 0200/2 2.00+ jump Jump instruction 0202/4 2.00+ header Magic signature "HdrS" 0206/2 2.00+ version Boot protocol version supported 0208/4 2.00+ realmode_swtch Boot loader hook (see below) 020C/2 2.00+ start_sys_seg The load-low segment (0x1000) (obsolete) 020E/2 2.00+ kernel_version Pointer to kernel version string 0210/1 2.00+ type_of_loader Boot loader identifier 0211/1 2.00+ loadflags Boot protocol option flags 0212/2 2.00+ setup_move_size Move to high memory size (used with hooks) 0214/4 2.00+ code32_start Boot loader hook (see below) 0218/4 2.00+ ramdisk_image initrd load address (set by boot loader) 021C/4 2.00+ ramdisk_size initrd size (set by boot loader) 0220/4 2.00+ bootsect_kludge DO NOT USE - for bootsect.S use only 0224/2 2.01+ heap_end_ptr Free memory after setup end 0226/1 2.02+ ext_loader_ver Extended boot loader version 0227/1 2.02+ ext_loader_type Extended boot loader ID 0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line 022C/4 2.03+ ramdisk_max Highest legal initrd address 0230/4 2.05+ kernel_alignment Physical addr alignment required for kernel 0234/1 2.05+ relocatable_kernel Whether kernel is relocatable or not 0235/1 2.10+ min_alignment Minimum alignment, as a power of two 0236/2 N/A pad3 Unused 0238/4 2.06+ cmdline_size Maximum size of the kernel command line 023C/4 2.07+ hardware_subarch Hardware subarchitecture 0240/8 2.07+ hardware_subarch_data Subarchitecture-specific data 0248/4 2.08+ payload_offset Offset of kernel payload 024C/4 2.08+ payload_length Length of kernel payload 0250/8 2.09+ setup_data 64-bit physical pointer to linked list of struct setup_data 0258/8 2.10+ pref_address Preferred loading address 0260/4 2.10+ init_size Linear memory required during initialization每个域的具体细节可参考boot.txt文档。
BOOTSEG = 0x07C0 /* 引导扇区的原始地址 */ SYSSEG = 0x1000 /* 历史的载入地址>>4 */ #ifndef SVGA_MODE #define SVGA_MODE ASK_VGA #endif #ifndef RAMDISK #define RAMDISK 0 #endif #ifndef ROOT_RDONLY #define ROOT_RDONLY 1 #endif .code16 .section ".bstext", "ax" .global bootsect_start bootsect_start: # 使开始地址正常化 ljmp $BOOTSEG, $start2 start2: movw %cs, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss xorw %sp, %sp sti cld movw $bugger_off_msg, %si msg_loop: lodsb andb %al, %al jz bs_die movb $0xe, %ah movw $7, %bx int $0x10 jmp msg_loop bs_die: # 允许用户按一个键,然后重启 xorw %ax, %ax int $0x16 int $0x19 # 0x19中断绝不会返回,无论它做什么 # 调用BIOS复位代码,便CPU工作在实模式下 ljmp $0xf000,$0xfff0 .section ".bsdata", "a" bugger_off_msg: .ascii "Direct booting from floppy is no longer supported.\r\n" .ascii "Please use a boot loader program instead.\r\n" .ascii "\n" .ascii "Remove disk and press any key to reboot . . .\r\n" .byte 0 # 下面设置内核的一些属性,setup需要。这是header的第一部分,来自以前的boot sector .section ".header", "a" .globl hdr hdr: setup_sects: .byte 0 /* 被build.c填充 */ root_flags: .word ROOT_RDONLY syssize: .long 0 /* 被build.c填充 */ ram_size: .word 0 /* 已过时 */ vid_mode: .word SVGA_MODE root_dev: .word 0 /* 被build.c填充 */ boot_flag: .word 0xAA55 # 偏移512处,setup的入口点 .globl _start _start: # Explicitly enter this as bytes, or the assembler # tries to generate a 3-byte jump here, which causes # everything else to push off to the wrong offset. .byte 0xeb # short (2-byte) jump .byte start_of_setup-1f 1: # header的第二部分,来自以前的setup.S:设置头部header,包括大量的bootloader参数,如header版本、内核版本字符串指针、bootloader类型、 # 内核装载时的很多标志、堆栈尾部地址指针、内核命令行地址指针和大小、32位保护模式入口地址、ramdisk地址和大小等 code32_start: # 这里对32位的代码,装载器可以设置可设置一个不同的入口地址 .long 0x100000 # 0x100000 = 为大内核的默认入口地址(保护模式) # ............ (省略) # End of setup header ##################################################### .section ".entrytext", "ax" start_of_setup: #ifdef SAFE_RESET_DISK_CONTROLLER # 重置磁盘控制器 movw $0x0000, %ax # 重置磁盘控制器 movb $0x80, %dl # 所有的的磁盘控制器All disks int $0x13 #endif # ............(省略) # 让%ss无效,创建一个新的栈 movw $_end, %dx testb $CAN_USE_HEAP, loadflags jz 1f movw heap_end_ptr, %dx 1: addw $STACK_SIZE, %dx jnc 2f xorw %dx, %dx # Prevent wraparound 2: # 现在%dx应该指向我们栈空间的尾部 andw $~3, %dx # dword对齐 jnz 3f movw $0xfffc, %dx # 确保不是0 3: movw %ax, %ss movzwl %dx, %esp # 清除%esp的上半部分 sti # 现在我们应该有一个工作空间 # 我们将进入%cs=%ds+0x20,设置好%cs pushw %ds pushw $6f lretw 6: # 在setup终止时检查签名 cmpl $0x5a5aaa55, setup_sig jne setup_bad # 对BSS(Block Started by Symbol)清零 movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl # 跳转到C代码(不会返回) calll main # ............(省略)由setup.ld中的ENTRY(_start)可知,_start汇编例程是bzImage内核映像开始执行的入口点,即引导扇区之后的开始处(偏移512字节处),它会准备大量的bootloader参数。最后的call main跳转到arch/x86/boot/main.c:main()函数处执行,这就是众所周知的main函数,它们都工作在实模式下。main函数先调用copy_boot_params函数把位于第一个扇区的参数复制到boot_params变量中,boot_params位于setup的数据段,然后调用链为arch/x86/boot/pm.c:go_to_protected_mode(void) --->arch/x86/boot/pmjump.S:protected_mode_jump()。
void __init mnt_init(void) { unsigned u; int err; init_rwsem(&namespace_sem); 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(); 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(); init_mount_tree(); }这里fs/ramfs/inode.c:init_rootfs()会调用fs/filesystems.c:register_filesystem()注册rootfs。然后fs/namespace.c:init_mount_tree()调用fs/super.c:do_kern_mount()在内核中挂载rootfs,调用fs/fs_struct.c:set_fs_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; }从上面的代码中的可以看出,这个rootfs的dentry对象的名字为"/",这就是我们看到的根目录"/"。
static int __init kernel_init(void * unused) { /* ......(省略) */ do_basic_setup(); /* Open the /dev/console on the rootfs, this should never fail */ 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); /* * check if there is an early userspace init. If yes, let it do all * the work */ if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace(); } /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ init_post(); return 0; }kernel_init会先调用do_basic_setup,这是一个很关键的函数。在此之前CPU子系统运行起来了,内存管理和进程管理也启动了,到do_basic_setup才开始做真正实际的工作。所有直接编译在kernel中的模块都是由它启动的。代码如下:
static void __init do_basic_setup(void)
{
init_workqueues();
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
driver_init();
init_irq_proc();
do_ctors();
do_initcalls();
}
do_initcalls()用来启动所有在__initcall_start和__initcall_end段之间的函数,而静态编译进内核的模块会将其初始化函数放置在这段区间里。其中与rootfs相关的初始化函数都会由rootfs_initcall()所引用。在init/initramfs.c中就有rootfs_initcall(populate_rootfs)的引用,这是用来初始化rootfs的,因此do_initcall()最终会调用到populate_rootfs()。需要特别指出的是initramfs.c模块的入口函数populate_rootfs()是否执行取决于Kernel的编译选项,参考init/Makefile,内核编译时必须配置CONFIG_BLK_DEV_INITRD选项才会执行这个函数。代码如下:
static int __init populate_rootfs(void) { char *err = unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start); if (err) panic(err); /* Failed to decompress INTERNAL initramfs */ if (initrd_start) { #ifdef CONFIG_BLK_DEV_RAM int fd; printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n"); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); if (!err) { free_initrd(); return 0; } else { clean_rootfs(); unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start); } printk(KERN_INFO "rootfs image is not initramfs (%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...\n"); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); if (err) printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err); free_initrd(); #endif } return 0; }(1)第一行的upack_to_rootfs()用来把内核映像中的initramfs释放到rootfs。它实际上有两个功能,一个是检测是否是属于cpio包,另外一个就是解压并释放cpio包。注意如果__initramfs_start和__initramfs_end的值相等,则initramfs长度为零,unpack_to_rootfs()不会做任何处理,直接返回。
void __init prepare_namespace(void) { int is_floppy; if (root_delay) { printk(KERN_INFO "Waiting %dsec before mounting root device...\n", root_delay); ssleep(root_delay); } /* * wait for the known devices to complete their probing * * Note: this is a potential source of long boot delays. * For example, it is not atypical to wait 5 seconds here * for the touchpad of a laptop to initialize. */ wait_for_device_probe(); md_run_setup(); if (saved_root_name[0]) { root_device_name = saved_root_name; if (!strncmp(root_device_name, "mtd", 3) || !strncmp(root_device_name, "ubi", 3)) { mount_block_root(root_device_name, root_mountflags); goto out; } ROOT_DEV = name_to_dev_t(root_device_name); if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } if (initrd_load()) goto out; /* wait for any asynchronous scanning to complete */ if ((ROOT_DEV == 0) && root_wait) { printk(KERN_INFO "Waiting for root device %s...\n", saved_root_name); while (driver_probe_done() != 0 || (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0) msleep(100); async_synchronize_full(); } is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; if (is_floppy && rd_doload && rd_load_disk(0)) ROOT_DEV = Root_RAM0; mount_root(); out: devtmpfs_mount("dev"); sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); }(1)对于将根文件系统存放到USB或者SCSI设备上的情况,Kernel需要等待这些耗费时间比较久的设备驱动加载完毕,所以这里存在一个Delay。
int __init initrd_load(void) { if (mount_initrd) { create_dev("/dev/ram", Root_RAM0); /* * Load the initrd data into /dev/ram0. Execute it as initrd * unless /dev/ram0 is supposed to be our actual root device, * in that case the ram disk is just set up here, and gets * mounted in the normal path. */ if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); handle_initrd(); return 1; } } sys_unlink("/initrd.image"); return 0; }(1)mount_initrd表示是否使用了image-initrd。可以通过kernel的参数“noinitrd“来配置mount_initrd的值,默认为1。很少看到有项目区配置该值,所以一般情况下,mount_initrd的值应该为1。
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); /* mount initrd on rootfs' /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); /* move initrd over / and chdir/chroot in initrd root */ sys_chdir("/root"); sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); /* * In case that a resume from disk is carried out by linuxrc or one of * its children, we need to tell the freezer not to wait for us. */ current->flags |= PF_FREEZER_SKIP; 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); /* switch root and cwd back to / of rootfs */ sys_fchdir(root_fd); sys_chroot("."); sys_close(old_fd); sys_close(root_fd); if (new_decode_dev(real_root_dev) == Root_RAM0) { sys_chdir("/old"); return; } 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"); } }(1)real_root_dev为一个全局变量,用来保存放用户指定的根设备号。
static noinline int init_post(void) __releases(kernel_lock) { /* ...... */ 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. " "See Linux Documentation/init.txt for guidance."); }注意run_init_process在调用相应程序运行的时候,用的是kernel_execve。也就是说调用进程会替换当前进程。只要上述任意一个文件调用成功,就不会返回到这个函数。如果上面几个文件都无法执行。打印出没有找到init文件的错误。运行用户空间中的init进程可能是以下几种情况:
下面给出内核映像完整的启动过程:
arch/x86/boot/header.S: --->header第一部分(以前的bootsector.S): 载入bootloader到0x7c00处,设置内核属性 --->_start() bzImage映像的入口点(实模式),header的第二部分(以前的setup.S) --->code32_start=0x100000 0x100000为解压后的内核的载入地址(1M高端地址) --->设置大量的bootloader参数、创建栈空间、检查签名、清空BSS --->arch/x86/boot/main.c:main() 实模式内核的主函数 --->copy_boot_params() 把位于第一个扇区的参数复制到boot_params变量中,boot_params位于setup的数据段 --->检查内存布局、设置键盘击键重复频率、查询Intel SpeedStep(IST)信息 --->设置视频控制器模式、解析命令行参数以便传递给decompressor --->arch/x86/boot/pm.c:go_to_protected_mode() 进入保护模式 --->屏蔽PIC中的所有中断、设置GDT和IDT --->arch/x86/boot/pmjump.S:protected_mode_jump(boot_params.hdr.code32_start,...) 跳转到保护模式 --->in_pm32() 跳转到32位保护模式的入口处(即0x100000处) --->jmpl *%eax 跳转到arch/i386/boot/compressed/head_32.S:startup_32()处执行 arch/i386/boot/compressed/head_32.S:startup_32() 保护模式下的入口函数 --->leal boot_stack_end(%ebx), %esp 设置堆栈 --->拷贝压缩的内核到缓冲区尾部 --->清空BSS --->compressed/misc.c:decompress_kernel() 解压内核 --->lib/decompress_bunzip2.c:decompress() --->lib/decompress_bunzip2.c:bunzip2() --->lib/decompress_bunzip2.c:start_bunzip() 解压动作 --->parse_elf() 将解压后的内核ELF文件(.o文件)解析到内存中 --->计算vmlinux编译时的运行地址与实际装载地址的距离 --->jmp *%ebp 跳转到解压后的内核的arch/x86/kernel/head_32.S:startup_32()处运行 arch/x86/kernel/head_32.S:startup_32() 32位内核的入口函数,即进程0(也称为清除进程) --->拷贝boot_params以及boot_command_line --->初始化页表:这会创建PDE和页表集 --->开启内存分页功能 --->为可选的浮点单元(FPU)检测CPU类型 --->head32.c:i386_start_kernel() --->init/main.c:start_kernel() Linux内核的启动函数,包含创建rootfs,加载内核模块和cpio-initrd --->很多初始化操作 --->setup_command_line() 把内核启动参数复制到boot_command_line数组中 --->parse_early_param() 体系结构代码会先调用这个函数,做时期的参数检查 --->parse_early_options() --->do_early_param() 检查早期的参数 --->parse_args() 解析模块的参数 --->fs/dcache.c:vfs_caches_init() 创建基于内存的rootfs(一个VFS) --->fs/namespace.c:mnt_init() --->fs/ramfs/inode.c:init_rootfs() --->fs/filesystems.c:register_filesystem() 注册rootfs --->fs/namespace.c:init_mount_tree() --->fs/super.c:do_kern_mount() 在内核中挂载rootfs --->fs/fs_struct.c:set_fs_root() 将rootfs配置为当前内存中的根文件系统 --->rest_init() --->arch/x86/kernel/process.c:kernel_thread(kernel_init,...) 启动一个内核线程来运行kernel_init函数,进行内核初始化 --->cpu_idle() 进入空闲循环 --->调度器周期性的接管控制权,提供多任务处理 init/main.c:kernel_init() 内核初始化过程入口函数,加载initramfs或cpio-initrd,或传统的image-initrd,把工作交给它 --->sys_open("/dev/console",...) 启动控制台设备 --->do_basic_setup() --->do_initcalls() 启动所有静态编译进内核的模块 --->init/initramfs.c:populate_rootfs() 初始化rootfs --->unpack_to_rootfs() 把initramfs或cpio-initrd解压释放到rootfs --->如果是image-initrd则拷贝到/initrd.image ####################################### 传统的image-initrd情形 ########################################### --->rootfs中没有/init文件 --->do_mounts.c:prepare_namespace() 加载image-initrd,并运行它的/linuxrc文件,以挂载实际的文件系统 --->do_mounts_initrd.c:initrd_load() 把image-initrd数据加载到默认设备/dev/ram0中 --->do_mounts_rd.c:rd_load_image() 加载image-initrd映像 --->identify_ramdisk_image() 识别initrd,确定是romfs、squashfs、minix,还是ext2 --->crd_load() 解压并为ramdisk分配空间,计算循环冗余校验码 --->lib/inflate.c:gunzip() 对gzip格式的ramdisk进行解压 --->do_mounts_initrd.c:handle_initrd() 指定的根设备不是/dev/ram0,由initrd来挂载真正的根文件系统 --->mount_block_root("/dev/root.old",...) 将initrd挂载到rootfs的/root下 --->arch/x86/kernel/process.c:kernel_thread(do_linuxrc, "/linuxrc",...) 启动一个内核线程来运行do_linuxrc函数 --->do_mounts_initrd.c:do_linuxrc() --->arch/x86/kernel/sys_i386_32.c:kernel_execve() 运行image-initrd中的/linuxrc --->将initrd移动到rootfs的/old下 --->若在linuxrc中根设备重新设成Root_RAM0,则返回,说明image-initrd直接作为最终的根文件系统 --->do_mounts.c:mount_root() 否则将真正的根文件系统挂载到rootfs的/root下,并切换到这个目录下 --->mount_block_root() --->do_mount_root() --->fs/namespace.c:sys_mount() 挂载到"/root" --->卸载initrd,并释放它的内存 --->do_mounts.c:mount_root() 没有指定另外的根设备,则initrd直接作为真正的根文件系统而被挂载 --->fs/namespace.c:sys_mount(".", "/",...) 根文件挂载成功,移动到根目录"/" ######################################################################################################## --->init/main.c:init_post() 启动用户空间的init进程 --->run_init_process(ramdisk_execute_command) 若加载了initramfs或cpio-initrd,则运行它的/init --->run_init_process("/sbin/init") 否则直接运行用户空间的/sbin/init --->arch/x86/kernel/sys_i386_32.c:kernel_execve() 运行用户空间的/sbin/init程序,并分配pid为1 --->run_init_process("/bin/sh") 当运行init没成功时,可用此Shell来代替,以便恢复机器 /init cpio-initrd(或initramfs)中的初始化脚本,挂载真正的根文件系统,启动用户空间的init进程 --->export PATH=/sbin:/bin:/usr/sbin:/usr/bin 设置cpio-initrd的环境变量$PATH --->挂载procfs、sysfs --->解析命令行参数 --->udevd --daemon --resolve-names=never 启动udev --->/initqueue/*.sh 执行/initqueue下的脚本完成对应初始化工作(现在该目录下为空) --->/initqueue-settled/*.sh 执行/initqueue-settled下的脚本(现在该目录下为空) --->/mount/*.sh 挂载真正的根文件系统 --->/mount/99mount-root.sh 根据/etc/fstab中的选项挂载根文件系统 --->/lib/dracut-lib.sh 一系列通用函数 --->把根文件系统挂载到$NEWROOT下 --->寻找真正的根文件系统中的init程序并存放在$INIT中 /sbin/init, /etc/init, /bin/init, 或/bin/sh --->从/proc/cmdline中获取启动init的参数并存放在$initargs中 --->switch_root "$NEWROOT" "$INIT" $initargs 切换到根分区,并启动其中的init进程注意kernel_evecve调用的是与具体体系平台相关的实现,但它是一个通用的系统调用,在linux/syscalls.h中声明,这个头文件中声明了与体系结构无关的所有系统调用接口。只不过kernel_evecve在实现时是与体系结构相关的,每种体系结构都要提供它的实现。
如果从体系结构无关的视角来看,start_kernel()可以看作时体系结构无关的Linux main函数,它是体系结构无关的代码的统一入口函数,这也是为什么文件会命名为init/main.c的原因。这个main.c粘合剂把各种体系结构的代码“粘合”到一个统一的入口处。
整个内核启动过程如下图:
图1 Linux内核启动过程