>  기사  >  운영 및 유지보수  >  Linux 부팅을 위한 세부 단계 공유

Linux 부팅을 위한 세부 단계 공유

零下一度
零下一度원래의
2017-07-02 09:47:071641검색

이 문서의 디렉토리:

14.1 프레스 전원 및 BIOS 단계

14.2 MBR 및 다양한 부트로더 단계

14.2.1 부트 로더

14.2.2 파티션 테이블

14.2.3 VBR/EBR을 사용하여 운영 체제 부팅

14.3 grub 단계

14.3.1 grub 사용 2 시작 프로세스

14.3.2 기존 grub 사용 시 부팅 프로세스

14.4 커널 로딩 단계

14.4.1 Load init ramdisk

14.4.2 initrd

14.4.3 initramfs

14.5 운영 체제 초기화

14.5.1 실행 수준

14.5.2 시스템 환경 초기화

14.5.3 런레벨 환경 초기화

14.6 단말기 초기화 및 로그인 시스템

14.6.1 단말기 초기화

14.6.2 로그인 프로세스


컴퓨터 er 시작은 커널 이전 로딩, 로딩으로 구분됩니다. 로딩 중과 로딩 후에 세 가지 주요 단계가 있습니다. 이 세 가지 주요 단계는 여러 개의 작은 단계로 나눌 수 있으며 각 중요한 작은 단계를 매우 자세히 분석합니다.

커널 로딩 전 단계는 운영체제와는 관계가 없습니다. 이 부분의 순서는 Linux나 Windows나 동일합니다. anaconda를 사용하여 Linux를 설치할 때 기본 그래픽 인터페이스는 GPT 파티션을 지원하지 않으며 최신 CentOS 7.3에서도 여전히 지원하지 않습니다. 따라서 이 기사에서는 주로 기존 BIOS 플랫폼의 시작 방법(MBR 방법)을 소개합니다. 바보야, UEFI 시작 방법을 이해할 수 없어).

커널 로딩 및 포스트 로딩 단계에서 CentOS 7은 systemd를 사용하는데 이는 CentOS 5나 CentOS 6의 sysV 스타일 init와는 매우 다르기 때문에 이 글에서는 sysV 스타일 init만 소개합니다.


14.1 전원 및 BIOS 단계를 누르세요.

전원을 누르면 컴퓨터의 전원이 켜지기 시작합니다. 가장 중요한 것은 CPU 회로를 연결한 다음 CPU가 CPU 핀을 통과하도록 하는 것입니다. CPU가 실행 중이면 관련 작업을 실행할 수 있습니다. 코드는 BIOS로 이동합니다.

bios는 전원 버튼을 누른 후 실행되는 첫 번째 프로그램으로, 하드웨어 자체 테스트(포스트), 하드웨어 사용 시간, 하드 디스크 크기와 같은 일부 하드웨어 정보를 이해하기 위해 CMOS의 정보를 읽습니다. 그리고 모델 등등 실제로 BIOS 인터페이스에 수동으로 진입할 때 표시되는 정보는 아래와 같이 이 단계에서 획득됩니다. 이 문서에서 가장 중요한 것은 부팅 장치 및 해당 부팅 순서(위에서 아래로)에 대한 정보를 얻는 것입니다.

하드웨어 감지 및 정보 획득이 완료되면 하드웨어 초기화를 시작하고 마지막으로 첫 번째 부팅 장치에서 MBR을 읽습니다. 첫 번째 부팅 장치에서 합리적인 MBR이 발견되지 않으면 두 번째부터 계속 읽습니다. 부팅 장치. 올바른 MBR이 발견될 때까지 부팅 장치를 검색합니다.


14.2 MBR 및 다양한 부트로더 단계

이 섹션에서는 다양한 BR(부트 레코드) 및 다양한 부트로더를 소개하지만 기본 기능만 간략하게 소개합니다.

MBR은 디스크의 첫 번째 섹터에 위치한 마스터 부트 레코드입니다. 이는 파티션이나 운영 체제와 관련이 없습니다. BIOS는 MBR의 레코드를 확실히 읽습니다.

부트로더/파티션 테이블/BRID는 MBR에 저장됩니다. 부트로더는 부팅 로딩을 위해 446바이트를 차지하며, 파티션 테이블은 64바이트를 차지하고, 각 기본 파티션이나 확장 파티션은 16바이트를 차지합니다. 16바이트의 첫 번째 바이트가 0x80이면, 해당 파티션이 활성화된 파티션(활성 파티션)이라는 의미입니다. 파티션), 활성화된 파티션은 하나만 허용됩니다; 마지막 2바이트는 BRID(부트 레코드 ID)로 0x55AA로 고정되어 저장 장치의 MBR을 식별하는 데 사용됩니다. 이것이 합리적이고 유효한 MBR입니까? BIOS는 MBR을 읽고 마지막 2바이트가 0x55AA가 아님을 발견하면 다음 부팅 장치를 읽습니다.


14.2.1 부트로더

MBR의 부트로더는 446바이트만 차지하기 때문에 저장할 수 있는 코드도 제한되어 있고, 로드하고 부팅할 수 있는 것도 제한되어 있기 때문에 다양한 부트로더가 존재합니다 디스크의 다른 위치에 설계되었습니다. 다양한 상황이 아래에 설명되어 있습니다.

