Linux内核启动过程(2.6.23)

[转载]Linux内核启动过程(2.6.23)---之一
2010/03/19 02:05 P.M.

内核版本号:2.6.23
当PC按下电源,CPU加电后,自动从0xFFFF0处开始执行代码,这个地址位于BIOS中。接着BIOS开始一系列系统检测,并在内存物理地址0处初 始化中断向量,供Linux内核启动过程中进行调用。此后,它将启动设备的第一个扇区(磁盘引导扇区,512Bytes)读入内存绝对地址0x7C00 处,并跳到这个地方开始执行(arch/i386/boot/header.s)。
注意,现在程序现在运行在16位实模式下,使用64k的段。 segment(段地址) : offset(偏移)构成了逻辑地址,段地址乘以 16 再加上 偏移 ,就得到 linear address(线性地址)。header.s(arch/i386/boot/header.s)中包含了大量的.h(头文件)。

18 #include <asm/segment.h>
19 #include <linux/utsrelease.h>
20 #include <asm/boot.h>
21 #include <asm/e820.h>
22 #include <asm/page.h>
23 #include <asm/setup.h>
24 #include "boot.h"
这些头文件包含了许多重要的宏和声明,在Linux 内核的其他程序中,都会用到。
.section ".header", "a"
.globl hdr
hdr:
上面开始了header.s的最重要的部分,hdr数据段存储了Linux kernel启动过程所需的信息数据,它的结构struct boot_params定义在 include/asm-i386/bootparam.h中。struct boot_params非常重要,它包含了其他的一些struct,并不是很复杂。下面开始了header.s程序,开始了header.s的真正的执 行。
.section ".inittext", "ax"
start_of_setup:
它首先初始化 Disk Controller(磁盘控制器),是通过 int 0x13进行的。然后设置寄存器,Zero the bss(初始化数据段),接着 call main(调用 main)跳转到:arch/i386/boot/main.c的main()函数开始执行。(终于见到我们的可亲可爱的C了,不过别高兴太早喔:~)
下面我们看看main()都做了什么(注:linux kernel 中有太多main函数,程序并不是总是从main开始执行)
copy_boot_params(); 复制 boot header 到 "zeropage"
validate_cpu(); 确保支持当前运行的CPU
set_bios_mode(); 告诉BIOS什么CPU我们将要去运行
detect_memory(); 检测Memory
keyboard_set_repeat(); 设置键盘 repeat rate (Why ?)
set_video(); 设置 Video mode
query_mca(); 获得 MCA 信息
query_voyager(); Voyager ?
query_ist(); 获得 Query Intel SpeedStep (IST) 信息
query_apm_bios(); 获得APM 信息
query_edd(); 获得EDD信息
go_to_protected_mode () ; 最后一件事,也是最重要的一件事,进入保护模式
上面只是函数调用的大致顺序,并不是真正的程序,通过这些函数我们可以一目了然地看到内核的执行过程(还是C好啊,不像汇编,得一行一行地看),当然你要 了解更多的细节,可以追踪到每一个函数中去。它通过上方面的检测,不断地填充struct boot_params结构,记住,struct boot_params 是很重要的喔。其他函数我们就不研究了,单看最后一个, go_to_protected_mode (), 究竟在哪儿? arch/i386/boot/pm.c
void go_to_protected_mode(void)
依次调用了如下函数:
realmode_switch_hook(); Hook before leaving realmode
move_kernel_around(); 把 Kernel/setup 移到 它们最终的地方
enable_a20() 开启 a20 门
reset_coprocessor(); 重置 coprocessor(协处理器)
mask_all_interrupts(); Mask(屏蔽所有中断)
setup_idt(); 开始转入保护模式……
setup_gdt();
protected_mode_jump (boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4));
protected_mode_jump ,传入了boot_params->hdr ->code32 _start作为第一个参数, 该参数在header.s
(arch/i386/boot/header.s) 设置如下 :

code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # 0x1000 = default for zImage
#else
.long 0x100000 # 0x100000 = default for big kernel
#endif
显然是跳转到 0x1000或0x100000处继续执行。
第二个参数,就是 boot_params的线性地址,注意,现在仍是实地址模式,线性地址为段地址乘16加上偏移。

(啊,天哪,又到 汇编里去了,AT&T汇编),在arch/i386/boot/pmjump.S中,
.globl protected_mode_jump
.type protected_mode_jump, @function

.code16
终于找到了,
arch/i386/boot/pmjump.S并不大,只有54行。我们看看它做了什么:
xorl %ebx, %ebx # Flag to indicate this is a boot
movl %edx, %esi # 传递过来的boot_params地址,转移到 ESI寄存器
movl %eax, 2 f # Patch ljmpl instruction 要跳转的地址 放到 2f
........
1:
movw $__BOOT_DS, %cx

