Linux0.11内核源码分析2-main函数运行之物理内存划分

381 阅读5分钟

一些参数的来源

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。