파일 시스템을 생성할 때 일부 파티션의 첫 번째 블록이 부트 섹터라는 것을 아직도 기억하시나요? 부트로더도 이 부트 섹터에 배치되며 크기도 매우 제한됩니다. 주 파티션의 부트 섹터라면 부트로더가 위치한 섹터를 VBR(볼륨 부트 레코드)이라고 합니다. 논리 파티션의 부트 섹터라면 부트로더가 위치한 섹터입니다. EBR(확장 부트 섹터)이라고 합니다. 안타깝게도 이 두 가지 부트로더 방법은 매우 불편하기 때문에 거의 사용되지 않습니다. 이후 부트 관리자(LILO 및 GRUB)가 등장하면서 잊혀졌습니다. 그러나 그럼에도 불구하고 파티션에는 여전히 부트 섹터가 있습니다.


14.2.2 파티션 테이블

하드 디스크 파티셔닝의 장점 중 하나는 다양한 운영 체제를 다양한 파티션에 설치할 수 있지만 부트로더는 각 운영 체제가 어느 파티션에 있는지 알아야 한다는 것입니다.

파티션 테이블의 길이는 64바이트에 불과하며 4개의 항목으로 나누어져 있으며 각 항목은 16바이트입니다. 따라서 하드 디스크는 최대 4개의 기본 파티션으로만 나눌 수 있습니다.

각 기본 파티션 테이블 항목의 16바이트는

(1) 6개 부분으로 구성됩니다. 첫 번째 바이트는 0 또는 0x80일 수 있습니다. 0x80은 기본 파티션이 활성 파티션임을 나타냅니다. 분할. 단일 디스크의 기본 파티션 하나만 활성화할 수 있습니다.

(2) 2~4번째 바이트: 기본 파티션의 첫 번째 섹터(실린더, 헤드, 섹터 번호 등)의 물리적 위치입니다.

(3) 5번째 바이트: 기본 파티션 유형.

(4) 6~8번째 바이트: 기본 파티션의 마지막 섹터의 물리적 위치입니다.

(5). 바이트 9-12: 기본 파티션의 첫 번째 섹터의 논리 주소입니다.

(6) 바이트 13-16: 기본 파티션의 총 섹터 수입니다.

마지막 4바이트인 "기본 파티션의 총 섹터 수"는 기본 파티션의 길이를 결정합니다. 즉, 기본 파티션의 총 섹터 수는 최대 2의 32제곱을 초과할 수 없습니다. 각 섹터가 512바이트라면 단일 파티션의 최대 크기는 2TB를 초과할 수 없음을 의미합니다.


14.2.3 VBR/EBR을 사용하여 운영 체제 부팅

지금은 grub이 운영 체제를 시작하는 방법을 논의하지 않고 VBR 및 EBR을 예로 들어 운영 체제를 부팅해 보겠습니다.

BIOS가 MBR의 부트 로더를 읽은 후 계속해서 파티션 테이블을 읽습니다. 두 가지 상황이 있습니다.

(1) 파티션 테이블을 검색할 때 기본 파티션 테이블의 첫 번째 바이트가 활성화된 파티션인 0x80인 경우 운영 체제가 기본 파티션에 설치되어 있음을 의미합니다. 로드된 MBR의 부트 로더 코드는 기본 파티션을 활성화하는 VBR의 부트 로더를 로드합니다. 이 시점에서 제어권은 VBR의 부트 로더로 넘겨집니다. 운영 체제가 기본 파티션에 설치되어 있지 않은 경우 논리 파티션에 설치해야 하므로 기본 파티션 테이블을 검색한 후 EBR이 있는 파티션을 찾을 때까지 확장 파티션 테이블을 계속 검색한 다음 MBR의 부트 로더는 EBR의 부트 로더에 제어권을 부여합니다.

즉, 하드 디스크에 여러 운영 체제가 설치되어 있으면 부트로더는 VBR이나 EBR 등 여러 위치에 배포되지만 BIOS에 의해 "바인딩"되는 MBR이 있어야 합니다. LINUX 운영 체제를 설치할 때 MBR이 어디에 설치되어 있는지 묻는 단계가 있습니다. 그러나 이 MBR은 반드시 MBR, VBR 또는 EBR일 수 있습니다. 단일 디스크, 시스템 공존, MBR을 덮어쓰면 안 됩니다(여기서는 grub이 고려되지 않음).

아래와 같이 단일 디스크에 3개의 운영체제 설치를 테스트했을 때의 파티션 구조입니다. 그 중 /dev/sda{1,2,3}는 첫 번째 CentOS 6 시스템이고, /dev/sda{5,6,7}는 두 번째 CentOS 7 시스템이고, /dev/sda{8,9,10 } 세 번째 CentOS 6 시스템입니다. 각 운영체제의 파티션 번호는 앞에서부터 /boot 파티션, 루트 파티션, 스왑 파티션입니다.

세 번째 운영체제 설치 시 부트로더 설치 위치를 묻는 단계를 아래 그림에서 살펴보세요.

첫 번째 운영 체제를 설치할 때 부트 로더는 /dev/sda에 설치하거나 /dev/sda1에 설치하도록 선택할 수 있습니다. 이렇게 하면 MBR과 VBR 중 하나를 선택하면 설치됩니다. 또한 설치합니다. 두 번째 운영 체제부터는 MBR 대신 EBR이 설치되며, 부트로더 위치를 지정해야 합니다(예: /dev/sda5 및 /dev/sda8). 그렇지 않으면 기본 옵션이 설치됩니다. /dev/sda 에 있지만 원본 MBR을 덮어쓰게 됩니다.

