Linux 2.6 版内核引导过程学习

本文详细解析了Linux2.6版本内核的启动过程,包括从加电启动到内核程序成功加载的每个步骤,强调了内核作为操作系统核心的重要性,并深入讨论了与Intel x86处理器相关的内存管理和中断处理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux 2.6 版内核引导过程学习
2010年12月29日
  [b]Linux 2.6 版内核引导过程浅谈 [/b]阅读linux内核源码的过程,是一个将计算机学科的所有理论课程与实际的产品紧密联系起来的过程。阅读类UNIX的源码,在国外操作系统课程的教学过程中,曾经伴随了一代计算机人才的培养。遗憾的是,我学了几年计算机专业的课程,直到最近才感觉有了一定的积累,进而阅读了linux内核的源码,切实感受到了作为计算机科学与技术精髓的操作系统的魅力所在。
  [b]1.[/b][b]Linux和linux内核 [/b]
  很多linux的用户一直争论和比较各种linux发行版的易用性,但是大家都认同一点:linux,通常是指linux内核,而且尽管软件包管理方式不同、图形界面不同以及上层应用软件也相应不同,但却都具有相同的内核--linux内核。内核是一个操作系统最核心的部分,相当于汽车发动机、PC机处理器在各自系统中的核心地位。
  由于我内核的阅读才刚刚起步。所以仅仅针采用x86处理器的PC机器的加电启动直到linux内核程序被成功加载的过程做一点源码阅读级别的阐述。最新的内核是2.6版本的,2.6内核与上一个版本2.4内核的差异还是较大的,这不仅仅表现在源码的组织上,更在于新内核所采用的诸多新的高性能算法上。
  2.4内核的资料在网上众多,成为我读懂内核源码的最为重要的参考。试图通过寻找内核开发组织的开发者邮件列表以求了解2.4和2.6差异的过程也是艰辛的。时间所限,我并未采取这一方法,而是采取了阅读2.4资料,对比2.6新版源码的策略。
  在搜索并阅读相关资料以求搞清楚linux内核到底是如何运转起来的过程中,我决定借这次课程论文的机会,求同存异,对2.6版本的内核做一点阐述。
  linux内核相当复杂。且不说作为操作系统的内核这样的程序,编写源码需要各类技巧,仅仅是所涉及到的知识,分门别类的就有许多许多。可见,内核的阅读是个长期和反复的过程。也正是如此,一方面,linux牵涉到了计算机领域几乎所有的知识及其实际运用的过程,内核的阅读彰显着迷人的魅力;另一方面,linux内核在操作系统领域的应用如此广泛,以至于广大的嵌入式系统开发者都选择了在某种程度上深入学习linux内核。
  [b]2. [/b][b]X86 [/b][b]IBM [/b][b]PC机的启动过程 [/b]
  由于准备写这篇文章的时候,最新的稳定版的内核是2.6.35.4,鉴于学习要面向新技术的策略,源码采用了这个最新的版本。
  我对linux内核上层软件所运行的intel x86处理器相关的内存管理、中断处理的过程并不太精通,这也限制了我目前对源码的理解。当然,最为重要的是,是因为机器启动和内核启动的过程中,大量的代码都是纯AT&T汇编(.S文件)写成的,尽管汇编都是直来直去,但面向实际运用的汇编程序也确实是一个难关,何况要想深入理解,还需要大量的intel x86处理器的知识。
  一般说来,运行linux 内核的PC计算机的启动过程是这样的:
  power on -> bios -> bootloader--->kernel boot --->系统初始化(main.c init.c)--->运行应用程序
  [b]3. [/b][b]Linux[/b][b]内核[/b][b]启动协议 [/b]
  阅读文档\linux-2.6.35\Documentation\x86\boot.txt
  现代支持bzImage的内核的内存布局
  程序段地址是由grub的大小来决定的。地址x应该在bootloader所允许的范围内,尽可能的低。
  启动过程的内存地址分配情况:
  | 保护模式的内核 |
  100000 +------------------------+ | I/O memory hole |
  0A0000+------------------------+ | 为 BIOS保留 |Leave as much as possible unused ~ ~ | 命令行 |(Can also be below the X+10000 mark)
  X+10000+------------------------+ | 栈/堆 |栈/堆空间是给内核实模式代码用的
  X+08000+------------------------+ | Kernel setup |setup代码运行在内核的实模式下 | Kernel boot 扇区 |The kernel legacy boot sector.
  X +------------------------+ | Boot loader |
  001000+------------------------+ | Reserved for MBR/BIOS |
  000800+------------------------+ | Typically used by MBR |
  000600+------------------------+ | BIOS use only |
  000000+------------------------+
  [b]传统上支持image和zimage的内存布局如下[/b][b]([/b]2.4[b]内核就是这样的布局[/b][b]): [/b]
  | |
  0A0000 +------------------------+
  | Reserved for BIOS | Do not use. Reserved for BIOS EBDA.
  09A000 +------------------------+
  | Command line |
  | Stack/heap | For use by the kernel real-mode code.
  098000 +------------------------+
  | Kernel setup | The kernel real-mode code.
  090200 +------------------------+ | Kernel boot sector
  | The kernel legacy boot sector.
  090000 +------------------------+
  | Protected-mode kernel | The bulk of the kernel image.
  010000 +------------------------+
  | Boot loader | stage1 :
  grub读取磁盘第一个512字节(硬盘的 0 道 0 面 1 扇区)被称为MBR(主引导记录),也称为bootsect)。MBR由一部分bootloader的引导代码、分区表和魔数三部分组成。
  stage1_5 :
  识别各种不同的文件系统格式。这使得grub识别到文件系统。
  stage2 :
  加载系统引导菜单,加载vmlinuz和initrd
  [b]6. [/b][b]内核加载过程 [/b] 启动过程是和体系结构相关的,对于x86体系结构,对于2.6的内核,可以分为以下过程:
  1) BOIS选择启动设备。
  2)从启动设备装载bootsector,。
  3)Bootsector中的grub程序装载bzImage(包含有setup、解压缩程序和内核映像)。
  4)在保护模式下解压内核。
  5)汇编代码执行低级初始化(主要是对硬件如CPU和内存的初始化)。
  6)执行上层C语言的初始化。
  bootsect.o setup.o 解压缩程序misc.o以及内核镜像vmlinuz被压缩成bzImage文件.linux 2.6中,bootsect.S和setup.S被整合为header.S。
  注 :ELF可重定位object文件(.o) 静态链接库文件.a),这是和启动密切相关的两个文件。因为.o和.a文件被链接成为可执行文件vmlinux。
  bzImage的文件构成图如下:
  [b]vmlinuz构成: [/b]
  1. 第一个512字节 (以前是在arch/i386/boot/bootsect.S)
  2. 第二个一段代码,若干不多个512字节 (以前是在arch/i386/boot/setup.S)
  3. 保护模式下的内核代码(在\arch\x86\boot\main.c)
  [b]Vmlinuz[/b][b]文件 [/b]
  vmlinux是采用linux所支持的可执行文件格式的包含有linux内核的静态链接的可执行文件,传统上,vmlinux被称为可引导的内核镜像。vmlinuz是vmlinux的压缩文件。
  [b]bzImage[/b][b]文件 [/b]
  使用make bzImage 命令编译内核源代码,可以得到采用zlib算法压缩的zImage文件,即big zImage文件。老的zImage解压缩内核到低端内存,bzImage解压缩内核到高端内存(1M(0x100000)以上),在保护模式下执行。
  bzImage文件包含有bootsect.o + setup.o + misc.o + piggy.o.
  [b]Initrd[/b][b]文件 [/b]
  initrd是initialized ram disk 的意思。主要用于加载硬件驱动模块,辅助内核的启动。
  [b]header.S [/b]
  D:\linux-2.6.35\arch\x86\boot\header.S 部分代码:
  第一部分定义了3个节,
  .bstext,
  .bsdata,
  .header,
  这3个节共同构成了vmlinuz的第一个512字节
  BOOTSEG= 0x07C0
  SYSSEG= 0x1000
  ljmp$BOOTSEG, $start2
  start2: movw%cs, %ax movw%ax, %ds movw%ax, %es movw%ax, %ss xorw%sp, %sp sti cld movw$bugger_off_msg, %si
  bugger_off_msg: .ascii"Direct booting from floppy is no longer supported.\r\n" .ascii"Please use a boot loader program instead.\r\n" .ascii"\n" .ascii"Remove disk and press any key to reboot . . .\r\n" .byte0
  下面设置内核的属性,setup的过程需要。这些属性是: .section ".header", "a" .globlhdr
  hdr:
  setup_sects:.byte 0/* Filled in by build.c */
  root_flags:.word ROOT_RDONLY
  syssize:.long 0/* Filled in by build.c */
  ram_size:.word 0/* 已经不用了 */
  vid_mode:.word SVGA_MODE
  root_dev:.word 0/* Filled in by build.c */
  boot_flag:.word 0xAA55
  header.S的第二部分,作用如同以前的setup.S
  略。
  最后,
  # Jump to C code (should not return) calllmain 调用main.c [b]head_32.S [/b]arch/x86/boot/compressed/head_32.S 是汇编写成的32位启动代码。
  其前身是linux/boot/head.S文件。
  startup发生在在绝对地址0x00001000处的。
  head_32.s调用misc.c中的decompress_kernel()函数,将内核vmlinuz解压到0x100000处。
  head_32.S 部分源码
  /*
  * Do the decompression, and jump to the new kernel..
  */ lealz_extract_offset_negative(%ebx), %ebp /* push arguments for decompress_kernel: */ pushl%ebp/* output address */ pushl$z_input_len/* input_len */ lealinput_data(%ebx), %eax pushl%eax/* input_data */ lealboot_heap(%ebx), %eax pushl%eax/* heap area */ pushl%esi/* real mode pointer */ calldecompress_kernel addl$20, %esp
  零号页面也就是这个
  /* The so-called "zeropage" */
  struct boot_params { struct screen_info screen_info;/* 0x000 */ struct apm_bios_info apm_bios_info;/* 0x040 */ __u8 _pad2[4];/* 0x054 */ __u64 tboot_addr;/* 0x058 */ struct ist_info ist_info;/* 0x060 */ __u8 _pad3[16];/* 0x070 */ __u8 hd0_info[16];/* obsolete! *//* 0x080 */ __u8 hd1_info[16];/* obsolete! *//* 0x090 */ struct sys_desc_table sys_desc_table;/* 0x0a0 */ __u8 _pad4[144];/* 0x0b0 */ struct edid_info edid_info;/* 0x140 */ struct efi_info efi_info;/* 0x1c0 */ __u32 alt_mem_k;/* 0x1e0 */ __u32 scratch;/* Scratch field! *//* 0x1e4 */ __u8 e820_entries;/* 0x1e8 */ __u8 eddbuf_entries;/* 0x1e9 */ __u8 edd_mbr_sig_buf_entries;/* 0x1ea */ __u8 _pad6[6];/* 0x1eb */ struct setup_header hdr; /* setup header *//* 0x1f1 */ __u8 _pad7[0x290-0x1f1-sizeof(struct setup_header)]; __u32 edd_mbr_sig_buffer[EDD_MBR_SIG_MAX];/* 0x290 */ struct e820entry e820_map[E820MAX];/* 0x2d0 */ __u8 _pad8[48];/* 0xcd0 */ struct edd_info eddbuf[EDDMAXNR];/* 0xd00 */ __u8 _pad9[276];/* 0xeec */
  } __attribute__((packed));
  enum { X86_SUBARCH_PC = 0, X86_SUBARCH_LGUEST, X86_SUBARCH_XEN, X86_SUBARCH_MRST, X86_NR_SUBARCHS,
  };
  [b]Misc.c[/b][b]部分源码 [/b]
  D:\linux-2.6.35.4\arch\x86\boot\compressed\misc.c
  做字符串显示用的scroll和putstr好难懂啊:
  static void scroll(void) { int i; memcpy(vidmem, vidmem + cols * 2, (lines - 1) * cols * 2); for (i = (lines - 1) * cols * 2; i screen_info.orig_video_mode == 0 && lines == 0 && cols == 0) return; x = real_mode->screen_info.orig_x; y = real_mode->screen_info.orig_y; while ((c = *s++) != '\0') { if (c == '\n') { x = 0; if (++y >= lines) { scroll(); y--; } } else { vidmem[(x + cols * y) * 2] = c; if (++x >= cols) { x = 0; if (++y >= lines) { scroll(); y--; } } }//end else }//end while real_mode->screen_info.orig_x = x; real_mode->screen_info.orig_y = y; pos = (x + cols * y) * 2;/* Update cursor position */ outb(14, vidport); outb(0xff & (pos >> 9), vidport+1); outb(15, vidport); outb(0xff & (pos >> 1), vidport+1); } [b]Misc.c中[/b][b]这段用于解压缩内核的代码,需要具备专业知识才能写出来: [/b]
  asmlinkage void decompress_kernel(void *rmode, memptr heap, unsigned char *input_data, unsigned long input_len, unsigned char *output)
  { real_mode = rmode; if (real_mode->hdr.loadflags & QUIET_FLAG) quiet = 1; if (real_mode->screen_info.orig_video_mode == 7) { vidmem = (char *) 0xb0000; vidport = 0x3b4; } else { vidmem = (char *) 0xb8000; vidport = 0x3d4; } lines = real_mode->screen_info.orig_video_lines; cols = real_mode->screen_info.orig_video_cols; free_mem_ptr = heap;/* Heap */ free_mem_end_ptr = heap + BOOT_HEAP_SIZE; if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1)) error("Destination address inappropriately aligned");
  #ifdef CONFIG_X86_64 if (heap > 0x3fffffffffffUL) error("Destination address too large");
  #else if (heap > ((-__PAGE_OFFSET-(512关系以及具体代码的详细分析有待于今后补充.
  8.
  参考文献:
  参考了好多好多
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值