head.s进行完初始化后调用start_kernel(init/main.c)继续各方面的初始化,主要是调用各方面函数初始化内核的数据结构,下面对与X86系统相关的调用函数简述其(与本文相关的)功能。
1. setup_arch() (arch/i386/kernel/setup.c);设置内核可用物理地址范围(memory_start~memory_end);设置init_task.mm的范围;调用request_region(kernel/resource.c)申请I/O空间。
2. paging_init() (arch/i386/mm/init.c);取消虚拟地址0x0对物理地址的低端4MB空间的映射;根据物理地址的实际大小初始化所有的页表。
3. trap_init() (arch/i386/kernel/traps.c);在IDT中设置各种入口地址,如异常事件处理程序入口,系统调用入口,调用门等。其中,trap0~trap17为各种错误入口(溢出,0除,页错误等,错误处理函数定义在arch/i386/kernel/entry.s);trap18~trap47保留;设置系统调用(INT 0x80)的入口为system_call(arch/i386/kernel/entry.s);在GDT中设置0号进程的TSS段描述符和LDT段描述符。
4. init_IRQ() (arch/i386/kernel/irq.c);初始化IDT 中0x20~0xff项。
5. time_init() (arch/i386/kernel/time.c);读取实时时间,重新设置时钟中断irq0的中断服务程序入口。
6. mem_init() (arch/i386/mm/init.c);初始化empty_zero_page;标记已被占用的页。
Linux进程和分段分页
每当启动一个新的进程,Linux都为其创建一个进程控制块(task_struct,include/linux/sched.h)。task_struct中最重要的与存储有关的成员为mm(mm_struct* mm,include/linux/sched.h)和tss(thread_struct tss,include/asm-i386/processor.h)。在创建过程中,系统所涉及的(与分段分页相关)功能包括:
1. 每个进程(根据需要)建立新页目录(mm成员pgd_t * pgd),并将其地址置入寄存器CR3中;相关代码:new_page_tables(mm/memory.c);//创建和初始化新页目录
SET_PAGE_DIR(include/asm-i386/pgtable.h);//设置页目录基地址寄存器
2. 在GDT中添加进程对应的TSS项和LDT项,其占用的GDT项号分别记录在tss成员tr(unsigned long tr)和ldt(unsigned long ldt)中;相关代码:
_LDT / _TSS(include/asm-i386/desc.h);//换算LDT / TSS对应的GDT项号
set_ldt_desc / set_tss_desc (arch/i386/kernel/traps.c);//在GDT中添加LDT / TSS描述符
3. 创建该进程的LDT(mm成员void * segments);相关代码:
copy_segments(arch/i386/kernel/process.c);//创建进程的LDT并初始化LDT
Linux采用"按需调页"的原则来分配内存页面,从而避免页表过多占用存储空间。创建一个进程时页面分配的情况大致是这样的:进程控制块(1页);内存态堆栈(1页);页目录(1页);页表(需要的n页)。在进程以后执行的执行中,再根据需要逐渐分配更多的内存页面。
参考资料
1. "Inter Architecture Software Developer's Manual Volume 3: System Programming",
http://developer.intel.com/design/pentiumii/manuals/243192.htm
2. "Linux操作系统及实验教程",李善平 郑扣根编著,机械工业出版社
3. "Linux 内核源代码分析",Scott Maxwell著,冯锐 邢飞 刘隆国 陆丽娜译,机械工业出版社
4. "Linux 系统分析与高级编程技术",周巍松等编著,机械工业出版社