또한 지정된 부트 로더 설치 경로 아래에는 운영 체제 목록이 있는 상자가 있으며, 여기에서 기본 운영 체제를 지정할 수 있습니다. 여기서 기본값은 MBR입니다. 또는 기본적으로 EBR로 이동합니다.

그래서 MBR/VBR과 EBR의 점프 관계는 아래와 같습니다.

이 메뉴 관리 방법을 사용하여 운영 체제를 시작하면 stage1, stage1.5 및 stage2의 개념이 필요하지 않습니다. 파티션에서 VBR 또는 EBR로 점프하기만 하면 됩니다. 파티션 시스템에 작업을 로드하고 안내합니다.

그러나 이러한 운영체제 시작 관리 메뉴는 더 이상 의미가 없습니다. 이제 grub에서 관리하므로 두 번째 운영체제나 n번째 운영체제를 설치할 때 부트로더 설치 위치를 수동으로 지정하지 않고 MBR을 덮어쓰십시오. 단일 디스크에서 여러 시스템의 공존을 달성하기 위해 해야 할 일은 grub 구성 파일을 수정하는 것뿐입니다.

grub을 사용하여 부팅 메뉴를 관리할 때 VBR/EBR은 쓸모가 없습니다. 자세한 내용은 아래를 참조하세요.


14.3 grub stage

grub 관리를 사용하여 시작하면 grub 프로그램에 의해 MBR의 부트로더가 설치되고 다른 부트로더도 설치됩니다. CentOS 6은 기존 grub을 사용하고 CentOS 7은 grub2를 사용합니다.

기존 grub을 사용하는 경우 설치된 부트 로더는 stage1, stage1_5 및 stage2입니다. grub2를 사용하는 경우 설치된 boot.img 및 core.img입니다. 전통적인 grub과 grub2의 차이점은 꽤 크므로 아래에서 별도로 설명하겠습니다. grub이 이해되지 않으면 제 다른 기사grub2 자세한 설명을 참조하세요.


14.3.1 grub2 사용 시 시작 프로세스

grub2 프로그램이 grub을 설치한 후 boot.img 및 core.img 파일도 /boot/grub2/i386-pc/ 디렉터리에 생성됩니다. 파일 시스템 클래스에 대한 모듈을 포함하는 일부 모듈 파일로.

[root@xuexi ~]# find /boot/grub2/i386-pc/ -name '*.img' -o -name "*fs.mod" -o -name "*ext[0-9].mod"  /boot/grub2/i386-pc/affs.mod/boot/grub2/i386-pc/afs.mod/boot/grub2/i386-pc/bfs.mod/boot/grub2/i386-pc/btrfs.mod/boot/grub2/i386-pc/cbfs.mod/boot/grub2/i386-pc/ext2.mod   # ext2、ext3和ext4都使用该模块/boot/grub2/i386-pc/hfs.mod/boot/grub2/i386-pc/jfs.mod/boot/grub2/i386-pc/ntfs.mod/boot/grub2/i386-pc/procfs.mod/boot/grub2/i386-pc/reiserfs.mod/boot/grub2/i386-pc/romfs.mod/boot/grub2/i386-pc/sfs.mod/boot/grub2/i386-pc/xfs.mod/boot/grub2/i386-pc/zfs.mod/boot/grub2/i386-pc/core.img
/boot/grub2/i386-pc/boot.img

其中boot.img就是安装在MBR中的boot loader。当然,它们的内容是不一样的,安装boot loader时,grub2-install会将boot.img转换为合适的代码写入MBR中的boot loader部分。

core.img是第二段Boot loader段,grub2-install会将core.img转换为合适的代码写入到紧跟在MBR后面的空间,这段空间是MBR之后、第一个分区之前的空闲空间,被称为MBR gap,这段空间最小31KB,但一般都会是1MB左右。

实际上,core.img是多个img文件的结合体。它们的关系如下图:

这张图解释了开机过程中grub2阶段的所有过程,boot.img段的boot loader只有一个作用,就是跳转到core.img对应的boot loader的第一个扇区,对于从硬盘启动的系统来说,该扇区是diskboot.img的内容,diskboot.img的作用是加载core.img中剩余的内容。

由于diskboot.img所在的位置是以硬编码的方式写入到boot.img中的,所以boot.img总能找到core.img中diskboot.img的位置并跳转到它身上,随后控制权交给diskboot.img。随后diskboot.img加载压缩后的kernel.img(注意,是grub的kernel不是操作系统的kernel)以初始化grub运行时的各种环境,控制权交给kernel.img。

但直到目前为止,core.img都还不识别/boot所在分区的文件系统,所以kernel.img初始化grub环境的过程就包括了加载模块,严格地说不是加载,因为在安装grub时,文件系统类的模块已经嵌入到了core.img中,例如ext类的文件系统模块ext2.mod。

加载了模块后,kernel.img就能识别/boot分区的文件系统,也就能找到grub的配置文件/boot/grub2/grub.cfg,有了grub.cfg就能显示启动菜单,我们就能自由的选择要启动的操作系统。

当选择某个菜单项后,kernel.img会根据grub.cfg中的配置加载对应的操作系统内核(/boot目录下vmlinuz开头的文件),并向操作系统内核传递启动时参数,包括根文件系统所在的分区,init ramdisk(即initrd或initramfs)的路径。例如下面是某个菜单项的配置:

