Skip to content

Latest commit

 

History

History
360 lines (216 loc) · 22.8 KB

2.12_20170419_linux系统启动流程.md

File metadata and controls

360 lines (216 loc) · 22.8 KB

Linux系统启动流程

一个简单的操作系统的组成

一个操作系统(linux)主要由内核(kernel)和根文件系统(rootfs)组成,系统运行后的某一时刻,不是在执行内核代码就是在rootfs上某个路径的某个应用程序的用户代码。系统输出的调用叫做系统调用,库所输出的调用叫做库调用,当把程序源代码编译成二进制文件后,调用的是二进制格式的库文件,所以开发环境的接口和运行环境的接口不完全相同。程序员写程序时虽然要发起系统调用,但通常不是直接调用系统调用,而是调用把系统调用再次封装后的,离用户更近的库调用(glibc)。

glibc是一个标准库,是开发接口,头文件说明了glibc中有哪些库,每一个库的内部有多少可调用函数,每一个函数的名字和接受的参数等。所以开发过程中应该有库文件、头文件,然后才可以编译,编译完成后,当需要依赖于某一库时,就直接调用二进制格式的库,将其装载到共享内存中,程序就可以运行了。实际上glibc已经是属于用户空间中的内容了。glibc在/{lib|lib64}、/usr/{lib|lib64}、/usr/local/{lib|lib64}目录中。

库是函数的集合,函数的英文是function,实际上一个函数就是一个功能,函数有调用接口,还可以接受参数。库也是二进制程序,没有执行入口,所以不能直接运行,只能被调用后运行。库被调用后可以产生返回值也可以不产生返回值,一般来说函数调用(function)有返回值,过程调用(procedure)没有返回值。

内核的主要功能有进程管理、内存管理、网络管理、驱动程序、文件系统、安全功能。

根文件系统中最重要的就是二进制文件,其他的类似帮助文件统统没有也可以正常运行,所以操作系统最核心的就是内核和用户空间的二进制程序。

linux内核

在内核设计中有两种流派,一种是微内核,一种是单内核。单内核是指把所有功能做成一个程序,各功能以线程来协同工作。而微内核是将每一个功能都是用一个独立的子系统来实现,子系统在一个框架下统一调配。Linux是单内核设计,Windows和Solaris是微内核设计的。

linux虽然是单内核设计,但却借鉴了微内核的思想,linux将内核的不同功能做成了功能模块,内核的模块也可以被调用,和库被用户程序调用一样,内核模块可以被内核调用,内核模块也被称为内核对象(kernel object),是以.ko结尾的文件。linux的内核还支持模块的动态装载和卸载,这样只保留内核的核心功能,其他的功能模块需要时加载,内核的核心模块的大小就小的很多了,模块化的另外一个好处就是将硬件驱动程序作为一个内核模块不会影响整个内核的稳定和安全。所以linux的内核就由核心文件和模块文件组成,还有一个非必要的模块叫做rmdisk。

内核的核心文件在/boot目录下vmlinuz-VERSION-release,模块文件在/lib/modules/VERSION-release,内核模块的目录名必须和内核核心文件的版本号发行号完全匹配。模块间也会存在依赖关系,所以在模块文件目录中也会有文件存储依赖关系。

rmdisk是为了解决在系统启动时,需要读取硬盘但又无法驱动硬盘的问题,rmdisk是一个虚拟的根文件系统,存放了驱动硬盘必要的文件。rmdisk和内核在同一个位置上,当内核被装载进内存时,rmdisk也被装载到内存。rmdisk是用户安装操作系统最后一步生成的,在centos5是使用mkintrd工具生成的,rmdisk在centos5中模拟的是硬盘(/boot/initrd-VERSION-release.img),在centos6之后使用的是dracut工具生成的,模拟的是文件系统(/boot/initramfs-VERSION-release.img)。内核的特性之一就是使用缓冲和缓存来加速系统运行,当使用在内存中的虚拟硬盘,内核仍然认为硬盘慢,会将文件再一次放到内存中,所以在centos6之后,改为了文件系统。

从动态角度看内核,使用uname可以查看内核信息,uname -n可以显示节点名称,-r选项可以查看内核的版本号和发行号。如果查看模块相关的信息使用lsmod,显示由核心已经装载的内核模块,显示的内容来自于/proc/modules文件。使用modinfo可以显示模块的详细描述信息。

modinfo [-k kernel] [ modulename|filename]
-n:只显示模块文件路径
-p:显示模块参数
-d:description