movl %cr0, %edx
orb $1, %dl # Protected mode (PE) bit 设置CR0的PE位,进入保护模式!!
movl %edx, %cr0
很简单嘛 :~)
# Jump to the 32-bit entrypoin t 进入32位程序段

.byte 0x66, 0xea # ljmpl opcode 跳转指令码 (为什么不用汇编呢?不太清楚)
2 : .long 0 # offset 偏移地址
.word __BOOT_CS # segment 段 类似于 ljmp segment : offset;

.size protected_mode_jump, .-protected_mode_jump
现在我们到哪儿了呢,想必大家都迷住了吧。我找了半天,终于找到了: arch/i386/boot/compressed/head.s
********** head.S contains the 32-bit startup code.
NOTE!!! Startup happens at absolute address 0x00001000, which is also where
* the page directory will exist. The startup code will be overwritten by
* the page directory.
必定是跳转到这里了,Startup 被加载到绝对地址 0x00001000处。head.s的作用想必大家已经猜到了吧。既然在compressed文件夹中,肯定是和压缩有关的啦。对!

.text

#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/page.h>
#include <asm/boot.h>

.section ".text.head","ax",@progbits
.globl startup_32 定义了startup_32函数,从这儿开始执行

startup_32: 程序开始……
接下来,startup_32要检测是不是被加载到预定的位置了呢,怎么检查呢,这里用了一个技巧:
leal (0x1e4+4)(%esi), %esp 还记得 ESI中存的是boot_params的地址吗?这条指令就是把 栈指针指向boot_params的一个成员,而这个成员 u32 scratch就是专为程序检查是否被加载到预定位置设置的作 为栈使用的

call 1f 当 call时,一条指令的指针要入栈,就是保存到 scratch中了
1: popl %ebp 把指令指针放到 EBP中,EBP存的是当前的位置
subl $1b, %ebp 1b指的是指令在程序段中的偏移,两者之差,就是程序被加载的address了


* %ebp contains the address we are loaded at by the boot loader and %ebx
* contains the address where we should move the kernel image temporarily
* for safe in-place decompression.
* 现在 EBP中含有我们被加载的地址,而 EBX 中含有我们应该暂时把内核 image移动到的安全位置的地址

接着,就开始复制并移动内核,接着跳到移动后的地址开始执行,并为解压过程设置栈(stack)

* Do the decompression, and jump to the new kernel..
*开始解压缩,然后跳到新的内核开始执行
movl output_len(%ebx), %eax #将参数依次入栈
pushl %eax
pushl %ebp # output address
movl input_len(%ebx), %eax
pushl %eax # input_len
leal input_data(%ebx), %eax
pushl %eax # input_data
leal _end(%ebx), %eax
pushl %eax # end of the image as third argument
pushl %esi # real mode pointer as second arg
call decompress_kernel
addl $20, %esp 恢复 栈
popl %ecx
如果需要,还要再移动内核,然后:
* Jump to the decompressed kernel.

xorl %ebx,%ebx
jmp *%ebp EBP 中有解压后新内核的地址

先说下内核的的解压缩。
内核解压缩的
decompress_kernel 在arch/i386/boot/compressed/misc.c中定义,
asmlinkage void decompress_kernel(void *rmode, unsigned long end,
uch *input_data, unsigned long input_len, uch *output)
{
……
……
putstr("Uncompressing Linux... "); 很高兴啊,Uncompressing Linux...
gunzip (); 是专门解压内核的gzip,它涉及到同目录下的relocs.c文件,
还有 lib/inflate.c(主要的解压例程)
putstr("Ok, booting the kernel. /n"); 很熟悉吧,Ok,booting the kernel.
return;
}