menuentry 'CentOS 6' --unrestricted {
        search --no-floppy --fs-uuid --set=root f5d8939c-4a04-4f47-a1bc-1b8cbabc4d32
        linux16 /vmlinuz-2.6.32-504.el6.x86_64 root=UUID=edb1bf15-9590-4195-aa11-6dac45c7f6f3 ro quiet
        initrd16 /initramfs-2.6.32-504.el6.x86_64.img
}

加载完操作系统内核后grub2就将控制权交给操作系统内核。

总结下,从MBR开始后的过程是这样的:

1.执行MBR中的boot loader(即boot.img)跳转到diskboot.img。

2.执行diskboot.img,加载core.img剩余的部分,并跳转到kernel.img。

3.kernel.img读取/boot/grub2/grub2.cfg,并显示启动管理菜单。

4.选中某菜单后,kernel.img加载该菜单项配置的操作系统内核/boot/vmlinux-XXX,并传递内核启动参数,包括根文件系统所在分区和init ramdisk的路径。

5.控制权交给操作系统内核。


14.3.2 使用传统grub时的启动过程

传统grub对应的boot loader是stage1和stage2,从stage1跳转到stage2大多数情况下还会用到stage1_5对应的boot loader。

与grub2相比,stage1和boot.img的作用是类似的,都在MBR中。当该段boot loader执行后,它的目的是跳转到stage1_5的第一个扇区上,然后由该扇区的代码加载剩余的内容,并跳转到stage2的第一个扇区上。

stage1_5存在的理由是因为stage2功能较多,导致其文件体积较大(一般至少都有100多K),所以并没有像core.img一样嵌入到磁盘上,而是简单地将其放在了boot分区上,但stage1并不识别boot分区的文件系统类型,所以借助中间的辅助boot loader即stage1_5来跳转。

stage1_5的目的之一是识别文件系统,但文件系统的类型有很多,所以对应的stage1_5也有很多种。

[root@xuexi ~]# ls -C /boot/grub/*stage1_5*/boot/grub/e2fs_stage1_5     /boot/grub/jfs_stage1_5       /boot/grub/vstafs_stage1_5
/boot/grub/fat_stage1_5      /boot/grub/minix_stage1_5     /boot/grub/xfs_stage1_5
/boot/grub/ffs_stage1_5      /boot/grub/reiserfs_stage1_5
/boot/grub/iso9660_stage1_5  /boot/grub/ufs2_stage1_5

虽然有很多种stage1_5,但每个boot分区也只能对应一种stage1_5。这个stage1_5对应的boot loader一般会被嵌入到MBR后、第一个分区前的中间那段空间(即MBR gap)。

当执行了stage1_5对应的boot loader后,stage1_5就能识别出boot所在的分区,并找到stage2文件的第一个扇区,然后跳转过去。

当控制权交给了stage2,stage2就能加载grub的配置文件/boot/grub/grub.conf并显示菜单并初始化grub的运行时环境,当选中操作系统后,stage2将和kernel.img一样加载操作系统内核,传递内核启动参数,并将控制权交给操作系统内核。

所以,stage1、stage1_5和stage2之间的关系如下图:

虽然绝大多数都提供了stage1_5,但它不是必须的,它的作用仅仅只是识别boot分区的文件系统类型,对于一个会编程的人来说,可以将固定boot分区的文件系统识别代码嵌入到stage1中,这样stage1自身就能识别boot分区,就不需要stage1_5了。

看看安装grub时,grub到底做了些什么工作。

grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.

首先检测各stage文件是否存在于/boot/grub目录下,随后嵌入stage1_5到磁盘上,该文件系统类型的stage1_5占用了15个扇区,最后安装stage1,并告知stage1 stage1_5的位置是第1到第15个扇区,之所以先嵌入stage1_5再嵌入stage1就是为了让stage1知道stage1_5的位置,最后还告知了stage1 stage2和配置文件menu.lst(它是grub.conf的软链接)的路径。


14.4 内核加载阶段

提前说明,下文所述均为sysV init系统启动风格,systemd的启动管理方式大不相同,所以不要将systemd管理的启动方式与此做比较。

到目前为止,内核已经被加载到内存掌握了控制权,且收到了boot loader最后传递的内核启动参数以及init ramdisk的路径。

所有的内核都是以bzImage方式压缩过的,压缩后CentOS 6的内核大小大约为4M,CentOS 7的内核大小大约为5M。内核要能正常运作下去,它需要进行解压释放。

解压释放之后,将创建pid为0的idle进程,该进程非常重要,后续内核所有的进程都是通过fork它创建的,且很多cpu降温工具就是强制执行idle进程来实现的。

然后创建pid=1和pid=2的内核进程。pid=1的进程也就是init进程,pid=2的进程是kthread内核线程,它的作用是在真正调用init程序之前完成内核环境初始化和设置工作,例如根据grub传递的内核启动参数找到init ramdisk并加载。


14.4.1 加载init ramdisk

在前面,已经创建了pid=1的init进程和pid=2的kthread进程,但注意,它们都是内核线程,全称是kernel_init和kernel_kthread,而真正能被ps捕获到的pid=1的init进程是由kernel_init调用init程序后形成的。

要加载/sbin/init程序,首先要找到根分区,根分区是有文件系统的,所以内核需要先识别文件系统并加载文件系统的驱动,但文件系统的驱动又是放在根分区的,这就出现了先有鸡还是先有蛋的矛盾。