modprobe可以装载和卸载内核模块

装载模块:modprobe [ -C config-file ]  [ modulename ]  [ module parame-ters... ]
         配置文件:/etc/modprobe.conf, /etc/modprobe.d/*.conf
卸载模块:modprobe [ -r ] modulename...

另外insmod和rmmod也可以装载和卸载模块,和modprobe有区别的是,这两个命令不会解决模块的依赖关系。

insmod [ filename ]  [ module options... ]
rmmod [ modulename ]

depmod是内核模块依赖关系文件及系统信息映射文件的生成工具。在内核模块的路径下有一个dep的文件,存储了内核模块间的依赖关系,为了方便查询,还有一个dep.bin的二进制程序,这也是真正用到的文件。

内核把内部状态信息及统计信息,以及可配置参数通过proc伪文件系统加以输出,参数有只读和可写两种,只读参数是用来输出信息的,可写参数可接受用户指定“新值”来实现对内核某功能或特性的配置,可以参数一般在/proc/sys,使用sysctl命令可以查看或设定此目录中诸多参数。默认配置文件在/etc/sysctl.conf

设置某参数sysctl -w parameter=VALUE        eg.sysctl -w kernel.hostname=jetv.com
通过读取配置文件设置参数sysctl -p [/path/to/conf_file]

实际上这是伪文件系统,并不能真正的编辑文件,可以通过sysctl或者echo命令修改“文件”。echo命令通过重定向的方式也可以修改大多数参数的值。

echo "VALUE" > /proc/sys/path/to/parameter
eg.echo "jetv.com" > /proc/sys/kernel/hostname

内核的路由转发的功能在/proc/sys/net/ipv4/ip_forward,只有两值0和1,0表示关闭,1表示开启。内核还有几个参数vm.drop_caches、kernel.hostname。

系统上除了proc目录,还有另外一个伪文件系统:/sys。输出内核识别出的各硬件设备的相关属性信息,也有内核对硬件特性的设定信息;有些参数是可以修改的,用于调整硬件工作特性。内核访问设备是通过驱动直接访问的,而用户访问设备是通过设备文件的。udev通过此路径下输出的信息动态为各设备创建所需要设备文件,udev是运行在用户空间的程序,udev为设备创建设备文件时,会读取其事先定义好的规则文件,一般在/etc/udev/rules.d及/usr/lib/udev/rules.d目录下;类似的工具还有udevadmin和hotplug。

ramdisk文件的制作

(1)mkinitrd命令

为当前正在使用的内核重新制作ramdisk文件。mkinitrd /boot/initramfs-$(uname -r).img $(uname -r)

(2)dracut命令

为当前正在使用的内核重新制作ramdisk文件。dracut /boot/initramfs-$(uname -r).img $(uname -r)

CentOS系统启动流程

1.加电自检(POST):加电自检用来检查各硬件是否正常。加电自检的过程是通过主板上ROM芯片(CMOS)所定义的一些程序来实现的,CMOS可以做一些设定,是在通过基本输入输出系统(BIOS)实现的。

2.引导加载次序(BOOT Sequence):按BIOS设定的引导次序依次加载设备,第一个有引导程序的设备即为本次启动的设备,没有boot loader无法引导,会跳过此设备 。

对linux而言有两种引导加载器,早些时候使用的是LILO(Linux Loader),现在在安卓上又有了用武之处,现在在桌面系统和服务器上使用的是GRUB(Grand Unified Boot Loader),它支持启动Windows、linux,Unix。GRUB有两个版本,分为Grub Legacy和grub2,二代是完全重写的,在centos5和6中使用的一代grub;centos7中使用的是二代grub。

Boot Loader就是用来找到操作系统所在的磁盘分区,并把内核解压并加载到内存的指定空间中,最后将控制权转交给内核。对于硬盘来说,Boot Loader就在硬盘的MBR中,MBR中前446字节存放bootloader,64字节是磁盘分区表(fat)和两字节标记符。

GRUB运行有两个阶段,第一个阶段是运行MBR中的Boot Loader,目的是为了找到硬盘中的第二阶段,第二阶段运行存放在硬盘上的某个分区上的程序。如果第二块硬盘有分区,那么grub就必须能要能识别文件系统才能继续运行,那么在第一阶段和第二阶段之间就要运行1.5阶段。在硬盘上/boot/grub中有各种文件可以用来grub驱动文件系统,但1.5阶段需要的文件也在硬盘上,实际上在系统安装时,分区的文件系统就已经确定了,所以需要的/boot/grub的某个文件就在第一阶段的mbr中的某一段位置上了。

3.内核初始化:内核探测各种可识别到的硬件并加载(可能借助于rmdisk)硬件驱动程序、以只读方式挂载根文件系统。以只读的方式的方式挂载根文件系统是因为fsck可以安全地对根文件系统做检查。

4.init初始化:内核初始化后,就该进行用户空间的初始化,如何初始化就由init的配置文件决定了。在centos5中,init使用的是Sys V,init的配置文件在/etc/inittab中;在centos6中,使用的是upstart,init的配置文件在/etc/inittab/和/etc/init/*.conf;在centos7中,init的"配置文件"在/usr/lib/systemd/system和/etc/systemd/system中。

以centos5为例:

系统运行级别是为了系统的运行或维护等应用目的而设定的,一般分为0-6七个级别,默认级别为3或5,运行级别的改变都是通过init应用程序进行的,切换级别使用init 级别数字,查看级别使用runlevel(centos7不支持),也可以使用who -r。

这七个级别分别是:

0:关机

1:单用户模式single,维护模式,以root用户登录,无需密码

2:多用户模式,维护模式,会启动网络功能,不启动网络文件系统(NFS)

3:多用户模式,正常模式,默认为文本界面

4:预留级别,同3级别

5:多用户模式,图形界面

6:重启

init的配置文件

/etc/inittab每一行定义一种action以及与之对应的process。格式为id:runlevel:action:process

inittab的部分内容:

id是一行的标识,runlevel表示对应的process会在那个级别上启动,action指明process运行的模式,process是执行的操作。

常用的action

  • wait:切换至此级别运行一次;
  • respawn:此process终止就重新启动;
  • initdefault:设定默认级别,process省略;
  • sysinit:设定系统初始化方式,一般指定为/etc/rc.d/rc.sysinit

当init开始启动,读取配置文件/etc/inittab,第一行id:5:initdefault:,说明了系统的默认运行级别,随后si::sysinit:/etc/rc.d/rc.sysinit,运行级别为空代表所有级别,设定了运行级别后,就会运行rc.sysinit的所有脚本,这个process基本运行许多的系统初始化任务。接下来就是运行指定级别下的脚本如l3:3:wait:/etc/rc.d/rc 3

系统初始化脚本应该做的任务有:设置主机名、设置欢迎信息、激活udev和selinux、挂载/etc/fstab文件中中定义的文件系统、检测根文件系统,并以读写方式重新挂载根文件系统、设置系统时钟、激活swap设备、根据/etc/sysctl.conf文件设置内核参数、激活lvm及软raid、加载额外设备的驱动程序,清理操作。

实际上rc是脚本,后面的数字是传递给脚本的参数,rc 3就意味着要读取/etc/rc.d/rc3.d/的所有文件。这里面的文件是以K和S开头,中间有两位数字,后有服务名的文件,先按顺序读取K开头的文件,并且传递stop参数,数字越小越优先执行,因此被依赖的应该后执行,所以数字就大一些。然后以S开头的文件按顺序读取,并传递start参数。

如果想要把一个服务开机自启动就可以在正常级别(默认级别)的目录下创建一个s开头的链接文件。每一个脚本在每一个级别下都有一个链接文件,可以是s开头也可能成k开头,这取决于系统的运行级别。

chkconfig可以管理链接文件,也可以查看指定的服务在某个级别是否运行,chkconfig --list可以列出所有的服务在所有级别的设定;

这些服务脚本在开头有一行是chkconfig: - 23 84,第一段表示使用chkconfig时,在哪些级别设置为s,-表示在所有级别都不设置为s,如果手动修改了,第二个是s开头的数字,第三个字段是以k开头脚本的字段。另外upstart格式会更详细,两种格式都可以被chkconfig使用。

SysV的服务脚本放置于/etc/rc.d/init.d(/etc/init.d),要想设置脚本在各级别的开启和关闭要使用chkconfig --add name,表示在7个级别的目录中分别创建链接文件,是s还是k文件有脚本内的设置有关。

使用chkconfig [--level levels] name <on|off|reset>修改指定的链接类型。省略level时表示2345。

需要注意的是,如果运行的是正常级别的话,会有一个特殊的服务最后启动是S99local,没有链接至/etc/rc.d/init.d的一个脚本,而是指向了/etc/rc.d/rc.local,这个文件就是一个脚本,可以把不便写成脚本的服务并且想要开机自动运行的命令写在这里。还有一个文件也指向/etc/rc.d/rc.local,就是/etc/rc.local。

接下来启动六个虚拟终端,虚拟终端只在2345级别下运行,级别1运行的是物理终端。mingetty会自动调用login程序。有图形界面的还会启动启动图形界面的服务。

Linux的账号验证程序是login,login会接收mingetty传来的用户名作为用户名参数。然后login会对用户名进行分析:如果用户名不是root,且存在 “/etc/nologin” 文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。只有 “/etc/securetty” 中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。”/etc/usertty” 文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。

在分析完用户名后,login将搜索 “/etc/passwd” 以及 “/etc/shadow” 来验证密码以及设置账户的其它信息,比如:主目录是什么、使用何种shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为 “/bin/bash”。

login程序成功后,会向对应的终端在输出最近一次登录的信息(在 “/var/log/lastlog” 中有记录),并检查用户是否有新邮件(在 “/usr/spool/mail/” 的对应用户名目录下)。然后开始设置各种环境变量:对于bash来说,系统首先寻找 “/etc/profile” 脚本文件,并执行它;然后如果用户的主目录中存在 .bash_profile 文件,就执行它,在这些文件中又可能调用了其它配置文件,所有的配置文件执行后后,各种环境变量也设好了,这时会出现大家熟悉的命令行提示符,到此整个启动过程就结束了。

对于CentOS6:

CentOS6的init程序使用的是Upstart,由ubuntu研发,但是依然命名为init,配置文件在/etc/inittab,但实际上upstart使用的是/etc/init/*.conf的多个文件,这些conf的文件语法遵循upstart配置文件的语法格式。

在centos6中,建立终端端是由配置文件/etc/init/tty.conf, /etc/init/serial.conf和/etc/sysconfig/init等配置文件来完成的。在2、3、4、5的运行级别中都将以respawn方式运行mingetty程序,mingetty程序能打开终端、设置模式。同时它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户身份。

Boot Loader-Grub

grub是boot loader的一种,分为grub 0.x和1.x的版本,其中0.x的版本又称为grub legacy,grub1.x又称为grub2,grub2是完全重写的与grub legacy完全不同。

grub legacy的运行分为三个阶段,第一个阶段是运行安装在硬盘上MBR中的boot loader,第二阶段是运行在MBR后一段扇区中的程序,为了让第一阶段的BootLoader能识别第三阶段所在的分区上的文件系统,第三阶段是运行在磁盘分区(/boot/grub/)中的程序。gurb的配置文件在/boot/grub/grub.conf,他还有一个符号链接在etc/grub.conf。

在第三阶段所在这个分区,不仅有grub真正执行的二进制程序,还存放了系统内核文件和ramdisk,这通常放置于一个基本磁盘分区。

grub提供的功能:

  1. 提供菜单并提供交互式接口:e编辑模式,用于实现编辑菜单;c命令模式,交互式接口
  2. 加载用户选择的内核或操作系统:允许传递参数给内核,还可以隐藏此菜单
  3. 为菜单提供保护机制:为编辑菜单进行认证和为启用内核或操作系统进行认证

grub通过hd(磁盘数字,分区数字)来识别硬盘 第一个数字是磁盘编号,用数字表示,从0开始编号,第二个数字是分区编号,用数字表示,用0开始编号。 思考:grub分区和根要不要在同一个分区?

grub命令行接口

  • help:获取帮助列表
  • help KEYWORD:获取指定命令的帮助
  • find (hd#,#) /path/to/file:定位某个文件,确定是否存在
  • root(hd#,#):指明根设备,指明根设备后,find就无须指明搜索的磁盘号和分区号
    • kernel /path/to/kernel file:设定本次启动时用到的内核文件;额外还可以添加许多内核支持使用的cmdline参数;例如:init=/path/to/init, selinux=0
  • initrd /PATH/TO/INITRAMFS_FILE: 设定为选定的内核提供额外文件的ramdisk;必须与内核版本号完全匹配
  • boot: 引导启动选定的内核;

手动在grub命令行接口启动系统:

grub> root (hd#,#)
grub> kernel /vmlinuz-VERSION-RELEASE ro root=/dev/DEVICE 
grub> initrd /initramfs-VERSION-RELEASE.img
grub> boot

,grub的显示菜单依赖于配置文件,配置文件在/boot/grub/grub.conf,常用的配置项有:

  • default=#: 设定默认启动的菜单项;落单项(title)编号从0开始;

  • timeout=#:指定菜单项等待选项选择的时长;

  • splashimage=(hd#,#)/PATH/TO/XPM_PIC_FILE:指明菜单背景图片文件路径;

  • hiddenmenu:隐藏菜单;

  • password [--md5] STRING: 菜单编辑认证;(grub-md5-crypt命令可以生成密码串)

  • title TITLE:定义菜单项“标题”, 可出现多次;

    ​ root (hd#,#):grub查找stage2及kernel文件所在设备分区;为grub的“根”;

    ​ kernel /PATH/TO/VMLINUZ_FILE [PARAMETERS]:启动的内核

    ​ initrd /PATH/TO/INITRAMFS_FILE: 内核匹配的ramfs文件;

    ​ password [--md5] STRING: 启动选定的内核或操作系统时进行认证;

进入单用户模式:

(1) 编辑grub菜单(选定要编辑的title,而后使用e命令); (2) 在选定的kernel后继续编辑,附加1, s, S或single都可以,回车完成编辑; (3) 在kernel所在行,键入“b”命令;

安装grub:

(1) grub-install:完整安装,各个阶段都安装 ​ grub-install --root-directory=/目录 /dev/DISK (2) grub ​ grub> root (hd#,#),指明根 ​ grub> setup (hd#),安装第一阶段

练习:

		1、新加硬盘,提供直接单独运行bash系统;
		2、破坏本机grub stage1,而后在救援模式下修复之;
		3、为grub设备保护功能;

编译内核

编译内核的前提:

(1) 准备好开发环境;

(2) 获取目标主机上硬件设备的相关信息;

(3) 获取到目标主机系统功能的相关信息,例如要启用的文件系统;

(4) 获取内核源代码包www.kernel.org

对于开发环境,在CentOS 6需要的包组有Server Platform Development和Development Tools。

获取目标主机硬件的相关信息:

  • CPU:cat /proc/cpuinfo | x86info -a | lscpu
  • PCI设备:lspci [-v|-vv]|lsusb| [-v|-vv]lsblk
  • 了解全部硬件信息:hal-device(也有gui工具)

简单依据模板文件的制作过程: ~]# tar xf linux-3.10.67.tar.xz -C /usr/src ~]# cd /usr/src ~]# ln -sv linux-3.10.67 linux ~]# cd linux ~]# cp /boot/config-$(uname -r) ./.config ~]# make menuconfig #配置内核选项 ~]# screen ~]# make -j #多线程编译 ~]# make modules_install ~]# make install 安装bzImage为/boot/vmlinuz-VERSION-RELEASE 生成initramfs文件 编辑grub的配置文件

重启系统,并测试使用新内核;

编译内核的步骤

(1)配置内核选项

  • 支持“更新”模式进行配置:(使用一种即可)

    (a) make config:基于命令行以遍历的方式去配置内核中可配置的每个选项;

    (b) make menuconfig:基于curses的文本窗口界面;

    (c) make gconfig:基于GTK开发环境的窗口界面;

    (d) make xconfig:基于Qt开发环境的窗口界面;

  • 支持“全新配置”模式进行配置:

    (a) make defconfig:基于内核为目标平台提供的“默认”配置进行配置;

    (b) make allnoconfig: 所有选项均回答为"no";

(2)编译make [-j #]

-j是用来启用的多线程编译模式

  • 只编译内核中的一部分功能:

    (a) 只编译某子目录中的相关代码

    ​ # cd /usr/src/linux

    ​ #make dir/

    (b) 只编译一个特定的模块:

    ​ # cd /usr/src/linux

    ​ # make dir/file.ko

    ​ 例如:只为e1000编译驱动:make drivers/net/ethernet/intel/e1000/e1000.ko

  • 交叉编译内核(编译的目标平台与当前平台不相同)

    # make ARCH=arch_name

    要获取特定目标平台的使用帮助 # make ARCH=arch_name help

  • 在已经执行过编译操作的内核源码树做重新编译:

    清理操作:

    # make clean:清理大多数编译生成的文件,但会保留config文件等;

    # make mrproper: 清理所有编译生成的文件、config及某些备份文件;

    # make distclean:mrproper、patches以及编辑器备份文件;

screen命令:将一个物理终端分隔到多个进程,实现多路复用。screen脱离了与终端的关系,即使终端关闭,运行在screen的进程也不会终止。

打开新的screen:
# screen
退出并关闭screen:
# exit
剥离当前screen:
Ctrl+a之后按d键
显示所有已经打开的screen:
screen -ls
恢复某screen
screen -r [session number]
练习:编译好,并启用之;