350 likes | 505 Views
linux的启动过程. 从BIOS启动到操作系统的怠速状态. 从开机加电到main函数之前的过程. 从开机到main函数的执行分三步完成,其目的是实现从启动盘加载操作系统程序,完成main函数所需的准备工作。 第一步,启动bios,准备实模式下的中断向量表和中断服务程序;第二部,从启动盘加载操作系统到内存,加载操作系统的工作就是利用第一步中准备的中断服务程序实现的;第三部,为执行32位的main函数做准备工作。. bios的启动原理简单叙述 .
E N D
linux的启动过程 从BIOS启动到操作系统的怠速状态
从开机加电到main函数之前的过程 • 从开机到main函数的执行分三步完成,其目的是实现从启动盘加载操作系统程序,完成main函数所需的准备工作。 • 第一步,启动bios,准备实模式下的中断向量表和中断服务程序;第二部,从启动盘加载操作系统到内存,加载操作系统的工作就是利用第一步中准备的中断服务程序实现的;第三部,为执行32位的main函数做准备工作。
bios的启动原理简单叙述 • 硬件逻辑设计者将CPU逻辑硬件设计为加电瞬间强行将CS的值置为0XFFFF,IP的值置为0X0000,这样CS:IP就指向0XFFFF0这个位置,而BIOS的程序入口地址恰恰为0XFFFF0,BIOS程序的第一条指令就设计在这个位置上。
加载第一部分代码--引导程序(bootsect) • 经过执行一系列的自检后,开始加载引导程序。 • 计算机硬件体系结构的设计与bios联手,会让CPU接收到一个int 19h中断,cpu接收倒这个中断,会立即找到int 19号中断向量,接下来中断向量把CPU指向0X0E6F2,这个是int 19号中断对应的中断向量服务程序。
这个程序将引导程序加载到0X07C00处。 为什么是0X07C00处??
加载第二部分代码--SETUP • 操作系统的设计者要全面,整体地考虑内存的规划 1: bootsect对内存的规划,规划的代码如下: SETUPLEN = 4 BOOTSEG = 0X07C0 INITSEG = 0X9000 SETUPSEG = 0X9020 SYSSEG = 0X10000 ENDSEG = SYSSEN + SYSSIZE
2 复制bootsect 接下来,bootsect启动程序将它自身从内存的0x07c00复制到0X90000处。 mov ax, #BOOTSEG mov ds , ax mov es , ax mov cs, #256 sub si , si sub di, di rep
movw jmpi go, INITSEG go:mov ax, cs mov ds, ax 这是一段很有趣的代码,大家看看,自己先分析分析!!
将Setup程序加载到内存中 • 加载setup这个程序要借助BIOS的int 0x13号中断所指向的程序 • 这个程序与0x19号对应的程序不同,int 0x19号启动的程序是有BIOS执行的,而int 0x13是由linux操作系统自身的代码启动的。 • 这个程序将setup加载到0X90200处,是紧挨着bootsect的。
加载system模块 • 任然使用int 0x13中断,将其加载到0X10000处往后的120kb空间中。 • system对于系统有着战略意义!!
现在bootsect的任务已经完成 下面通过jmpi 0, SETUPSEG这条语句跳转至0X90200处(setup处),开始执行setup。 setup利用BIOS程序提取内存运行的所需系统数据这些数据将覆盖bootsec程序所在区域。共510个字节,这种方式对内存的使用率极为高效。 到目前为止操作系统内核的加载工作已经完成。接下来系统通过加载到内存中的代码,将实现从实模式到保护模式的转换。
开始向32位模式转变,为main函数的调用做准备 • 1关中断并将syste移动到内存的起始为止0X00000,代码如下: • do_move: • mov es, ax ;(0x00000) • add ax, #0x10000 • cmp ax, #0x90000 • jz end_move • mov ds, ax • sub di,di • sub si, si • mov cx, #0x80000
rep movsw jmp do_move 理解这样做的意义吗???
设置中断描述符表和全局描述符表 内核代码: lidt ldt_48 lgdt gdt_48 gdt: .word 0,0,0,0 .word 0x07ff .word 0x0000 .word 0x9a00 .word 0x00c0 .word 0x07ff .word 0x0000 .word 0x9200 .word 0x00c0 idt_48: .word 0
.word 0,0 gdt_48: .word 0x800 .word 512 + gdt , 0x9 GDT表对保护模式下管理段描述符有重大意义!
接下来打开A20实现32位寻址,并将CR0寄存器的第0位设置为1(开启保护模式)接下来打开A20实现32位寻址,并将CR0寄存器的第0位设置为1(开启保护模式) 代码如下: mov ax , #0x0001 lmsw ax jmpi 0, 8
在执行main函数之前,先要执行三个由汇编代码生成的程序,即bootse, setup, head.s之后,才执行main函数。 head程序与前面两个程序的加载方式不同。 先将head.s 和c写成的 程序汇编成目标代码,然后在连接成system模块,也就是说,system模块里既有内核程序,又有head程序,两者是紧挨着的,然后setup会将system模块复制到0X00000位置,head程序在main函数程序前面主要是做一些调用main函数的准备工作,还要对内存进行布局,即用程序自身的代码在程序自身所在的内存空间创建内核分页机制,即在0X00000处创建页目录表,页表,缓冲区, GDT, IDT, 并将head已经执行的程序覆盖,这意味着程序将自己废弃自己,main函数即将执行。
从main到怠速 • 系统达到怠速状态前所作的一切准备工作的核心目的就是让用户程序能够以“进程”的方式运行,能够实现这一目标的标准包括三个方面:用户程序能够在主机上进行运行,能够与外设进行交互,以及能够让用户以它为媒介进行人机交互。
第一阶段:创建进程0,并让进程0具备32位保护模式下的主机运算能力。第一阶段:创建进程0,并让进程0具备32位保护模式下的主机运算能力。 第二阶段:以进程0为母本创建进程1,进程1还能以文件的方式与外设进行数据交互 第三阶段:以进程1为母本创建进程2,进程2在全面具备进程1所拥有的能力和环境的基础上,进一步具备支持“人机交互”的能力,最终实现怠速。
作为一个支持多进程的现代操作系统,意味着各个用户程序在运行的过程中,彼此不能干扰,才能保证正常的运转。然而,进程自身并没有一个天然的“边界”,所以需要人为的设计一套“边界”来保护它,这套边界就是为进程提供的进程管理数据结构,包括:进程管理结构(task_struct),进程槽(tase[])和GDT等。task_struct是每个进程所独有的,它标识了进程的各项属性,包括剩余时间片,进程状态等,task存储着系统所以进程的task_struct结构的指针。作为一个支持多进程的现代操作系统,意味着各个用户程序在运行的过程中,彼此不能干扰,才能保证正常的运转。然而,进程自身并没有一个天然的“边界”,所以需要人为的设计一套“边界”来保护它,这套边界就是为进程提供的进程管理数据结构,包括:进程管理结构(task_struct),进程槽(tase[])和GDT等。task_struct是每个进程所独有的,它标识了进程的各项属性,包括剩余时间片,进程状态等,task存储着系统所以进程的task_struct结构的指针。
物理内存的规划 • 进程0在主机的运算主要是通过CPU和内存相互配合工作实现的,因此系统要对主机物理内存的使用进行规划,这样才能使进程0具备运算能力,除了内核区外,其余物理内存要完成的工作是不同的,“主机内存”主要是来承载进程的相关信息,如:task_struct ,缓冲区主要作为主机与外设进行数据交互的中转站; • 系统主要根据内存条的大小对"主内存“,”缓冲区“进行设置,
对内存起始地知道重新确定,标志着主内存区和缓冲区的大小和位置都已确定,于是系统开始调用mem_init函数,先对内存区的管理结构进行设置,代码如下:对内存起始地知道重新确定,标志着主内存区和缓冲区的大小和位置都已确定,于是系统开始调用mem_init函数,先对内存区的管理结构进行设置,代码如下: mem_init(long start_mem, long end_mem) { ... for ( i = 0; i <PAGING_PAGES; i++) mem_map[i] = USED; ... while (end_mem-- > 0) mem_map[i++] = 0; }
系统对除了内核的数据域外是使用分页管理的,于是使用一个叫mem_map[]的数组记录每个页面的使用次数。系统对除了内核的数据域外是使用分页管理的,于是使用一个叫mem_map[]的数组记录每个页面的使用次数。 为啥不对内核数据区进行分页管理呢??
进程0创建前的准备工作 • 异常处理程序与中断描述符表挂接 • 初始化设备请求项 • 设置开机时间 现在开始激活进程0,在这之前还要做三件事。 1:进程0的task_struct 的母本已经在代码设计时事先设计好了,要将其管理结构与全局描述符表挂接,并对全局描述表,进程槽等初始化。 2:作为一个现代操作系统,支持多进程,所以要能够多进程轮流执行,所以要设置时钟中断。 3:进程0要具备处理系统调用的能力。系统通过函数set_sysyem_gate将system_call与中断描述符表挂接,这样就具备里处理系统调用的能力。
经过一系列的准备工作进程0具备了在主机上执行的能力,接下来进程就可以工作了,进程0这时就做了一件事,创建进程1.经过一系列的准备工作进程0具备了在主机上执行的能力,接下来进程就可以工作了,进程0这时就做了一件事,创建进程1. 创建进程1 先将自己的状态变为用户态,然后调用fork(),现在主要看看fork()的代码。
main.c 中对fork()的声明:static inline _syscall0(int, fork); static inline _syscall0(type , name) { long __res; __asm__ volatile ("int $0X80 : "=a" (__res) :"0" (__NR__##name)); if (__res >= 0) return (type) __res; errno = -__res; return -1; }
接着在进程槽中为进程1申请一个空闲的位置并获取进程号,接着在进程槽中为进程1申请一个空闲的位置并获取进程号, 先调用fing_empty_process函数,为这个进程获得一个可用的进程号和一个空闲的任务号。 接下来将进程0的管理数据结构拷贝给进程1的管理结构,调用copy_process()来实现的,这些内容作为进程1管理结构的雏形。具体执行如下: 调用get_free_page函数, 在主内存末端申请一个空闲页面具体代码如下:
int copy_process (int nr, ling ebp, long edi, long esi, long gs, long none, long ebx, long ecx, long ds,long eip, long cs, long eglags, long esp, long ss) { .. p = (struct tast_struct*)get_free_page(); ... //nr = 1; task[nr] = p; *p = *current; //设置为不可中断状态 p->state = TASK_UNINTERRUPTIBLE;
... p->pid = last_pid; p->father = current->pid; p->counter = p->priority; p->signal = 0; p->alaem = 0; .... /*还要将进程1的TSS 中的成员进行初始化,等 等工作.*/ ... }
接下来设置进程1的线性地址空间及物理页面。每个进程都有可能加载自己的代码,这些代码主要占主内存的空间。接下来设置进程1的线性地址空间及物理页面。每个进程都有可能加载自己的代码,这些代码主要占主内存的空间。 所以这里要为进程1设置页目录项,以及为其创建页表,并将进程0的页表项复制给进程1的项表中,以此为进程1将来执行代码创造条件。创建页目录项和复制页表,使用copy_mem函数来完成的。在此不做赘述。进程1此时还没有对应的代码,,它与进程0管理的页面暂时完全一致,等将来有了自己的代码时,再把关系解除。
设置完进程1的页表和页目录表后,还要继续调整进程1的管理结构,,如打开的文件,进程0当前工作的目录i节点等等。因为进程0不具备与外设以文件的形式交互,所以这些值都是空的。设置完进程1的页表和页目录表后,还要继续调整进程1的管理结构,,如打开的文件,进程0当前工作的目录i节点等等。因为进程0不具备与外设以文件的形式交互,所以这些值都是空的。 之后把进程1的任务状态描述符表盒局部描述符表挂接在全局描述符表中,最后将进程1的状态设置为就绪态。并返回进程号1,标志着进程1已经创建完成。此时进程1具备进程0的全部能力。
接下来系统切换到进程1中开始执行-----加载根文件系统。接下来系统切换到进程1中开始执行-----加载根文件系统。 1:进程1通过对一些与硬盘管理相关的数据结构的设置,为其与硬盘以文件形式进行数据交换初步奠定基础。 2:进程1用虚拟盘代替软盘,使之成为根设备。 3:进程1以虚拟盘中提供的数据为依据,简直根文件系统,从虚拟盘中读取根文件系统的超级块,并将超级块及其附属信息加载到指定主机结构中。 所以进程1的主要工作是加载根文件系统及创建进程2。创建进程2的过程与进程0创建进程1的过程步骤一样。
进程2会进一步扩展与外设交换的能力,具体表现为,将通过加载shell程序,为最终建立其一套人机交换界面打好基础。进程2会进一步扩展与外设交换的能力,具体表现为,将通过加载shell程序,为最终建立其一套人机交换界面打好基础。 当这一步完成之后系统就达到了怠速的状态!!