解决的方法之一是像grub2识别boot分区的文件系统一样,将根文件系统驱动模块嵌入到内核中,但文件系统的种类太多,而且会升级,这样就导致内核不断的嵌入新的文件系统驱动模块,内核不断增大,这显然是不合适的。

解决方法之二则像传统grub借助中间过渡引导段stage1_5一样,将根文件系统的驱动模块放入一个中间过渡文件,在加载根文件系统之前先加载这个过渡文件,再由过渡文件跳转到根文件系统。

方法二正是现在采用的,其采用的中间过渡文件称为init ramdisk,它是在安装完操作系统时生成的,这样它会收集到当前操作系统的根文件系统是什么类型的文件系统,也就能只嵌入一个对应的文件系统驱动模块使其变得足够小。如下图,它是安装操作系统时安装完所有软件包后执行的一个收集过程。

在CentOS 5上采用的init ramdisk称为initrd,而CentOS 6和CentOS 7采用的则是initramfs,它们的目的是一样的,但在实现上却大有不同。但它们都存放在/boot目录下。

[root@xuexi ~]# ll -h /boot/init*
-rw-------. 1 root root 19M Feb 25 11:53 /boot/initramfs-2.6.32-504.el6.x86_64.img

可以看到,它们的大小有十多兆,由此也可知道init ramdisk的作用肯定不仅仅只是找到根文件系统,它还会做其他工作。具体还做什么工作,请继续阅读下文。


14.4.2 initrd

initrd其实是一个镜像文件系统,是在内存中划分一片区域模拟磁盘分区,在该文件中包含了找到根文件系统的脚本和驱动。

既然是文件系统,那么内核也必须要带有对应文件系统的驱动,另外文件系统要使用就必须有根"/",这个根是内存中的"虚根"。由于内核加载到这里已经初始化一些运行环境了,所以内核的运行状态等参数也要保存下来,保存的位置就是内存中虚根下的/proc和/sys,此外还有收集到的硬件设备信息以及设备的运行环境也要保存下来,保存的位置是/dev。到此为止,pid=2的内核线程kernel_kthread就完成了基本工作,开始转到kernel_init进程上了。

再之后就是kernel_init挂载真正的根文件系统并从虚根切换到实根,最后kernel_init将调用init程序,也就是真正的能被我们看见的pid=1的init进程,然后将控制权交给init,所以从现在开始,将切换到用户空间,后续剩余的事情都将由用户空间的程序完成。

以下是CentOS 5.8中initrd文件的解压过程和捷报后的目录结构。

[root@localhost ~]# cp /boot/initrd-2.6.18-308.el5.img /tmp/initrd.gz
[root@localhost tmp]# gunzip initrd.gz
[root@localhost tmp]# cpio -id < initrd 

[root@localhost tmp]# lsbin  dev  etc  init  initrd  lib  proc  sbin  sys  sysroot


14.4.3 initramfs

initramfs比initrd先进了一些,initrd必须是一个文件系统,是在内存中模拟出磁盘分区的,所以内核必须要带有它的文件系统驱动,而initramfs则仅仅只是一个镜像压缩文件而非文件系统,所以它不需要带文件系统驱动,在加载时,内核会将其解压的内容装入到一个tmpfs 中。

initramfs和initrd最大的区别在于init进程的区别对待。initramfs为了尽早进入用户空间,它将init程序集成到了initramfs镜像文件中,这样就可以在initramfs装入tmpfs时直接运行init进程,而不用去找根文件系统下的/sbin/init,由此挂载根文件系统的工作将由init来完成,而不再是内核线程kernel_init完成。最后从虚根切换到实根。

那根分区下的/sbin/init是干嘛的呢?可以认为是init ramdisk中init的一个备份,如果ramdisk中找不到init就会去找/sbin/init。另外,在正常运行的操作系统环境下,/sbin/init还经常用来完成其他工作,如发送信号。

其实initramfs完成了很多工作,解开它的镜像文件就能发现它的目录结构和真实环境下的目录结构类似。以下是CentOS 7上initramfs-3.10.0-327.el7.x86_64解包过程和解包后的目录结构。

[root@xuexi ~]# cp /boot/initramfs-3.10.0-327.el7.x86_64.img /tmp/initramfs.gz
[root@xuexi ~]# cd /tmp; gunzip /tmp/initramfs.gz
[root@xuexi tmp]# cpio -id < initramfs

[root@xuexi tmp]# ls -l
total 8lrwxrwxrwx  1 root root    7 Jun 29 23:28 bin -> usr/bin
drwxr-xr-x  2 root root   42 Jun 29 23:28 dev
drwxr-xr-x 11 root root 4096 Jun 29 23:28 etc
lrwxrwxrwx  1 root root   23 Jun 29 23:28 init -> usr/lib/systemd/systemd
lrwxrwxrwx  1 root root    7 Jun 29 23:28 lib -> usr/lib
lrwxrwxrwx  1 root root    9 Jun 29 23:28 lib64 -> usr/lib64
drwxr-xr-x  2 root root    6 Jun 29 23:28 proc
drwxr-xr-x  2 root root    6 Jun 29 23:28 root
drwxr-xr-x  2 root root    6 Jun 29 23:28 run
lrwxrwxrwx  1 root root    8 Jun 29 23:28 sbin -> usr/sbin-rwxr-xr-x  1 root root 3041 Jun 29 23:28 shutdown
drwxr-xr-x  2 root root    6 Jun 29 23:28 sys
drwxr-xr-x  2 root root    6 Jun 29 23:28 sysroot
drwxr-xr-x  2 root root    6 Jun 29 23:28 tmp
drwxr-xr-x  7 root root   61 Jun 29 23:28 usr
drwxr-xr-x  2 root root   27 Jun 29 23:28 var

