linux内核–从开机到初始进程创建
文章目录
一、简介
linux是类unix(如:mac os x、solaris、freeBSD等)中的一员,这里对linux内核基础知识及从开机到创建初始进程的主要过程进行介绍。
二、linux内核
Linux内核最早是于1991年由芬兰黑客林纳斯·托瓦兹为自己的个人电脑开发的。Linux内核是开源的类Unix操作系统内核, 它符合POSIX 标准。它提供了一套应用程序接口(API),通过接口用户程序能与内核及硬件交互。仅仅一个内核并不是一套完整的操作系统。一套基于 Linux 内核的完整操作系统才叫作Linux 操作系统。
2.1 linux内核代码
内核代码地址:https://www.kernel.org/
下载地址为:https://mirrors.edge.kernel.org/pub/linux/kernel/
2.2 linux开源免费
linux不是商业操作系统,源码在GNU(自由软件基金会协调GNU项目,供所有人免费使用)公共许可证下开放的,任何人都可以下载研究。
2.3 linux内核版本号
linux内核版本号根据其发展阶段,分为3种:
2.3.1 linux内核1.0及以前版本
第一个版本为0.01,后续有0.02、0.03、0.10等,到最后是1.0版本
2.3.2 linux内核1.0(不包含)到2.5(包含)版本
linux内核1.0之后到2.5(包含)版本这期间的版本,版本号由3个圆点分隔的数字(A.B.C)组成,A表示主版本号,变动小,B表示次版本号(其中偶数为稳定版、奇数为开发版),C表示末版本号,表示安全更新、bug修复等。
2.3.2 linux内核2.6版本及以后
linux内核从2.6版本开始,版本格式和之前类似,采用A.B.C.D格式,只是第二位B不在根据奇偶性区分稳定版和开发版了,C随着新版本发布而增加,D表示安全更新等。
三、linux内核代码的加载
3.1 linux内核整体加载流程
加载流程很清晰,总结起来,先由硬件加载BIOS代码,再由BIOS加载内核代码,再由内核代码创建进程。每一步只需完成自己的工作,剩下的就交给下一个流程。
相关知识:
-
为什么要加载Linux内核
cpu运行只能读取内存中的程序,因此需要将操作系统加载到内存中;
-
Linux内核加载位置
Linux内核是加载在RAM中,刚开始RAM什么都没有,断电后RAM中信息消失;
-
cpu运行模式:
实模式:访问地址为程序真实物理地址。
保护模式:程序地址为虚拟地址,有权限控制等。
-
内核代码从何而来
早期为软盘,现在如硬盘、ssd、网络。
3.2 硬件加载BIOS代码
硬件加载BIOS代码流程:
硬件加载BIOS,逻辑很简单,就是机器加电开机时,强制cpu读取主板上的只读内存ROM的第一个位置,即是BIOS代码的位置,便完成了BIOS的加载。
相关知识:
-
BIOS代码在哪
BIOS程序位于主板上的ROM中(只读存储器)
-
什么是cs
cs是代码寄存器,位于cpu中,指示当前cpu执行的代码在内存中的区域。
-
什么是ip
指令指针寄存器,位于cpu中,指示指令在代码段中的偏移量。
3.4 BIOS加载内核代码
3.4.1 BIOS内存分配
BIOS程序位于主板上的ROM中(只读存储器),内存分布上包含的内容有:
相关知识:
-
中断向量表
记录中断号对应的中断服务的内存地址。
-
中断服务程序
具有特定功能的程序,响应中断向量表。
-
BIOS程序
检测显卡、内存等设备,建立中断向量表和中断服务程序 。
3.4.2 BIOS加载内核代码流程
BIOS加载内核代码流程如下:
BIOS先加载引导程序bootsect,接下来就由引导程序加载后续setup代码及内核代码。其中setup代码用于获取外设信息,为内核程序main的执行做准备,head代码会将寻址改为新的32位(举例),管理内存,建立分页机制。
相关知识:
-
引导程序加载的位置确定
由硬件厂商和操作系统约定:硬件厂商固定加载第一扇区的代码,操作系统设计者将引导程序放在第一扇区里。
-
引导程序的作用
加载后续的setup代码和系统模块代码。
-
setup程序
获取内核运行需要的机器系统数据,如光标信息、硬盘信息等,为保护模式做准备,后续main函数执行需要。
-
系统模块
包含head程序和内核程序。其中head程序仍然是为main执行做准备,在内存中建立分页机制。
3.4.3 BIOS加载内核源码(1.0版本)查看:
boot/bootsect.S中源码
//规划内存,指定待加载的setup、system代码位置。
SETUPSECS = 4 ! nr of setup-sectors
BOOTSEG = 0x07C0 ! original address of boot-sector
INITSEG = DEF_INITSEG ! we move boot here - out of the way
SETUPSEG = DEF_SETUPSEG ! setup starts here
SYSSEG = DEF_SYSSEG ! system loaded at 0x10000 (65536).
...
_main:
#if 0 /* hook for debugger, harmless unless BIOS is fussy (old HP) */
int 3
#endif
//复制自身代码,并跳转到新位置
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
cld
rep
movsw
//定位到初始代码,自此不再依赖BIOS代码
jmpi go,INITSEG
...
! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
//加载setup代码
load_setup:
xor dx, dx ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ax,#0x0200+SETUPSECS ! service 2, nr of sectors
! (assume all on head 0, track 0)
int 0x13 ! read it
jnc ok_load_setup ! ok - continue
push ax ! dump error code
call print_nl
mov bp, sp
call print_hex
pop ax
xor dl, dl ! reset FDC
xor ah, ah
int 0x13
jmp load_setup
ok_load_setup:
boot/setup.S中源码
entry start
start:
! ok, the read went well so we get current cursor position and save it for
! posterity.
//检查鼠标
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
! Get memory size (extended mem, kB)
//检查内存
mov ah,#0x88
int 0x15
mov [2],ax
! set the keyboard repeat rate to the max
//检查键盘
mov ax,#0x0305
xor bx,bx ! clear bx
int 0x16
boot/head.S中源码
/*
* swapper_pg_dir is the main page directory, address 0x00001000 (or at
* address 0x00101000 for a compressed boot).
*/
//运行32位模式,建立分页机制
startup_32:
cld
movl $(KERNEL_DS),%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
...
1: movl $(KERNEL_DS),%eax # reload all the segment registers
mov %ax,%ds # after changing gdt.
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp`
xorl %eax,%eax
lldt %ax
pushl %eax # These are the parameters to main :-)
pushl %eax
pushl %eax
cld # gcc2 wants the direction flag cleared at all times
//调用main函数
call _start_kernel
3.5 内核代码创建初始进程
由head.S代码发起对内核代码main函数的调整,接着由main函数创建进程0,再根据线程0创建进程1,再由进程1创建进程2,每一级进程在前一级进程基础上,添加更丰富的内容。
相关知识:
-
进程的创建
除了进程0外,其它进程都是以上一个进程为母本创建的。
-
用户态
除进程0,后续进程都是在用户态创建的。如:创建进程1,需要将进程0从内核态转为用户态。
-
进程0
进程0的运算能力由cpu和内存配合实现。也就是此时有主内存区来存放进程的信息。
-
进程1
复制进程0,创建进程1,加载要文件系统,以文件形式进行数据交互。
-
进程2
以进程1为母本创建进程2,加载shell程序,为人机交互做准备,准备配置文件、环境变量等。
3.5.1 进程0创建源码
init/main.c中源码:
if (MOUNT_ROOT_RDONLY)
root_mountflags |= MS_RDONLY;
if ((unsigned long)&end >= (1024*1024)) {
memory_start = (unsigned long) &end;
low_memory_start = PAGE_SIZE;
} else {
memory_start = 1024*1024;
low_memory_start = (unsigned long) &end;
}
low_memory_start = PAGE_ALIGN(low_memory_start);
memory_start = paging_init(memory_start,memory_end);
if (strncmp((char*)0x0FFFD9, "EISA", 4) == 0)
EISA_bus = 1;
trap_init();
init_IRQ();
//调用进程0创建
sched_init();
kernel/sched.c中源码:
//进程0创建,任务状态、局部数据寄存器等挂载到全局描述符中
void sched_init(void)
{
int i;
struct desc_struct * p;
bh_base[TIMER_BH].routine = timer_bh;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
set_tss_desc(gdt+FIRST_TSS_ENTRY,&init_task.tss);
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&default_ldt,1);
set_system_gate(0x80,&system_call);
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1 ; i<NR_TASKS ; i++) {
task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
load_TR(0);
load_ldt(0);
outb_p(0x34,0x43); /* binary, mode 2, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
if (request_irq(TIMER_IRQ,(void (*)(int)) do_timer)!=0)
panic("Could not allocate timer IRQ!");
}