linux内核--从开机到初始进程创建

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运行加载内核代码
内核代码运行创建初始进程

加载流程很清晰,总结起来,先由硬件加载BIOS代码,再由BIOS加载内核代码,再由内核代码创建进程。每一步只需完成自己的工作,剩下的就交给下一个流程。

相关知识:

  • 为什么要加载Linux内核

    cpu运行只能读取内存中的程序,因此需要将操作系统加载到内存中;

  • Linux内核加载位置

    Linux内核是加载在RAM中,刚开始RAM什么都没有,断电后RAM中信息消失;

  • cpu运行模式:

    实模式:访问地址为程序真实物理地址。

    保护模式:程序地址为虚拟地址,有权限控制等。

  • 内核代码从何而来

    早期为软盘,现在如硬盘、ssd、网络。

3.2 硬件加载BIOS代码

硬件加载BIOS代码流程:

Created with Raphaël 2.2.0 开机加电 cpu启动 cs设置为0xFFFF、ip设置为0xFFF 基于cs:ip在ROM中寻址 加载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程序

相关知识:

  • 中断向量表

    记录中断号对应的中断服务的内存地址。

  • 中断服务程序

    具有特定功能的程序,响应中断向量表。

  • BIOS程序

    检测显卡、内存等设备,建立中断向量表和中断服务程序 。

3.4.2 BIOS加载内核代码流程

BIOS加载内核代码流程如下:

加载引导程序bootsect代码
加载setup程序
加载系统模块
运行setup代码
运行系统模块head/main

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 内核代码创建初始进程
调用main函数
进程0运行,具备主机的运算能力
进程1运行,能以文件形式与外设进行数据交互
进程2,具备人机交互的能力

由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!");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值