另外,还可以在其sbin目录下发现init程序。

[root@xuexi tmp]# ll sbin/init
lrwxrwxrwx 1 root root 22 Jun 29 23:28 sbin/init -> ../lib/systemd/systemd


14.5 操作系统初始化

下文解释的是sysV风格的系统环境,与systemd初始化大不相同。

当init进程掌握控制权后,意味着已经进入了用户空间,后续的事情也将以用户空间为主导来完成。

init的名称是initialize的缩写,是初始化的意思,所以它的作用也就是初始化的作用。在内核加载阶段,也有初始化动作,初始化的环境是内核的环境,是由kernel_init、kernel_thread等内核线程完成的。而init掌握控制权后,已经可以和用户空间交互,意味着真正的开始进入操作系统,所以它初始化的是操作系统的环境。

操作系统初始化涉及了不少过程,大致如下:读取运行级别;初始化系统类的环境;根据运行级别初始化用户类的环境;执行rc.local文件完成用户自定义开机要执行的命令;加载终端;


14.5.1 运行级别

在sysV风格的系统下,使用了运行级别的概念,不同运行级别初始化不同的系统类环境,你可以认为windows的安全模式也是使用运行级别的一种产物。

在Linux系统中定义了7个运行级别,使用0-6的数字表示。

0:halt,即关机

1:单用户模式

2:不带NFS的多用户模式

3:完整多用户模式

4:保留未使用的级别

5:X11,即图形界面模式

6:reboot,即重启

实际上,执行关机或重启命令的本质就是向init进程传递0或6这两个运行级别。

sysV的init程序读取/etc/inittab文件来获取默认的运行级别,并根据此文件所指定的配置执行默认运行级别对应的操作。注意,systemd管理的系统是没有/etc/inittab文件的,即使有也仅仅只是出于提醒的目的,因为systemd没有了运行级别的概念,说实话,systemd管的真的太多了。

CentOS 6.6上该文件内容如下:

[root@xuexi ~]# cat /etc/inittab
# inittab is only used by upstart for the default runlevel.
#
# ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
## System initialization is started by /etc/init/rcS.conf
#
# Individual runlevels are started by /etc/init/rc.conf
#
# Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf
#
# Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
# with configuration in /etc/sysconfig/init.#
# For information on how to write upstart event handlers, or how
# upstart works, see init(5), init(8), and initctl(8).
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#id:3:initdefault:

该文件告诉我们,系统初始化过程由/etc/init/rcS.conf完成,运行级别类的初始化过程由/etc/init.conf来完成,按下CTRL+ALT+DEL键要执行的过程由/etc/init/control-alt-delete.conf来完成,终端加载的过程由/etc/init/tty.conf和/etc/init/serial.conf读取配置文件/etc/sysconfig/init来完成再文件最后,还有一行"id:3:initdefault",表示默认的运行级别为3,即完整的多用户模式。

确认了要进入的运行级别后,init将先读取/etc/init/rcS.conf来完成系统环境类初始化动作,再读取/etc/init/rc.conf来完成运行级别类动作。


14.5.2 系统环境初始化

先看看/etc/init/rcS.conf文件的内容。

[root@xuexi ~]# cat /etc/init/rcS.conf
# rcS - runlevel compatibility
#
# This task runs the old sysv-rc startup scripts.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file rcS.override and put your changes there.
 
start on startup
 
stop on runlevel
 
task
 
# Note: there can be no previous runlevel here, if we have one it's bad# information (we enter rc1 not rcS for maintenance).  Run /etc/rc.d/rc
# without information so that it defaults to previous=N runlevel=S.
console output
pre-start scriptfor t in $(cat /proc/cmdline); docase $t inemergency)
                                start rcS-emergency
                                break
                        ;;esacdoneend scriptexec /etc/rc.d/rc.sysinitpost-stop scriptif [ "$UPSTART_EVENTS" = "startup" ]; then[ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
                [ -z "$runlevel" ] && runlevel="3"for t in $(cat /proc/cmdline); docase $t in-s|single|S|s) runlevel="S" ;;
                                [1-9])       runlevel="$t" ;;esacdoneexec telinit $runlevelfiend script

其中"exec /etc/rc.d/rc.sysinit"这一行就表示要执行/etc/rc.d/rc.sysinit文件,该文件定义了系统初始化(system initialization)的内容,包括:

(1).确认主机名。

(2).挂载/proc和/sys等特殊文件系统,使得内核参数和状态可与人进行交互。是否还记得在内核加载阶段时的/proc和/sys?

(3).启动udev,也就是启动类似windows中的设备管理器。

(4)初始化硬件参数,如加载某些驱动,设置时钟等。

(5).设置主机名。

(6).执行fsck检测磁盘是否健康。

(7).挂载/etc/fstab中除/proc和NFS的文件系统。

(8).激活swap。

(9).将所有执行的操作写入到/var/log/dmesg文件中。


14.5.3 运行级别环境初始化

执行完系统初始化后,接下来就是执行运行级别的初始化。先看看/etc/init/rc.conf的内容。

