计算机启动-围绕内存的所有操作
计算机启动-围绕内存的所有操作
计算机 启动过程
每次加电后,CPU总是到内存(其实是只读存储器)里面预设的位置拿第一条指令、并从这个位置开始,一条条执行后面的一整块代码。
这段指令是主板上固件程序的一部分,这个固件程序负责检查核心硬件(内存、显卡等)是否正常;
在机器加电的时候,首先会进行CPU的初始化,reset一些寄存器。在i8086架构中,cs(code segment register, 代码段寄存器)会被初始化成0xf000
, pc(program counter, 程序计数寄存器)会被初始化为0xfff0
, 也就是说CPU的第一条指令实在cs:pc处取指的。这条指令会跳转到BIOS程序执行入口,BIOS程序进行初始化和自检工作后,加载位于主引导扇区中的bootloader, 再把控制权交给bootloader , bootloader又通过一定的规则,同时完成从实模式到保护模式的转换,加载位于磁盘上的操作系统内核,最后把控制权交给操作系统内核,完成加载工作。
注:cs:pc在i8086实模式下对应的物理地址是
(cs << 4) + pc = 0xf0000 + 0xfff0 = 0xffff0
可以分为以下几个步骤:
-
加电自检(Power-On Self-Test,POST):当计算机通电时,硬件设备会进行自检,包括检查内存、处理器、硬盘等是否正常工作。这个过程通常只需要几秒钟。
-
启动引导加载程序(Bootloader):计算机会加载引导加载程序,例如GRUB(GRand Unified Bootloader)或LILO(Linux Loader)。这些引导加载程序位于硬盘的引导扇区,它们负责加载操作系统内核。
-
加载内核(Kernel):引导加载程序会加载Linux内核,将其加载到内存中。内核是操作系统的核心部分,负责管理硬件设备、内存、文件系统等。
-
初始化(Initialization):内核加载后,会执行一系列初始化任务。这些任务包括初始化设备驱动程序、创建进程、设置系统参数等。
-
运行init进程:在初始化完成后,内核会启动init进程,它是用户空间的第一个进程。init进程会读取配置文件(如/etc/inittab或/etc/init.d/)并启动其他系统进程和服务。
-
启动系统服务:init进程会根据配置文件启动各种系统服务,如网络服务、登录管理器、时间同步等。
-
用户登录:一旦系统服务启动完成,用户可以通过登录界面或命令行登录系统。
总结起来,Linux启动过程包括硬件自检、引导加载程序加载内核、内核初始化、启动init进程、启动系统服务和用户登录。这个过程通常在几秒钟到几分钟之间,具体时间取决于硬件配置和系统设置。
所有和内存相关的操作
1. 加电自检阶段的内存检测
加电自检(POST)阶段的内存检测主要包括以下几个部分:
-
初始化内存控制器:在开始内存检测之前,计算机会对内存控制器进行初始化,以确保它能够正确地与内存模块进行通信。
-
写入和读取测试模式:计算机会向内存中写入特定的测试模式和数据序列,然后再读取并验证这些数据。这可以帮助检测内存模块是否能够正确地接收和存储数据。
-
内存地址线测试:计算机会逐个测试内存地址线,确保每根地址线都能够正确地传输地址信息。这可以帮助检测内存地址线是否存在连接问题或者其他故障。
-
数据线测试:计算机会逐个测试内存数据线,确保每根数据线都能够正确地传输数据。这可以帮助检测内存数据线是否存在连接问题或者其他故障。
-
内存模块大小和类型检测:计算机会检测安装的内存模块的大小和类型,并与系统配置进行匹配。这可以帮助确保内存模块的兼容性和一致性。
具体的内存检测过程可能会因计算机硬件和固件的不同而有所差异。一般来说,计算机会在启动时进行这些检测,并将结果显示在屏幕上或通过声音信号进行通知。如果检测过程中发现内存问题,计算机可能会停止启动并提示用户采取进一步的措施,如更换或重新插拔内存模块。
2. bios把引导加载程序放入内存
-
BIOS找到第一个可引导的设备、读取该设备的引导扇区(boot sector),一般位于硬盘的第一个物理扇区,是一个512字节大小的区域,其中包含了引导加载程序的代码。
-
BIOS将引导设备的第一个扇区加载到内存中的特定位置是内存地址0x7C00处。
这个位置是一个固定的位置,被用作引导加载程序的入口点。一旦引导扇区被加载到这个内存位置,CPU就会开始执行这个代码,从而启动操作系统的引导过程。 -
为什么是 内存地址0x7C00处?
参考为什么 BIOS 将 MBR 加载到 x0 中的 7x00C86 中?- Glamenv-Septzen.net(zh)0x7c00这个地址来自Intel的第一代个人电脑芯片8088,之后的CPU为了保持兼容就一直使用这个地址。
1981年8月,IBM公司最早的个人电脑 IBM PC 5150 上市,就用了这个芯片。当时搭配的操作系统是86-DOS.这个操作需要的内存最少是32KB。内存地址从0x0000开始编号,32KB的内存就是 0x0000~0x7FFF。
8088芯片本身需要占用 0x0000~0x03FF,用来保存各种中断处理程序的储存位置。(主引导记录本身就是中断信号INT 19h的处理程序)。所以,内存只剩下 0x0400~0x7FFF可以使用。
为了把尽量多的连续内存留给操作系统,主引导记录就被放到了内存地址的尾部。由于一个扇区是512 字节,主引导扇区本身也会产生数据,需要另外留出512字节保存。所以,它的预留位置就变成了:
0x7FFF - 512 - 512 + 1 = 0x7c00,也即 32KB - 1KB。
所以0x7c00 就是这样来的。
3. 引导加载程序对内存进行一些初始化操作
引导加载程序在控制权转交给操作系统之前,需要对内存进行基本的初始化。下面是一些常见的内存初始化操作:
-
设置堆栈(Stack Setup):堆栈是用于存储函数调用和局部变量等临时数据的一种数据结构。引导加载程序需要设置堆栈指针,以确保在后续的代码执行过程中能够正确地使用堆栈。
具体来说,堆栈指的是一块内存区域,用于存储函数调用时的返回地址、局部变量等信息。堆栈的设置包括两个步骤:
- 确定堆栈的起始地址;
- 将堆栈指针(Stack Pointer)初始化为堆栈的起始地址。
在启动时,处理器会将堆栈指针(SP,Stack Pointer)初始化为一个特定的值,通常是内存末端的地址。
然后,引导加载程序会根据系统的需要,设置一个新的堆栈指针,以便可用于进一步的操作。
具体的做法是将新的堆栈指针值存储到处理器的堆栈指针寄存器(SP register)中,这样处理器在执行指令或调用函数时,就会使用新的堆栈指针。这个过程通常被称为 “堆栈初始化” 或 “堆栈设置”。 -
清零寄存器(Zero Registers):为了确保在运行操作系统之前,所有寄存器中的值都是可控的,引导加载程序会将所有寄存器清零,这样可以避免旧数据的干扰。
-
建立内存映射表(Memory Mapping):引导加载程序需要建立内存映射表,将物理内存地址映射到虚拟内存地址。这样操作系统在运行时就能够正确访问和管理内存。
在引导加载程序初始化内存时,通常会执行以下步骤以建立内存映射表(Memory Mapping):
-
设置段寄存器:引导加载程序使用实模式来操作系统启动的早期阶段。在实模式下,内存被分为段(Segment)和偏移量(Offset)。程序需要设置段寄存器,以正确访问内存。
-
初始化全局描述符表(Global Descriptor Table,GDT):GDT是一个数据结构,用于定义不同的内存段的属性和位置。引导加载程序需要初始化GDT,以便操作系统能够正确使用内存。
-
启用分页机制(Paging):分页机制是一种将物理内存映射到虚拟内存的技术。引导加载程序需要启用分页机制,以便操作系统可以使用虚拟内存管理和地址转换。
-
设置页表和页目录:页表和页目录是分页机制中的关键数据结构,用于映射虚拟地址到物理地址。引导加载程序需要创建并初始化页表和页目录,以建立正确的内存映射关系。
-
启用内存保护:引导加载程序可能会设置内存保护功能,例如开启写保护,以防止对关键内存区域的非法写操作。
这些步骤可以确保内存的基本初始化和正确映射,为操作系统的正常运行提供必要的环境。具体的实现方式可能因操作系统和硬件平台而有所不同。
-
-
加载中断向量表(Interrupt Vector Table):中断向量表存储了各种硬件和软件中断的入口点,引导加载程序需要加载和设置正确的中断向量表,以确保操作系统能够正确响应中断。
这里加载的 初级的中断向量表,还有一些高级中断向量表由操作系统负负责加载对内存进行基本的初始化,包括加载中断向量表(Interrupt Vector Table),可以通过以下步骤来完成:
-
确定中断向量表的位置:中断向量表是一个存储中断处理程序入口地址的数据结构。它通常位于内存的固定位置,例如0x0000:0x0000处(实模式下)或0x00000000处(保护模式下)。
-
加载中断向量表:将中断向量表的内容加载到确定的位置。这可能涉及将中断处理程序的入口地址写入适当的内存位置。在实模式下,中断向量表是一个简单的数组;在保护模式下,中断向量表是一个数据结构,包含每个中断的处理程序地址。
-
设置中断描述符表(Interrupt Descriptor Table,IDT):在保护模式下,中断向量表实际上是IDT的一部分。引导加载程序需要初始化IDT,并将其指向中断向量表的位置。
-
启用中断:在初始化完中断向量表和IDT后,引导加载程序需要启用中断,以便操作系统可以正确处理各种中断事件。
这些步骤将确保中断向量表被正确加载到内存,并为操作系统提供中断处理的基本框架。具体的实现方式可能因操作系统和硬件平台而有所不同。
-
4. 加载内核
引导加载程序加内核,并移交控制权
详细步骤:
- 内核文件被成功加载到内存中,引导加载程序会传递一些启动参数给内核,比如根文件系统的位置、内存分配信息等。接着,引导加载程序会将控制权转交给内核。此时,内核接管了对计算机硬件的控制,并开始执行自己的初始化过程。
- 内核首先会解压自身,因为内核文件通常是经过压缩的,所以在加载到内存后需要进行解压操作。
- 内核会初始化系统的核心部分,包括进程管理、内存管理、设备驱动、文件系统等。
- 内核会识别和配置计算机的硬件设备,如CPU、内存、外部设备等,并建立相应的数据结构以便系统能够与这些硬件设备进行交互。
- 接下来,内核会初始化虚拟文件系统、网络子系统和其他子系统,为用户空间程序的运行做准备。
- 内核还会启动第一个用户空间进程——init进程。这个进程是所有其他用户空间进程的祖先进程,负责系统的初始化和进程的管理。
- 一旦init进程被启动,内核的加载阶段就结束了,系统已经切换到用户空间,并开始执行用户空间的初始化和服务启动。
总的来说,加载内核是Linux启动过程中非常重要的一步,它标志着系统内核开始接管对硬件的控制,并进行系统内核的初始化工作。成功加载内核后,系统将进入用户空间,并开始执行用户空间的初始化进程,为用户提供各种服务和功能。
关键数据结构及初始化
- 段描述符表(Global Descriptor Table,GDT)
- 中断描述符表(Interrupt Descriptor Table,IDT)
- 页目录表(Page Directory Table,PDT)
- Linux0.11内核:内核会将所有页的页描述符struct page 放到全局数组mem_map[]中
- 节点使用 struct pglist_data 结构来描述。
内存管理区是使用 struct zone 结构来描述的。
页框是使用 struct page 结构来表示的。所有页描述符都保存在全局数组 mem_map[]
1. mem_init(main_memory_start, memory_end)函数 设置PDT
内核执行该函数,初始化 全局数组mem_map[]。分为内核源码区,缓冲区,主内存区
mem_map 数组的各个位置上赋值,而且显示全部赋值为 USED 也就是 100,然后对其中一部分又赋值为了 0。
赋值为 100 的部分就是 USED,也就表示内存被占用,
2. trap_init();函数 设置IDT
set_trap_gate(0,÷_error);
设置中断处理程序到IDT中
3. 进程调度初始化,shed_init()
初始化了下 TSS和 LDT;
TSS 叫任务状态段,就是保存和恢复进程的上下文的
LDT 叫局部描述符表,是与 GDT 全局描述符表相对应的,内核态的代码用 GDT 里的数据段和代码段,而用户进程的代码用每个用户进程自己的 LDT 里得数据段和代码段
4. buffer_init(long buffer_end)
初始化缓冲区
5。hd_init(); 硬盘初始化
设置blk_dev[] 来进行管理,每一个索引表示一个块设备。
- 下述为计算机启动的初始步骤
加电自检(Power-On Self-Test,POST)
加电自检是Linux启动过程的第一步,也是计算机硬件初始化的过程。在这个阶段,计算机会对硬件进行自我检测,以确保硬件正常运行。
具体来说,加电自检包括以下几个主要的步骤:
-
供电检测:计算机加电后,电源单元会向主板和其他硬件组件提供电源。在此过程中,计算机会检测电源是否正常工作,以确保稳定的供电。
-
主板检测:计算机主板是各种硬件组件的连接中心。加电自检会对主板进行检测,包括检查主板上的电容、晶振、芯片组等是否正常。
-
CPU初始化:加电自检会初始化CPU,包括设置CPU的工作模式、时钟频率等。此外,还会检查CPU的缓存、指令集等功能是否正常。
-
内存检测:计算机会对安装在主板上的内存模块进行检测。它会读取内存的配置信息,如容量、频率等,并检查内存是否正常工作。
-
显卡检测:加电自检还会检测计算机中的显卡。它会读取显卡的信息,并确保显卡能够正常工作。
-
储存设备检测:计算机会检测硬盘、光驱等储存设备是否连接正常。它会读取设备的信息,并确保它们能够被正确识别和访问。
-
外部设备检测:加电自检还会检测计算机外部设备,如键盘、鼠标、打印机等。它会确保这些设备能够被正确识别和操作。
-
自检结果显示:自检过程中,计算机会将检测结果显示在屏幕上。通常会显示各个硬件组件的信息、状态和错误提示,以便用户或技术人员进行故障排除。
加电自检是Linux启动过程中非常重要的一步,它确保了计算机硬件的正常工作。如果在自检过程中发现硬件故障或异常,计算机会通过声音提示或屏幕显示错误信息,以便用户采取相应的措施修复问题。只有当加电自检通过并确认硬件正常后,计算机才能继续进行后续的启动步骤。
启动引导加载程序(Bootloader)
启动引导加载程序是Linux启动过程的第二步,它的主要任务是在计算机启动时加载操作系统内核。在这个阶段,计算机会读取硬盘中的引导扇区,并将引导加载程序加载到内存中进行执行。
下面是启动引导加载程序的详细步骤:
-
开机自检完成后,计算机会通过主板上的BIOS(基本输入/输出系统)开始引导过程。BIOS是固化在主板上的一组软件,负责初始化硬件并提供基本的输入输出功能。
-
BIOS会根据预设的引导设备顺序(如硬盘、光盘、USB等)找到第一个可引导的设备。
-
BIOS会读取该设备的引导扇区(boot sector),引导扇区位于硬盘的第一个物理扇区。引导扇区是一个512字节大小的区域,其中包含了引导加载程序的代码。
-
引导加载程序可以是GRUB(GRand Unified Bootloader)或LILO(LInux LOader)等。这些引导加载程序通常已经被安装在硬盘的引导扇区上。
-
引导加载程序会将自己的代码加载到计算机的内存中,并在控制权转交给它之前对内存进行一些基本的初始化。
引导加载程序对内存进行一些初始化操作
引导加载程序在控制权转交给操作系统之前,需要对内存进行基本的初始化。下面是一些常见的内存初始化操作:
-
设置堆栈(Stack Setup):堆栈是用于存储函数调用和局部变量等临时数据的一种数据结构。引导加载程序需要设置堆栈指针,以确保在后续的代码执行过程中能够正确地使用堆栈。
-
清零寄存器(Zero Registers):为了确保在运行操作系统之前,所有寄存器中的值都是可控的,引导加载程序会将所有寄存器清零,这样可以避免旧数据的干扰。
-
建立内存映射表(Memory Mapping):引导加载程序需要建立内存映射表,将物理内存地址映射到虚拟内存地址。这样操作系统在运行时就能够正确访问和管理内存。
-
加载中断向量表(Interrupt Vector Table):中断向量表存储了各种硬件和软件中断的入口点,引导加载程序需要加载和设置正确的中断向量表,以确保操作系统能够正确响应中断。
-
设置保护模式(Enable Protected Mode):引导加载程序可能会将计算机从实模式切换到保护模式。在保护模式下,操作系统可以更好地管理内存和处理器资源。
-
加载其他必要数据结构:引导加载程序可能会加载一些其他必要的数据结构,例如页表、段描述符等,以便操作系统能够正确地访问和管理内存。
这些内存初始化操作是为了确保操作系统能够在一个干净、可控的环境中运行。具体实现这些操作的方法会根据操作系统和硬件平台的不同而有所差异。引导加载程序会根据特定的规范和约定来执行这些操作,以确保内存被正确初始化,并为操作系统的正常运行做好准备。
-
-
引导加载程序会在内存中查找配置文件,如GRUB的配置文件是grub.cfg。配置文件包含了操作系统内核的位置、启动参数等信息。
-
引导加载程序根据配置文件的指示,定位并加载操作系统内核。内核文件通常位于硬盘的某个分区上,如/boot目录。
-
引导加载程序将内核加载到内存中的指定位置,并将控制权转交给内核,使其开始执行。
启动引导加载程序是Linux启动过程中非常关键的一步,它负责从硬盘中读取引导扇区和引导加载程序的代码,并将内核加载到内存中,为后续的初始化和运行init进程做准备。通过引导加载程序,用户可以选择不同的操作系统或不同的内核进行启动,提供了灵活性和可定制性。
加载内核(Kernel)
加载内核(Kernel)是Linux启动过程中的重要步骤,它涉及将操作系统内核加载到内存并启动内核以进行系统初始化。以下是加载内核的详细步骤:
-
引导加载程序(如GRUB)在加载后,会尝试找到并加载操作系统内核。它会根据配置文件中指定的内核位置和启动参数来执行这一步骤。
-
引导加载程序会将内核文件(通常名为vmlinuz)从硬盘的指定位置加载到计算机内存中。内核文件通常位于/boot目录下的一个特定位置。
-
一旦内核文件被成功加载到内存中,引导加载程序会传递一些启动参数给内核,比如根文件系统的位置、内存分配信息等。
-
接着,引导加载程序会将控制权转交给内核。此时,内核接管了对计算机硬件的控制,并开始执行自己的初始化过程。
-
内核首先会解压自身,因为内核文件通常是经过压缩的,所以在加载到内存后需要进行解压操作。
-
内核会初始化系统的核心部分,包括进程管理、内存管理、设备驱动、文件系统等。
-
内核会识别和配置计算机的硬件设备,如CPU、内存、外部设备等,并建立相应的数据结构以便系统能够与这些硬件设备进行交互。
-
接下来,内核会初始化虚拟文件系统、网络子系统和其他子系统,为用户空间程序的运行做准备。
-
内核还会启动第一个用户空间进程——init进程。这个进程是所有其他用户空间进程的祖先进程,负责系统的初始化和进程的管理。
-
一旦init进程被启动,内核的加载阶段就结束了,系统已经切换到用户空间,并开始执行用户空间的初始化和服务启动。
总的来说,加载内核是Linux启动过程中非常重要的一步,它标志着系统内核开始接管对硬件的控制,并进行系统内核的初始化工作。成功加载内核后,系统将进入用户空间,并开始执行用户空间的初始化进程,为用户提供各种服务和功能。