xv6-1

bootasm.S bootmain.c 作用是加载kernel到ELF程序头指定的物理地址(已经进入了保护模式)。

entry.S作用是实现物理地址到虚拟地址的转换。它们的链接地址是0x80100000,但是实际却被加载到物理地址运行

因为链接地址是0x80100000,所以变量的地址是0x80100000+0ffset。

开启分页之后,可以使用虚拟地址,然后一个jmp eax跳到虚拟地址,就不用V2P转换了。

实际还是按照原来的代码顺序继续向下执行,但是物理地址变成了虚拟地址。

如果开启分页机制,且这个分页不是一对一映射,那么必须改变eip。

现在转入main()函数,我们一个一个的函数看。

 

// 把 kernel end - 4M 的虚拟内存以PGSIZE大小为单位使用链表连接在一起
// 链表头为 kmem.freelist
// 主要用到的函数: freerange()/kfree()
kinit1(end, P2V(4*1024*1024)); // phys page allocator


// 建立内核映射页表,切换cr3替换原来临时的页映射
// 主要用到的函数:  setupkvm()/switchkvm()
kvmalloc();      // kernel page table
// 执行完此函数后,有如下映射关系:
//   KERNBASE..KERNBASE+EXTMEM: mapped to 0..EXTMEM (for I/O space)
//   KERNBASE+EXTMEM..data: mapped to EXTMEM..V2P(data)
//                for the kernel's instructions and r/o data
//   data..KERNBASE+PHYSTOP: mapped to V2P(data)..PHYSTOP, 
//                                  rw data + free physical memory
//   0xfe000000..0: mapped direct (devices such as ioapic)


// 获取CPU数量/CPU-id/判断是否为SMP
mpinit();        // collect info about this machine


// 初始化APIC 这里自己看这个函数的实现
lapicinit();


// 初始化GDT并且加载 这里注意gs/struct cpu *cpu/struct proc *proc
// 每个CPU都有自己独立的GDT 共享一个IDT
// Set up CPU's kernel segment descriptors.
// Run once on entry on each CPU.
seginit();     // set up segments


// Segments in proc->gdt.
#define NSEGS     7
// Per-CPU state
struct cpu {
  uchar id;                    // Local APIC ID; index into cpus[] below
  struct context *scheduler;   // swtch() here to enter scheduler
  struct taskstate ts;         // Used by x86 to find stack for interrupt
  struct segdesc gdt[NSEGS];   // x86 global descriptor table
  volatile uint started;       // Has the CPU started?
  int ncli;                    // Depth of pushcli nesting.
  int intena;                  // Were interrupts enabled before pushcli?
  
  // Cpu-local storage variables; see below
  struct cpu *cpu;
  struct proc *proc;           // The currently-running process.
};

extern struct cpu cpus[NCPU];
extern int ncpu;


// 初始化8259A PIC
picinit();       // interrupt controller
// 初始化IOAPIC
ioapicinit
// 初始化控制台 开键盘中断
// 思考键盘中断是如何与每个CPU对应的
consoleinit();   // I/O devices & their interrupts
// 初始化串口
uartinit();      // serial port
// 进程表初始化 仅仅是填充一个锁结构
pinit();         // process table
// 双向循环链表连接buffer cache块 暂不研究
binit();         // buffer cache
// 文件表初始化 仅仅是填充一个锁结构
fileinit();      // file table
// 此函数暂时不考虑 注意inode是什么:
// http://zh.wikipedia.org/wiki/Inode
iinit();         // inode cache
// ide磁盘初始化
ideinit();       // disk
// 非SMP下 8259A PIC timer 初始化
if(!ismp)
  timerinit();   // uniprocessor timer
  
// AP起始执行地址0x7000,执行内容entryother.S
// 此处是一个一个的AP执行初始化,一个执行完后再另一个
// 注意为它们申请栈 以及 传递参数的方式
// BSP必须等待所有AP初始化完毕才能继续向下执行
// AP初始化完毕都会执行 scheduler() 如果此时没有RUNNABLE进程,
// AP会在scheduler()内循环
// Start the non-boot (AP) processors.
startothers();   // start other processors

// P2V(4M) - P2V(PHYSTOP) 的虚拟内存以PGSIZE大小为单位使用链表连接在一起
// 链表头为 kmem.freelist
kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()


// 申请一个proc结构并填充
// 设置进程状态为 RUNNABLE
// 注意一个函数 inituvm()
// 我们的initcode大小不能超过PGSIZE 
userinit();      // first user process


// Common CPU setup code.
static void
mpmain(void)
{
  cprintf("cpu%d: starting\n", cpu->id);
  idtinit();       // load idt register
  // extern struct cpu *cpu asm("%gs:0");  // &cpus[cpunum()]
  // 下面这句话如果是非原子操作 ,是这样的:
  // cpus[cpunum()]->started = 1; 
  // 也就是把当前CPU对应结构的started成员赋值为1
  xchg(&cpu->started, 1); // tell startothers() we're up
  scheduler();     // start running processes
}

 

posted @ 2014-08-20 22:31  乾卦  阅读(202)  评论(0)    收藏  举报