[root@xuexi ~]# cat /etc/init/rc.conf
# rc - System V runlevel compatibility
#
# This task runs the old sysv-rc runlevel scripts.  It
# is usually started by the telinit compatibility wrapper.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file rc.override and put your changes there.
 
start on runlevel [0123456]
 
stop on runlevel [!$RUNLEVEL]
 
task
 
export RUNLEVEL
console outputexec /etc/rc.d/rc $RUNLEVEL

最后一行"exec /etc/rc.d/rc $RUNLEVEL"说明调用/etc/rc.d/rc这个脚本来初始化指定运行级别的环境。Linux采用了将各运行级别初始化内容分开管理的方式,将0-6这7个运行级别要执行的初始化脚本分别放入rc[0-6].d这7个目录中。

[root@xuexi ~]# ls -l /etc/rc.d/total 60drwxr-xr-x. 2 root root  4096 Jun 11 02:42 init.d-rwxr-xr-x. 1 root root  2617 Oct 16  2014 rc
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc0.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc1.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc2.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc3.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc4.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc5.d
drwxr-xr-x. 2 root root  4096 Jun 11 02:42 rc6.d-rwxr-xr-x. 1 root root   220 Oct 16  2014 rc.local-rwxr-xr-x. 1 root root 19914 Oct 16  2014 rc.sysinit

实际上/etc/init.d/下的脚本才是真正的脚本,放入rcN.d目录中的文件只不过是/etc/init.d/目录下脚本的软链接。注意,/etc/init.d是Linux耍的一个小把戏,它是/etc/rc.d/init.d的一个符号链接,在有些类unix系统中是没有/etc/init.d的,都是直接使用/etc/rc.d/init.d。

以/etc/rc.d/rc3.d为例。

[root@xuexi ~]# ll /etc/rc.d/rc3.d/ | headtotal 0lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K01smartd -> ../init.d/smartd
lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K10psacct -> ../init.d/psacct
lrwxrwxrwx. 1 root root 19 Feb 25 11:51 K10saslauthd -> ../init.d/saslauthd
lrwxrwxrwx  1 root root 22 Jun 10 08:59 K15htcacheclean -> ../init.d/htcacheclean
lrwxrwxrwx  1 root root 15 Jun 10 08:59 K15httpd -> ../init.d/httpd
lrwxrwxrwx  1 root root 15 Jun 11 02:42 K15nginx -> ../init.d/nginx
lrwxrwxrwx. 1 root root 18 Feb 25 11:52 K15svnserve -> ../init.d/svnserve
lrwxrwxrwx. 1 root root 20 Feb 25 11:51 K50netconsole -> ../init.d/netconsole
lrwxrwxrwx  1 root root 17 Jun 10 00:50 K73winbind -> ../init.d/winbind

可见,rcN.d中的文件都以K或S加一个数字开头,其后才是脚本名称,且它们都是/etc/rc.d/init.d中文件的链接。S开头表示进入该运行级别时要运行的程序,S字母后的数值表示启动顺序,数字越大,启动的越晚;K开头的表示退出该运行级别时要杀掉的程序,数值表示关闭的顺序。

所有这些文件都是由/etc/rc.d/rc这个程序调用的,K开头的则传给rc一个stop参数,S开头的则传给rc一个start参数。

打开rc0.d和rc6.d这两个目录,你会发现在这两个目录中除了"S00killall"和"S01reboot",其余都是K开头的文件。

在rc[2-5].d这几个目录中,都有一个S99local文件,且它们都是指向/etc/rc.d/rc.local的软链接。S99表示最后启动的一个程序,所以rc.local中的程序是2345这4个运行级别初始化过程中最后运行的一个脚本。这是Linux提供给我们定义自己想要在开机时(严格地说是进入运行级别)就执行的命令的文件。

当初始化完运行级别环境后,将要准备登录系统了。


14.6 终端初始化和登录系统

Linux是多任务多用户的操作系统,它允许多人同时在线工作。但每个人都必须要输入用户名和密码才能验证身份并最终登录。但登陆时是以图形界面的方式给用户使用,还是以纯命令行模式给用户使用呢?这是终端决定的,也就是说在登录前需要先加载终端。至于什么是终端,见我的另一篇文章Linux终端类型


14.6.1 终端初始化

在Linux上,每次开机都必然会开启所有支持的虚拟终端,如下图。

这些虚拟终端是由getty命令(get tty)来完成的,getty命令有很多变种,有mingetty、agetty、rungettty等,在CentOS 5和CentOS 6都使用mingetty,在CentOS 7上使用agetty。getty命令的作用之一是调用登录程序/bin/login。

例如,在CentOS 6下,捕获tty终端情况。

[root@xuexi ~]# ps -elf | grep tt[y]4 S root       1412      1  0  80   0 -  1016 n_tty_ Jun21 tty2     00:00:00 /sbin/mingetty /dev/tty24 S root       1414      1  0  80   0 -  1016 n_tty_ Jun21 tty3     00:00:00 /sbin/mingetty /dev/tty34 S root       1417      1  0  80   0 -  1016 n_tty_ Jun21 tty4     00:00:00 /sbin/mingetty /dev/tty44 S root       1419      1  0  80   0 -  1016 n_tty_ Jun21 tty5     00:00:00 /sbin/mingetty /dev/tty54 S root       1421      1  0  80   0 -  1016 n_tty_ Jun21 tty6     00:00:00 /sbin/mingetty /dev/tty64 S root       1492   1410  0  80   0 - 27118 n_tty_ Jun21 tty1     00:00:00 -bash

