Linux 内核主要由 5 个模块构成,它们分别是:进程调度模块、内存管理模块、文件系统模块、进程间通信模块和网络接口模块。
首先说一下pc机的启动流程,在加电后,80X86会先进入实模式并进入地址0XFFFF0开始自动执行代码,这个地址一般带表了BIOS的代码地址,PC的BIOS将会执行硬件检测和诊断功能,并在0地址进行中断向量的初始化,最后BIOS会将启动设备的第一个扇区读入地址0X7C00位置,并从这个位置开始执行代码。
boot目录下包含三个汇编代码文件
bootsect文件是在记录在磁盘的第一个扇区中,BIOS系统会将其加载到0X7C00的位置并执行。
在bootsect.s文件中做的主要的事情如下:
1.将自身移动到0X9000位置,这一步主要是为了后面讲head和setup的代码放到boot sect上面
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
jmpi go,INITSEG
2.是利用 ROM BIOS 中断 INT 0x13 将 setup 模块从磁盘第 2 个扇区开始读到0x90200 开始处,共读 4 个扇区。在读操作过程中如果读出错,则显示磁盘上出错扇区位置,然后复位驱动器并重试,没有退路。
load_setup:
xor dx, dx ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors
int 0x13 ! read it
jnc ok_load_setup ! ok - continue
push ax ! dump error code
call print_nl
mov bp, sp
call print_hex
pop ax
xor dl, dl ! reset FDC
xor ah, ah
int 0x13
j load_setup
ok_load_setup:
3.将 system 模块加载到 0x10000(64KB)开始处
4.检查要使用哪个根文件系统设备
5.到此,所有程序都加载完毕,我们就跳转到被加载在 bootsect 后面的 setup 程序去。
setup.s的主要作用如下:
1.通过BIOS系统调用(0X13)获取系统参数并将系统参数存储在0X90000的位置(原存放bootsect.s的位置)
2.初始化GDT(全局表述表)和IDT(中断表述表)以及GDTR和IDTR寄存器
3.将system模块从bootsect.s加载进来的位置,移动到0X00000的位置
head.s被作为system的一部分并置于system的头部,所以被命名为head,其次还负责将eip指向main.c从而执行main.c进行系统中各个模块的初始化。
init目录下仅存在一个main.c文件,系统在执行完 boot/head.s 程序后就会将执行权交给 main.c。该程序虽然不长,但却包括了内核初始化的所有工作。
main.c的主体流程可以从main方法来看
1.将setup.s获取的系统参数从0X90080处取出并存到内核的全局变量中
2.计算内存的结束位置,也就是计算memory_end(机器具有的物理内存容量(字节数))的大小
3.根据物理内存容:量计算高速缓冲区的大小(buffer_memory_end)
4.计算主内存的起始位置,main_memory_start代表了主内存的位置
此时内存的分布为:system(内核程序)->高速缓冲区->虚拟盘(如果有的话)->主内存
// 内核初始化主程序。初始化结束后将以任务0(idle任务即空闲任务)的身份运行。
void main(void) /* This really IS void, no error here. */
{ /* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
// 下面这段代码用于保存:
// 根设备号 ->ROOT_DEV;高速缓存末端地址->buffer_memory_end;
// 机器内存数->memory_end;主内存开始地址->main_memory_start;
// 其中ROOT_DEV已在前面包含进的fs.h文件中声明为extern int
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO; // 复制0x90080处的硬盘参数
memory_end = (1<<20) + (EXT_MEM_K<<10); // 内存大小=1Mb + 扩展内存(k)*1024 byte
memory_end &= 0xfffff000; // 忽略不到4kb(1页)的内存数
if (memory_end > 16*1024*1024) // 内存超过16Mb,则按16Mb计
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024) // 如果内存>12Mb,则设置缓冲区末端=4Mb
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024) // 否则若内存>6Mb,则设置缓冲区末端=2Mb
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024; // 否则设置缓冲区末端=1Mb
main_memory_start = buffer_memory_end;
// 如果在Makefile文件中定义了内存虚拟盘符号RAMDISK,则初始化虚拟盘。此时主内存将减少。
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
// 以下是内核进行所有方面的初始化工作。阅读时最好跟着调用的程序深入进去看,若实在
// 看不下去了,就先放一放,继续看下一个初始化调用。——这是经验之谈。o(∩_∩)o 。;-)
mem_init(main_memory_start,memory_end); // 主内存区初始化。mm/memory.c
trap_init(); // 陷阱门(硬件中断向量)初始化,kernel/traps.c
blk_dev_init(); // 块设备初始化,kernel/blk_drv/ll_rw_blk.c
chr_dev_init(); // 字符设备初始化, kernel/chr_drv/tty_io.c
tty_init(); // tty初始化, kernel/chr_drv/tty_io.c
time_init(); // 设置开机启动时间 startup_time
sched_init(); // 调度程序初始化(加载任务0的tr,ldtr)(kernel/sched.c)
// 缓冲管理初始化,建内存链表等。(fs/buffer.c)
buffer_init(buffer_memory_end);
hd_init(); // 硬盘初始化,kernel/blk_drv/hd.c
floppy_init(); // 软驱初始化,kernel/blk_drv/floppy.c
sti(); // 所有初始化工作都做完了,开启中断
// 下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0执行。
move_to_user_mode(); // 移到用户模式下执行
if (!fork()) { /* we count on this going ok */
init(); // 在新建的子进程(任务1)中执行。
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
// pause系统调用会把任务0转换成可中断等待状态,再执行调度函数。但是调度函数只要发现系统中
// 没有其他任务可以运行是就会切换到任务0,而不依赖于任务0的状态。
for(;;) pause();
}