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 }

浙公网安备 33010602011771号