Linux启动过程概述
来源:互联网 发布:mcpe联机软件 编辑:程序博客网 时间:2024/05/16 10:33
第一步:加载BIOS
当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它。这是因为BIOS中包含了CPU的相关信息、设备启动顺序信息、硬盘信息、内存信息、时钟信息、PnP特性等等。在此之后,计算机心里就有谱了,知道应该去读取哪个硬件设备了。
第二步:读取MBR
众所周知,硬盘上第0磁道第一个扇区被称为MBR,也就是Master Boot Record,即主引导记录,它的大小是512字节,别看地方不大,可里面却存放了预启动信息、分区表信息。
系统找到BIOS所指定的硬盘的MBR后,就会将其复制到0×7c00地址所在的物理内存中。其实被复制到物理内存的内容就是Boot Loader,而具体到你的电脑,那就是lilo或者grub了。
第三步:BootLoader
Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核做好一切准备。
Boot Loader有若干种,其中Grub、Lilo和spfdisk是常见的Loader。
在“i386/boot”目录下,bootsect.S是生成引导扇区的汇编源码,它首先将自己拷贝到0x90000上,然后将紧接其后的setup部分拷贝到0x90200,将真正的内核代码拷贝到0x100000。以上拷贝动作都是以bootsect.S、setup.S以及vmlinux在磁盘上连续存放为前提的,也就是说,我们的bzImage文件或者是zImage文件是按照bootsect、setup、vmlinux这样的顺序组织,并存放于引导分区的首扇区的连续磁盘扇区之中。
bootsect.S完成加载动作后,就直接跳转到0x90200,这正是setup.S的程序入口。setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到0x90000-0x901FF内存中,这个地方正是bootsect.S存放的地方,这时它将系统参数覆盖。以后这些参数将由保护模式下的代码来读取。
除此之外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到0x100000的内核引导代码,BootLoader过程结束。
第四步:内核引导
内核引导入口:在i386系统中,当内核以bzImage的形式压缩,即大内核方式(__BIG_KERNEL__)压缩时就需要预先处理bootsect.S和setup.S,按照大核模式使用$(CPP)处理生成bbootsect.S和bsetup.S,然后再编译生成相应的.o文件,并使用"/i386/boot/compressed/build.c"生成的build工具,将实际的内核(未压缩的,含kernel中的head.S代码)与"/i386/boot/compressed"下的head.S和misc.c合成到一起,其中的head.S代替了"/i386/kernel/head.S"的位置,由Bootloader引导执行(startup_32入口),然后它调用misc.c中定义的decompress_kernel()函数,使用"lib/inflate.c"中定义的gunzip()将内核解压到0x100000,再转到其上执行"i386/kernel/head.S"中的startup_32代码。
Part 1:核心数据结构初始化
start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。 这些动作有的是公共的,有的则是需要配置的才会执行的。
在start_kernel()函数中,
· 输出Linux版本信息(printk(linux_banner))
· 设置与体系结构相关的环境(setup_arch())
· 页表结构初始化(paging_init())
· 使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init())
· 使用alpha_mv结构和entry.S入口初始化系统IRQ(init_IRQ())
· 核心进程调度器初始化(包括初始化几个缺省的Bottom-half,sched_init())
· 时间、定时器初始化(包括读取CMOS时钟、估测主频、初始化定时器中断等,time_init())
· 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,(parse_options())
· 控制台初始化(为输出信息而先于PCI初始化,console_init())
· 剖析器数据结构初始化(prof_buffer和prof_len变量)
· 核心Cache初始化(描述Cache信息的Cache,kmem_cache_init())
· 延迟校准(获得时钟jiffies与CPU主频ticks的延迟,calibrate_delay())
· 内存初始化(设置内存上下界和页表项初始值,mem_init())
· 创建和设置内部及通用cache("slab_cache",kmem_cache_sizes_init())
· 创建uidtaskcount SLAB cache("uid_cache",uidcache_init())
· 创建文件cache("files_cache",filescache_init())
· 创建目录cache("dentry_cache",dcache_init())
· 创建与虚存相关的cache("vm_area_struct","mm_struct",vma_init())
· 块设备读写缓冲区初始化(同时创建"buffer_head"cache用户加速访问,buffer_init())
· 创建页cache(内存页hash表初始化,page_cache_init())
· 创建信号队列cache("signal_queue",signals_init())
· 初始化内存inode表(inode_init())
· 创建内存文件描述符表("filp_cache",file_table_init())
· 检查体系结构漏洞(对于alpha,此函数为空,check_bugs())
· SMP机器其余CPU(除当前引导CPU)初始化(对于没有配置SMP的内核,此函数为空,smp_init())
· 启动init过程(创建第一个核心线程,调用init()函数,原执行序列调用cpu_idle() 等待调度,init())
至此start_kernel()结束,基本的核心环境已经建立起来了。
Part 2外设初始化
init()函数作为核心线程,首先锁定内核(仅对SMP机器有效),然后调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。过程如下:
· 总线初始化(比如pci_init())
· 网络初始化(初始化网络数据结构,包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,将调用protocols结构中包含的所有协议的初始化过程,sock_init())
· 创建bdflush核心线程(bdflush()过程常驻核心空间,由核心唤醒来清理被写过的内存缓冲区,当bdflush()由kernel_thread()启动后,它将自己命名为kflushd)
· 创建kupdate核心线程(kupdate()过程常驻核心空间,由核心按时调度执行,将内存缓冲区中的信息更新到磁盘中,更新的内容包括超级块和inode表)
· 设置并启动核心调页线程kswapd(为了防止kswapd启动时将版本信息输出到其他信息中间,核心线调用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_setup())
· 启动任何使用__initcall标识的函数(方便核心开发者添加启动函数,do_initcalls())
· 文件系统初始化(filesystem_setup())
· 安装root文件系统(mount_root())
至此do_basic_setup()函数返回init(),在释放启动内存段(free_initmem())并给内核解锁以后,init()打开/dev/console设备,重定向stdin、stdout和stderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。
init()函数到此结束,内核的引导部分也到此结束了,这个由start_kernel()创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体:
· start_kernel()本身所在的执行体,这其实是一个"手工"创建的线程,它在创建了init()线程以后就进入cpu_idle()循环了,它不会在进程(线程)列表中出现
· init线程,由start_kernel()创建,当前处于用户态,加载了init程序
· kflushd核心线程,由init线程创建,在核心态运行bdflush()函数
· kupdate核心线程,由init线程创建,在核心态运行kupdate()函数
· kswapd核心线程,由init线程创建,在核心态运行kswapd()函数
· keventd核心线程,由init线程创建,在核心态运行context_thread()函
- 打开终端线,并设置模式
- 输出登录界面及提示,接受用户名的输入
- 以该用户名作为login的参数,加载login程序
第五步:init进程和inittab引导指令
init进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空 间内加载init程序,它的进程号是1。
init程序需要读取/etc/inittab文件作为其行为指针,inittab是以行为单位的描述性(非执行性)文本,每一个指令行都具有以下格式:
id:runlevel:action:process其中id为入口标识符,runlevel为运行级别,action为动作代号,process为具体的执行程序。
id一般要求4个字符以内,对于getty或其他login程序项,要求id与tty的编号相同,否则getty程序将不能正常工作。
runlevel是init所处于的运行级别的标识,一般使用0-6以及S或s。0、1、6运行级别被系统保留,0作为shutdown动作,1作为重启至单用户模式,6为重启;S和s意义相同,表示单用户模式,且无需inittab文件,因此也不在inittab中出现,实际上,进入单用户模式时,init直接在控制台(/dev/console)上运行/sbin/sulogin。
在一般的系统实现中,都使用了2、3、4、5几个级别,在Redhat系统中,2表示无NFS支持的多用户模式,3表示完全多用户模式(也是最常用的级别),4保留给用户自定义,5表示XDM图形登录方式。7-9级别也是可以使用的,传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值,以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功才会执行。
initdefault是一个特殊的action值,用于标识缺省的启动级别;当init由核心激活 以后,它将读取inittab中的initdefault项,取得其中的runlevel,并作为当前的运行级 别。如果没有inittab文件,或者其中没有initdefault项,init将在控制台上请求输入 runlevel。
sysinit、boot、bootwait等action将在系统启动时无条件运行,而忽略其中的runlevel,其余的action(不含initdefault)都与某个runlevel相关。各个action的定义在inittab的man手册中有详细的描述。
第六步:rc启动脚本
一般情况下,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开头的脚本,在此之前,如果相应的脚本也存在K开头的链接,而且这些已经处于运行态了(以/var/lock/subsys/下的文件作为标志),则将首先启动K开头的脚本,以stop作为参数停止这些已经启动了的服务,然后再重新运行。显然,这样做的直接目的就是当init改变运行级别时,所有相关的服务都将重启,即使是同一个级别。
rc程序执行完后,系统环境已经设置好了,下面就该用户登录系统了。
第七步:getty和login
在rc返回后,init将得到控制,并启动mingetty。mingetty是getty的简化,不能处理串口操作。getty的功能一般包括:
- 打开终端线,并设置模式
- 输出登录界面及提示,接受用户名的输入
- 以该用户名作为login的参数,加载login程序
login程序在getty的同一个进程空间中运行,接受getty传来的用户名参数作为登录的用户名。
如果用户名不是root,且存在于/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。
只有/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。
当用户登录通过了这些检查后,login将搜索/etc/passwd文件(必要时搜索/etc/shadow文件)用于匹配密码、设置主目录和加载shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为/bin/sh。在将控制转交给shell之前,getty将输出/var/log/lastlog中记录的上次登录系统的信息,然后检查用户是否有新邮件(/usr/spool/mail{username})。在设置好shell的uid、gid以及term、path等环境变量以后,进程加载shell,login的任务也就完成了。
第八步:bash
运行级别3下的用户login以后,将启动一个用户指定的shell,以下以/bin/bash为例继续我们的启动过程。
bash是Bourne Shell的GNU扩展,除了继承了sh的所有特点外,还增加了很多特性和功能。由login启动的bash是作为一个登录shell启动的,它继承了getty设置的term、path等环境变量,其中path对于普通用户为"/bin;/usr/bin;/usr/local/bin",对于root为"/sbin;usr/sbin'/usr/bin"。作为登录shell,它将首先寻找/etc/profile脚本文件,并执行它;然后如果存在~/.bash_profile,则执行它,否则执行~/.bash_login,如果该文件也不存在,则执行~/.profile文件。然后bash将作为一个交互式shell执行~/.bashrc文件,很多系统中,~/.bashrc都将启动/etc/bashrc作为系统范围内的配置文件。
当显示出命令行提示符的时候,整个启动过程就结束了 。此时的系统,运行着内核,运行着几个核心进程,运行着init进程,运行着一批由rc启动脚本激活的守护进程(如inetd等),运行着一个bash作为用户的命令解释器。
- Linux启动过程概述
- Linux启动过程概述
- Linux内核启动过程概述
- Linux文件系统和启动过程概述
- Linux启动过程概述(X86)
- 计算机的启动过程概述linux
- WindowsXP启动过程概述
- Windows启动过程概述
- APK启动过程概述
- Xen启动过程概述
- Android启动过程概述
- 2.6.27.7启动过程概述
- uboot启动过程之概述
- S5PV210的启动过程概述
- s5pv210的启动过程概述
- linux启动流程概述
- Linux引导过程概述
- Linux 开机过程概述
- js玩具——UI组件:HtmlEventListener Html事件事件监听器
- Android开发实例详解之IMF(Android SDK Sample—SoftKeyboard)
- 使用模板时不要忘记把NULL实例化!
- android 软键盘 返回键 消失事件 监听
- 使用fat jar和exe4j把java程序打包成exe执行文件
- Linux启动过程概述
- ABAP中读取EXCEL中不同的SHEET数据
- 串行化方法
- 页面调用OCX(ActiveX)控件,自动下载、注册及 javascript对ActiveX的访问、控制和事件调用等。
- 打包,解包
- 范式哈夫曼算法的分析与实现(一)
- JDBC调用数据库的基本步骤
- 二叉排序树,完成创建节点,插入节点,删除节点,查找节点,中序遍历的功能
- flex4.5 下控制 skinClass 里某个图形的颜色