在CentOS 7下,捕获tty终端情况。

[root@xuexi tmp]# ps -elf | grep tt[y]4 S root       8258      1  0  80   0 - 27507 n_tty_ 04:17 tty2     00:00:00 /sbin/agetty --noclear tty2 linux4 S root       8259      1  0  80   0 - 27507 n_tty_ 04:17 tty3     00:00:00 /sbin/agetty --noclear tty3 linux4 S root       8260      1  0  80   0 - 27507 n_tty_ 04:17 tty4     00:00:00 /sbin/agetty --noclear tty4 linux4 S root       8262    915  0  80   0 - 29109 n_tty_ 04:17 tty1     00:00:00 -bash4 S root       8307   8305  0  80   0 - 29109 n_tty_ 04:17 tty5     00:00:00 -bash4 S root       8348   8346  0  80   0 - 29136 n_tty_ 04:17 tty6     00:00:00 -bash

细心一点会发现,有的tty终端仍然以/sbin/mingetty进程或/sbin/agetty进程显示,有些却以bash进程显示。这是因为getty进程在调用/bin/login后,如果输入用户名和密码成功登录了某个虚拟终端,那么gettty程序会融合到bash(假设bash是默认的shell)进程,这样getty进程就不会再显示了。

虽然getty不显示了,但并不代表它消失了,它仍以特殊的方式存在着。是否还记得/etc/inittab文件?此文件中提示了终端加载的过程由/etc/init/tty.conf读取配置文件/etc/sysconfig/init来完成。

[root@xuexi ~]# grep tty -A 1 /etc/inittab
# Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
# with configuration in /etc/sysconfig/init.

那么就看看/etc/init/tty.conf文件。

[root@xuexi ~]# cat /etc/init/tty.conf
# tty - getty
#
# This service maintains a getty on the specified device.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file tty.override and put your changes there.
 
stop on runlevel [S016] 
respawninstance $TTYexec /sbin/mingetty $TTYusage 'tty TTY=/dev/ttyX  - where X is console id'

此文件中的respawn表示进程由init进程监视,一发现被杀掉了init会立即重启它。所以,只要getty进程一结束,init会立即监视到而重启该进程。因此,用户登录成功后getty只是融合到了bash进程中,并非退出,否则init会立即重启它,而它会调用login程序让你再次输入用户和密码。

再看看/etc/sysconfig/init文件。

[root@xuexi ~]# cat /etc/sysconfig/init
# color => new RH6.0 bootup
# verbose => old-style bootup
# anything else => new style bootup without ANSI colors or positioning
BOOTUP=color
# column to start "[  OK  ]" label inRES_COL=60# terminal sequence to move to that column. You could change this
# to something like "tput hpa ${RES_COL}" if your terminal supports it
MOVE_TO_COL="echo -en \\033[${RES_COL}G"# terminal sequence to set color to a 'success' color (currently: green)
SETCOLOR_SUCCESS="echo -en \\033[0;32m"# terminal sequence to set color to a 'failure' color (currently: red)
SETCOLOR_FAILURE="echo -en \\033[0;31m"# terminal sequence to set color to a 'warning' color (currently: yellow)
SETCOLOR_WARNING="echo -en \\033[0;33m"# terminal sequence to reset to the default color.
SETCOLOR_NORMAL="echo -en \\033[0;39m"# Set to anything other than 'no' to allow hotkey interactive startup...
PROMPT=yes
# Set to 'yes' to allow probing for devices with swap signatures
AUTOSWAP=no# What ttys should gettys be started on?
ACTIVE_CONSOLES=/dev/tty[1-6]
# Set to '/sbin/sulogin' to prompt for password on single-user mode
# Set to '/sbin/sushell' otherwise
SINGLE=/sbin/sushell

ACTIVE_CONSOLES 지시문은 열 가상 터미널을 결정합니다. SINGLE은 단일 사용자 모드에서 호출할 로그인 프로그램과 쉘을 결정합니다.


14.6.2 로그인 프로세스

가상 터미널로 로그인하지 않고 ssh용으로 할당된 의사 터미널로 로그인한다면 getty 프로세스가 생성될 때쯤에는 이미 부팅 프로세스가 완료된 상태입니다. . 하지만 어떤 단말기에 로그인하더라도 로그인 과정 역시 부팅 과정의 일부로 간주될 수 있으므로 간략하게 설명하겠습니다.

getty 프로세스가 가상 터미널을 활성화한 후 로그인 프로세스를 호출하여 사용자에게 사용자 이름이나 비밀번호를 입력하라는 메시지를 표시합니다(또는 SSH와 같은 의사 터미널 연결 프로그램은 사용자 이름과 비밀번호를 입력하라는 메시지를 표시합니다). 사용자 입력이 완료되면 입력된 사용자 이름이 올바른지 여부, 사용자 이름이 로그인이 명확하게 금지되어 있는지, PAM 모듈이 이 사용자에게 부과하는 제한 사항 등을 확인합니다. 다양한 로그 파일에도 기록되어야 합니다. 로그인에 성공하면 사용자의 bash가 로드됩니다. bash를 로드하는 과정에는 다양한 구성 파일 읽기, 다양한 환경 초기화 등이 필요합니다. 하지만 무슨 일이 있어도 로그인이 성공하면 부팅 과정이 완료된 것입니다.


위 내용은 Linux 부팅을 위한 세부 단계 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.