《linux启动过程综述》由会员分享,可在线阅读,更多相关《linux启动过程综述(6页珍藏版)》请在金锄头文库上搜索。
1、Linux 启动过程综述BootloaderLinux Bootloader 被编译后置于引导扇区(ARC 的分区首扇区或 SRM 的磁盘 0扇区),得到控制后初始化一些数据结构,再将控制转给“main.c”中的 start_kernel(), start_kernel()向控制台输出一些提示,调用pal_init()初始化 PAL 代码,调用 openboot() 打开引导设备(通过读取Firmware 环境),调用 load()将核心代码加载到 START_ADDR(见 “include/asm-alpha/system.h”),再将 Firmware 中的核心引导参数加载到ZERO_PA
2、GE(0) 中,最后调用 runkernel()将控制转给 0x100000 的kernel,bootloader 部分结束。Bootloader 中使用的所有“srm_”函数在“arch/alpha/lib/”中定义。以上这种 Boot 方式是一种最简单的方式,即不需其他工具就能引导 Kernel,前提是按照 Makefile 的指导,生成 bootimage 文件,内含以上提到的bootloader 以及 vmlinux,然后将 bootimage 写入自磁盘引导扇区始的位置中。当采用 MILO 这样的引导程序来引导 Linux 时,不需要上面所说的Bootloader,而只需要 vmli
3、nux 或 vmlinux.gz,引导程序会主动解压加载内核到 0x1000(小 核心数据结构初始化-内核引导第一部分start_kernel()中调用了一系列初始化函数,以完成 kernel 本身的设置。这些动作有的是公共的,有的则是需要配置的才会执行的。在 start_kernel()函数中, 输出 Linux 版本信息(printk(linux_banner)) 设置与体系结构相关的环境(setup_arch()) 页表结构初始化(paging_init()) 使用arch/alpha/kernel/entry.S中的入口点设置系统自陷入口(trap_init()) 使用 alpha_m
4、v 结构和 entry.S 入口初始化系统 IRQ(init_IRQ()) 核心进程调度器初始化(包括初始化几个缺省的 Bottom-half,sched_init()) 时间、定时器初始化(包括读取 CMOS 时钟、估测主频、初始化定时器中断等,time_init()) 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,(parse_options()) 控制台初始化(为输出信息而先于 PCI 初始化,console_init()) 剖析器数据结构初始化(prof_buffer 和 prof_len 变量) 核心 Cache 初始化(描述 Cache 信息的 Cache,
5、kmem_cache_init()) 延迟校准(获得时钟 jiffies 与 CPU 主频 ticks 的延迟,calibrate_delay()) 内存初始化(设置内存上下界和页表项初始值,mem_init()) 创建和设置内部及通用cache(slab_cache,kmem_cache_sizes_init()) 创建 uid taskcount SLAB cache(uid_cache,uidcache_init()) 创建文件 cache(files_cache,filescache_init()) 创建目录 cache(dentry_cache,dcache_init()) 创建与虚
6、存相关的cache(vm_area_struct,mm_struct,vma_init()) 块设备读写缓冲区初始化(同时创建buffer_headcache 用户加速访问,buffer_init()) 创建页 cache(内存页 hash 表初始化,page_cache_init()) 创建信号队列 cache(signal_queue,signals_init()) 初始化内存 inode 表(inode_init()) 创建内存文件描述符表(filp_cache,file_table_init()) 检查体系结构漏洞(对于 alpha,此函数为空,check_bugs()) SMP 机器
7、其余 CPU(除当前引导 CPU)初始化(对于没有配置 SMP 的内核,此函数为空,smp_init()) 启动 init 过程(创建第一个核心线程,调用 init()函数,原执行序列调用 cpu_idle() 等待调度,init()) 至此 start_kernel()结束,基本的核心环境已经建立起来了。 外设初始化-内核引导第二部分init()函数作为核心线程,首先锁定内核(仅对 SMP 机器有效),然后调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。过程如下: 总线初始化(比如 pci_init()) 网络初始化(初始化网络数据结构,包括 sk_init()、s
8、kb_init()和proto_init()三部分,在 proto_init()中,将调用 protocols 结构中包含的所有协议的初始化过程,sock_init()) 创建 bdflush 核心线程(bdflush()过程常驻核心空间,由核心唤醒来清理被写过的内存缓冲区,当 bdflush()由 kernel_thread()启动后,它将自己命名为 kflushd) 创建 kupdate 核心线程(kupdate()过程常驻核心空间,由核心按时调度执行,将内存缓冲区中的信息更新到磁盘中,更新的内容包括超级块和inode 表) 设置并启动核心调页线程 kswapd(为了防止 kswapd 启
9、动时将版本信息输出到其他信息中间,核心线调用 kswapd_setup()设置 kswapd 运行所要求的环境,然后再创建 kswapd 核心线程) 创建事件管理核心线程(start_context_thread()函数启动context_thread()过程,并重命名为 keventd) 设备初始化(包括并口 parport_init()、字符设备 chr_dev_init()、块设备 blk_dev_init()、SCSI 设备 scsi_dev_init()、网络设备net_dev_init()、磁盘初始化及分区检查等等,device_setup()) 执行文件格式设置(binfmt_s
10、etup()) 启动任何使用_initcall 标识的函数(方便核心开发者添加启动函数,do_initcalls()) 文件系统初始化(filesystem_setup()) 安装 root 文件系统(mount_root()) 至此 do_basic_setup()函数返回 init(),在释放启动内存段(free_initmem())并给内核解锁以后,init()打开/dev/console 设备,重定向 stdin、stdout 和stderr 到控制台,最后,搜索文件系统中的 init 程序(或者由 init=命令行参数指定的程序),并使用 execve()系统调用加载执行 init
11、程序。 init()函数到此结束,内核的引导部分也到此结束了,这个由 start_kernel()创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体: start_kernel()本身所在的执行体,这其实是一个手工创建的线程,它在创建了 init()线程以后就进入 cpu_idle()循环了,它不会在进程(线程)列表中出现 init 线程,由 start_kernel()创建,当前处于用户态,加载了 init 程序 kflushd 核心线程,由 init 线程创建,在核心态运行 bdflush()函数 kupdate 核心线程,由 init 线程创建,在核心态运行 k
12、update()函数 kswapd 核心线程,由 init 线程创建,在核心态运行 kswapd()函数 keventd 核心线程,由 init 线程创建,在核心态运行 context_thread()函数 init 进程和 inittab 引导指令init 进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载 init 程序,它的进程号是 1。init 程序需要读取/etc/inittab 文件作为其行为指针,inittab 是以行为单位的描述性(非执行性)文本,每一个指令行都具有以下格式:id:runlevel:action:process 其中 id 为入口标识
13、符,runlevel 为运行级别,action 为动作代号,process 为具体的执行程序。id 一般要求 4 个字符以内,对于 getty 或其他 login 程序项,要求 id 与 tty的编号相同,否则 getty 程序将不能正常工作。runlevel 是 init 所处于的运行级别的标识,一般使用 06 以及 S 或s。0、1、6 运行级别被系统保留,0 作为 shutdown 动作,1 作为重启至单用户模式,6 为重启;S 和 s 意义相同,表示单用户模式,且无需 inittab 文件,因此也不在 inittab 中出现,实际上,进入单用户模式时,init 直接在控制台(/dev/
14、console)上运行/sbin/sulogin。在一般的系统实现中,都使用了 2、3、4、5 几个级别,在 Redhat 系统中,2表示无 NFS 支持的多用户模式,3 表示完全多用户模式(也是最常用的级别),4 保留给用户自定义,5 表示 XDM 图形登录方式。79 级别也是可以使用的,传统的 Unix 系统没有定义这几个级别。runlevel 可以是并列的多个值,以匹配多个运行级别,对大多数 action 来说,仅当 runlevel 与当前运行级别匹配成功才会执行。initdefault 是一个特殊的 action 值,用于标识缺省的启动级别;当 init 由核心激活以后,它将读取 i
15、nittab 中的 initdefault 项,取得其中的runlevel,并作为当前的运行级别。如果没有 inittab 文件,或者其中没有initdefault 项,init 将在控制台上请求输入 runlevel。sysinit、boot、bootwait 等 action 将在系统启动时无条件运行,而忽略其中的 runlevel,其余的 action(不含 initdefault)都与某个 runlevel 相关。各个 action 的定义在 inittab 的 man 手册中有详细的描述。rc 启动脚本上一节已经提到 init 进程将启动运行 rc 脚本,这一节将介绍 rc 脚本具体
16、的工作。一般情况下,rc 启动脚本都位于/etc/rc.d 目录下,rc.sysinit 中最常见的动作就是激活交换分区,检查磁盘,加载硬件模块,这些动作无论哪个运行级别都是需要优先执行的。仅当 rc.sysinit 执行完以后 init 才会执行其他的 boot或 bootwait 动作。如果没有其他 boot、bootwait 动作,在运行级别 3 下,/etc/rc.d/rc 将会得到执行,命令行参数为 3,即执行/etc/rc.d/rc3.d/目录下的所有文件。rc3.d下的文件都是指向/etc/rc.d/init.d/目录下各个 Shell 脚本的符号连接,而这些脚本一般能接受 start、stop、restart、status 等参数。rc 脚本以 start参数启动所有以 S 开头的脚本,在此之前,如果相应的脚本也存