好了,继续下去,现在Linux应该是跳转到新的解压缩后的位置开始执行了吧(跳来跳去真辛苦啊
[转载]Linux内核启动过程(2.6.23)----之二
2010/03/19 02:06 P.M.
到哪儿了呢? 还是汇编~:(

arch/i386/kernel/head.s head.s文件,578L(行代码,不小啊,不过这算是最后一个啦,坚持哈)
head.s 虽然大,但很有条理:
重新设置段
把BSS段设置为0
把 boot_params的参数重新COPY到安全的地方
Initialize page tables 初始化页表
为SMP设置页表(如果有的话)
* Enable paging
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 # set the page table pointer..设置页指针
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 # ..and set paging (PG) bit 启动分页
ljmp $__BOOT_CS,$1f # Clear prefetch and normalize %eip 设置EIP
1:
lss stack_start,%esp #Set up the stack pointer 设置 栈 指针

pushl $0 #
Initialize eflags. 把EFLAGES设置为0
popfl

检测 CPU 类型
setup_idt 设置 IDT表
include arch/i386/xen/xen-head.S
…………

360(Line) jmp start_kernel 跳转到 init/main.c
现在,终于熬过了 AT&T汇编 ,是不是很有成就感呢,下面的start_kernel要是也讲习懂了,Linux内核的其他部分,就不在话下了,加油啊,我现在水平还不够,只能列出start_kernel源码,大家一同分析
513 asmlinkage void __init start_kernel(void)
514 {
515 char * command_line;
516 extern struct kernel_param __start___param[], __stop___param[];
517
518 smp_setup_processor_id();
519
520
524 unwind_init();
525 lockdep_init();
526
527 local_irq_disable();
528 early_boot_irqs_off();
529 early_init_irq_lock_class();
530
531
535 lock_kernel();
536 tick_init();
537 boot_cpu_init();
538 page_address_init();
539 printk(KERN_NOTICE);
540 printk(linux_banner);
541 setup_arch(&command_line);
542 setup_command_line(command_line);
543 unwind_setup();
544 setup_per_cpu_areas();
545 smp_prepare_boot_cpu();
546
547
sched_init();
553
557 preempt_disable();
558 build_all_zonelists();
559 page_alloc_init();
560 printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line);
561 parse_early_param();
562 parse_args("Booting kernel", static_command_line, __start___param,
563 __stop___param - __start___param,
564 &unknown_bootoption);
565 if (!irqs_disabled()) {
566 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
567 "enabled *very* early, fixing it/n");
568 local_irq_disable();
569 }
570 sort_main_extable();
571 trap_init();
572 rcu_init();
573 init_IRQ();
574 pidhash_init();
575 init_timers();
576 hrtimers_init();
577 softirq_init();
578 timekeeping_init();
579 time_init();
580 profile_init();
581 if (!irqs_disabled())
582 printk("start_kernel(): bug: interrupts were enabled early/n");
583 early_boot_irqs_on();
584 local_irq_enable();
585
586
591 console_init();
592 if (panic_later)
593 panic(panic_later, panic_param);
594
595 lockdep_info();
596
597
602 locking_selftest();
603
604 #ifdef CONFIG_BLK_DEV_INITRD
605 if (initrd_start && !initrd_below_start_ok &&
606 initrd_start < min_low_pfn << PAGE_SHIFT) {
607 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
608 "disabling it./n",initrd_start,min_low_pfn << PAGE_SHIFT);
609 initrd_start = 0;
610 }
611 #endif
612 vfs_caches_init_early();
613 cpuset_init_early();
614 mem_init();
615 kmem_cache_init();
616 setup_per_cpu_pageset();
617 numa_policy_init();
618 if (late_time_init)
619 late_time_init();
620 calibrate_delay();
621 pidmap_init();
622 pgtable_cache_init();
623 prio_tree_init();
624 anon_vma_init();
625 #ifdef CONFIG_X86
626 if (efi_enabled)
627 efi_enter_virtual_mode();
628 #endif
629 fork_init(num_physpages);
630 proc_caches_init();
631 buffer_init();
632 unnamed_dev_init();
633 key_init();
634 security_init();
635 vfs_caches_init(num_physpages);
636 radix_tree_init();
637 signals_init();
638
639 page_writeback_init();
640 #ifdef CONFIG_PROC_FS
641 proc_root_init();
642 #endif
643 cpuset_init();
644 taskstats_init_early();
645 delayacct_init();
646
647 check_bugs();
648
649 acpi_early_init();
650
651
652 rest_init();
653 }

至此,内核正常启动起来了。太高兴了,现在还没有研究到 start_kernel 内部,再接再厉!!

start_kernel( )程序用于初始化系统内核的各个部分,包括:

*设置内存边界,调用paging_init( )初始化内存页面。
*初始化陷阱,中断通道和调度。
*对命令行进行语法分析。
*初始化设备驱动程序和磁盘缓冲区。
*校对延迟循环。

前面两篇,网友乔迁已经很详细的分析了内核的启动过程, 最终内核将跑到rest_init();
static void noinline __init_refok rest_init (void)
__releases(kernel_lock)
{
int pid;

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
unlock_kernel();

/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();

/* Call into cpu_idle with preempt disabled */
cpu_idle();
}

最后的function'rest_init' 作了以下工作:

*开辟内核线程'init'
*调用unlock_kernel
*建立内核运行的cpu_idle环, 如果没有调度,就一直死循环

所以start_kernel永远不能终止.它会无穷地循环执行cpu_idle.
最后,系统核心转向move_to_user_mode( ),以便创建初始化进程(init)。此后,进程0开始进入无限循环。
初始化进程开始执行/etc/init、/bin/init 或/sbin /init中的一个之后,系统内核就不再对程序进行直接控制了。之后系统内核的作用主要是给进程提供系统调用,以及提供异步中断事件的处理。多任务机制已 经建立起来,并开始处理多个用户的登录和fork( )创建的进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值