.ld 文件是链接器配置文件或者叫链接脚本,它有自己的一套语法,链接器最终会根据链接器配置文件中的规则来生成最终的二进制文件。这里我们就不做具体的语法介绍了,有兴趣的同学请自行 Google 吧,我们只解释一下几个关键点
12345678910111213141516171819202122
/* Simple linker script for the JOS kernel.
See the GNU ld 'info' manual ("info ld") to learn the syntax. */
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SECTIONS
{
/* Link the kernel at this address: "." means the current address */
/* Must be equal to KERNLINK */
. = 0x80100000;
.text : AT(0x100000) {
*(.text .stub .text.* .gnu.linkonce.t.*)
}
/* Adjust the address for the data segment to the next page */
. = ALIGN(0x1000);
// ......
}
#include "asm.h"#include "memlayout.h"#include "mmu.h"#include "param.h"# Multiboot header. Data to direct multiboot loader..p2align2.text.globlmultiboot_headermultiboot_header:#definemagic0x1badb002#defineflags0.longmagic.longflags.long(-magic-flags)# By convention, the _start symbol specifies the ELF entry point.# Since we haven't set up virtual memory yet, our entry point is# the physical address of 'entry'..globl_start_start=V2P_WO(entry)# Entering xv6 on boot processor, with paging off..globlentryentry:#Turnonpagesizeextensionfor4Mbytepagesmovl%cr4,%eaxorl$(CR4_PSE),%eaxmovl%eax,%cr4#Setpagedirectorymovl$(V2P_WO(entrypgdir)),%eaxmovl%eax,%cr3#Turnonpaging.movl%cr0,%eaxorl$(CR0_PG|CR0_WP),%eaxmovl%eax,%cr0#Setupthestackpointer.movl$(stack+KSTACKSIZE),%esp#Jumptomain(),andswitchtoexecutingat#highaddresses.Theindirectcallisneededbecause#theassemblerproducesaPC-relativeinstruction#foradirectjump.mov$main,%eaxjmp*%eax.commstack,KSTACKSIZE
// Boot page table used in entry.S and entryother.S.// Page directories (and page tables), must start on a page boundary,// hence the "__aligned__" attribute. // Use PTE_PS in page directory entry to enable 4Mbyte pages.__attribute__((__aligned__(PGSIZE)))pde_tentrypgdir[NPDENTRIES]={// Map VA's [0, 4MB) to PA's [0, 4MB)[0]=(0)|PTE_P|PTE_W|PTE_PS,// Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)[KERNBASE>>PDXSHIFT]=(0)|PTE_P|PTE_W|PTE_PS,};//PAGEBREAK!// Blank page.
// Bootstrap processor starts running C code here.// Allocate a real stack and switch to it, first// doing some setup required for memory allocator to work.intmain(void){kinit1(end,P2V(4*1024*1024));// phys page allocatorkvmalloc();// kernel page tablempinit();// collect info about this machinelapicinit();seginit();// set up segmentscprintf("\ncpu%d: starting xv6\n\n",cpu->id);picinit();// interrupt controllerioapicinit();// another interrupt controllerconsoleinit();// I/O devices & their interruptsuartinit();// serial portpinit();// process tabletvinit();// trap vectorsbinit();// buffer cachefileinit();// file tableiinit();// inode cacheideinit();// diskif(!ismp)timerinit();// uniprocessor timerstartothers();// start other processorskinit2(P2V(4*1024*1024),P2V(PHYSTOP));// must come after startothers()userinit();// first user process// Finish setting up this processor in mpmain.mpmain();}
至此我们已经了解一台 PC 从加电启动开始如何从实模式到保护模式、内存寻址如何从分段式到分页式,启动方式如何从 BIOS 到引导区程序再从引导区程序加载内核到内存中运行。