一些参数的来源
init/main.c
中的main方法是如何运行的,请看这里Linux0.11内核源码分析1-main函数运行之前的准备
在boot/head.s
中,已经将main函数的地址压到栈里面,这里不是常见的call
指令,因为call
指令会把下一条指令的EIP压到栈里,等到调用ret
指令后,会把之前压到栈中的EIP值再次赋给EIP寄存器,但是这里没有使用call
指令,只是模拟了call
指令的行为,把main函数的地址压到栈里,setup_paging
结束后,EIP寄存器被恢复到main函数地址,所以main函数开始运行。
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $main
jmp setup_paging
L6:
jmp L6
struct drive_info { char dummy[32]; } drive_info; // 用于存放硬盘参数表信息
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
0x90080
这个地址是哪来的,其实都在setup.s
中设置好的,在boot/setup.s
中,获取硬盘信息,硬盘的参数原来保存在中断向量0x41号地址处的值,因为一个中断向量占据4个字节,系统初始化的时候会在地址0的地方创建占据1KB的空间创建256个中断向量,当然这是实模式下的中断向量,并且只能放在固定的内存地址,但是在保护模式下,使用idtr
保存中断描述符表,那么idt就可以放在内存的任何地方,扯远了~,下面的汇编代码就是把内存ds:[4*0x41]
= 0x0000:[4*0x41]
的数据复制到es:0x0080
= 0x9000:#0x0080
地址的地方。
这就是上面0x90080
的来历,可以看出一个磁盘信息占据16字节,drive_info
占据32字节,所以linux0.11最多支持2个硬盘。
! Get hd0 data
mov ax,#0x0000 ! ax = 0x0000
mov ds,ax
lds si,[4*0x41] ! 取中断向量0x41 的值,也即hd0 参数表的地址ds:si
mov ax,#INITSEG ! ax = 0x9000
mov es,ax
mov di,#0x0080
mov cx,#0x10 ! 循环了16次,
rep
movsb ! 每次复制一个字节
0x901FC
的由来也在Linux0.11内核源码分析1-main函数运行之前的准备这里说的比较清楚了。这里简单讲下,bootsect被复制到0x90000地址处,root_dev的偏移地址就是0x01FC,那么真实的物理地址就是0x901FC
.org 508 !0x01FC
root_dev:
.word ROOT_DEV
0x90002
的由来:
! 获取扩展内存大小(多少kB)
mov ah,#0x88
int 0x15
mov [2],ax ! 将得到信息保存到0x90002
好了把这几个值得由来搞懂了,下面正式开始分析main函数,我采用一段一段分析,要是一上来贴出那么长得代码,你还会看下去码?😂
物理内存格局初始化
memory_end = (1<<20) + (EXT_MEM_K<<10); // 1MB + 扩展内存总数(MB),即内存总数
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;
先获取总内存,因为linux最大支持16MB,后面我们都按照16MB计算,打开A20后,寻址空间可以达到4GB
然后计算缓冲区的结束位置,main_memory_start
表示主内存的起始位置,可见主内存就在缓冲区的后面,主内存就是我们进程占用的地方。
虚拟盘的设置与初始化
// 如果在Makefile文件中定义了内存虚拟盘符号RAMDISK,则初始化虚拟盘。此时主内存将减少。
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
虚拟盘设备是一种利用物理内存来模拟实际磁盘存储数据的方式。其目的主要是为了提高对“磁盘”数据的读写操作速度,以空间换时间的策略,后面很多地方都会用到,这里我们稍微讲下。 虚拟盘它位于缓冲区和主内存区之间。如果是使用虚拟盘,将会在主存的起始减去虚拟盘的大小。
kernel/blk_drv/ramdisk.c
long rd_init(long mem_start, int length)
{
int i;
char *cp;
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
rd_start = (char *) mem_start; // 虚拟盘内存起始地址
rd_length = length; // 虚拟盘的大小
cp = rd_start;
for (i=0; i < length; i++)
*cp++ = '\0'; //该内存区域清0
return(length);
}
以后如果需要操作虚拟盘都会用到request_fn
,后面再讲,然后把虚拟盘中的数据初始化为0
物理内存管理初始化
mem_init(main_memory_start,memory_end);
mm/memory.c
// linux0.11内核默认支持的最大内存容量是16MB,可以修改这些定义适合更多的内存。
#define LOW_MEM 0x100000 // 内存低端(1MB),内核所在的地方
#define PAGING_MEMORY (15*1024*1024) // 主内存区最多15M.
#define PAGING_PAGES (PAGING_MEMORY>>12) // 分页后的物理内存页面数(3840)
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 将物理地址转换为页号
#define USED 100 // 页面被占用标志.
// 物理内存映射字节图(1字节代表1页内存)。每个页面对应的字节用于标志页面当前引
// 用(占用)次数。它最大可以映射15MB的内存空间。在初始化函数mem_init()中,对于
// 不能用做主内存页面的位置均都预先被设置成USED(100).
static unsigned char mem_map [ PAGING_PAGES ] = {0,}; //如同 unsigned char mem_map [3840]
....
void mem_init(long start_mem, long end_mem)
{
int i;
// 首先将1MB到16MB范围内所有内存页面对应的内存映射字节数组项置为已占用状态,
// 即各项字节全部设置成USED(100)。PAGING_PAGES被定义为(PAGING_MEMORY>>12),
// 即1MB以上所有物理内存分页后的内存页面数(15MB/4KB = 3840).
HIGH_MEMORY = end_mem; // 设置内存最高端(16MB)
for (i=0 ; i<PAGING_PAGES ; i++)
mem_map[i] = USED;
// 然后计算主内存区起始内存start_mem处页面对应内存映射字节数组中项号i和主内存区页面数。
// 此时mem_map[]数组的第i项正对应主内存区中第1个页面。最后将主内存区中页面对应的数组项
// 清零(表示空闲)。对于具有16MB物理内存的系统,mem_map[]中对应4MB-16MB主内存区的项被清零。
i = MAP_NR(start_mem); // 主内存区位置处页面号
end_mem -= start_mem;
end_mem >>= 12; // 主内存区中的总页面数
while (end_mem-->0)
mem_map[i++]=0; // 主内存区页面对应字节值清零
}
首先定义了只能使用15MB的主内存映射表unsigned char mem_map []
,每一个字节代表1页,先循环把主存标记为以占用状态,然后把主存中的值全部清0。