linux0.11内核main.c中的内存初始化 /*非常详解*/
自从进入安全模式之后,CPU的寻址能力从1M一下子扩展到4G,物理地址=段基址(CS)*16+偏移地址(IP)的日子一去不复返了;可以想象,从这个时候的内存的初始化也就成为一个关键步骤。那么、内核究竟是怎么做的呢?下面的代码就是这个时候内核代码,
.
#define RAMDISK 32 /*这个定义是我特意加上去的,原代码中无此定义*/
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
.
void main (void )
{
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1 << 20) + (EXT_MEM_K << 10);
memory_end &= 0xfffff000;
if (memory_end > 16 * 1024 * 1024)
memory_end = 16 * 1024 * 1024;
if (memory_end > 12 * 1024 * 1024)
buffer_memory_end = 4 * 1024 * 1024;
else if (memory_end > 6 * 1024 * 1024)
buffer_memory_end = 2 * 1024 * 1024;
else
buffer_memory_end = 1 * 1024 * 1024;
main_memory_start = buffer_memory_end;
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK * 1024);
#endif
mem_init (main_memory_start, memory_end);
.
#define RAMDISK 32 /*这个定义是我特意加上去的,原代码中无此定义*/
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
.
void main (void )
{
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1 << 20) + (EXT_MEM_K << 10);
memory_end &= 0xfffff000;
if (memory_end > 16 * 1024 * 1024)
memory_end = 16 * 1024 * 1024;
if (memory_end > 12 * 1024 * 1024)
buffer_memory_end = 4 * 1024 * 1024;
else if (memory_end > 6 * 1024 * 1024)
buffer_memory_end = 2 * 1024 * 1024;
else
buffer_memory_end = 1 * 1024 * 1024;
main_memory_start = buffer_memory_end;
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK * 1024);
#endif
mem_init (main_memory_start, memory_end);
.
它完成了内存的分页、分配工作。其中调用了其他的代码,且看他是如何工作的:
ROOT_DEV = ORIG_ROOT_DEV; /* #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) */
drive_info = DRIVE_INFO; /* (*(struct drive_info *)0x90080) */
他们的意思非常想近
0x901FC 和 0x90080 所在地址开始长度unsigned short与struct drive_info长度的内存空间内容,已在前面在系统引导代码/boot/setup.s 中已做了设置
具体是:
0x90080地址开始存放的是两个硬盘参数表,0x90080~0x9008f放了第一个硬盘的参数;0x90090~0x9009f存放了第二个硬盘的参数表,具体代码
!
第一个硬盘
mov ax,#0x0000
mov ds,ax
lds si,[ 4*0x41] ! 取中断向量0x41 的值,也即hd0 参数表的地址 ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 ! 传输的目的地址: 0x9000:0x0080 = es:di
mov cx,#0x10 ! 循环0x10次没次一个字节,共传输0x10 字节。
rep
movsb
! 第二个硬盘
mov ax,#0x0000
mov ds,ax
lds si,[ 4*0x46] ! 取中断向量0x46 的值,也即hd1 参数表的地址= ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0090 ! 传输的目的地址: 0x9000:0x0090 = es:di
mov cx,#0x10
rep
movsb
mov ax,#0x0000
mov ds,ax
lds si,[ 4*0x41] ! 取中断向量0x41 的值,也即hd0 参数表的地址 ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 ! 传输的目的地址: 0x9000:0x0080 = es:di
mov cx,#0x10 ! 循环0x10次没次一个字节,共传输0x10 字节。
rep
movsb
! 第二个硬盘
mov ax,#0x0000
mov ds,ax
lds si,[ 4*0x46] ! 取中断向量0x46 的值,也即hd1 参数表的地址= ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0090 ! 传输的目的地址: 0x9000:0x0090 = es:di
mov cx,#0x10
rep
movsb
#define EXT_MEM_K (*(unsigned short *)0x90002)
其中0x90002的内容也是在Setup.s设置好的
! Get memory size (extended mem, kB) !
下面3 句取扩展内存的大小值(KB)。
! 是调用中断0x15,功能号ah = 0x88
! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)。
! 若出错则CF 置位,ax = 出错码。
mov ah,#0x88
int 0x15
mov [ 2],ax ! 将扩展内存数值存在0x90002 处(1 个字)。
! 是调用中断0x15,功能号ah = 0x88
! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)。
! 若出错则CF 置位,ax = 出错码。
mov ah,#0x88
int 0x15
mov [ 2],ax ! 将扩展内存数值存在0x90002 处(1 个字)。
具体实现还得参考一下setup.s在此处的上下文,由于这并非该文重点就讲到说到这里,其实大家在这只需要知道,EXT_MEM_K是当前物理内存的1M以后的扩展内存就可以了。
(如果你对(*(unsigned short *)0x90002) 不了解什么意思 那么请参考前一篇指针文章。)
memory_end = (1 << 20) + (EXT_MEM_K << 10); /*后面会看到内核的将占用1M的空间*/
可以知道 memory_end = 1024(K) + EXT_MEM_K(K) /*注意单位*/
memory_end &= 0xfffff000; /*因为以后的操作都是对整的4空间当一个整体进行的*/
忽略小于等于4K大小的内存,也就是说经过这个设置,linux内存的每一页大小为4K。
也许有人搞不清楚4K怎么算得?
上面将memory_end(以二进制方式)底上的12位(0xfff)全设0,2的12次方bits恰好是4K。
if (memory_end > 16 * 1024 * 1024)
memory_end = 16 * 1024 * 1024;
这里设置内存大大小最终不超过16M
也许有的人会怎么想:不是说总内存可以用到4G吗?这里怎么~~~,且看下面
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; /* 缓冲区地址不小于1M*/
接下来根据memory_end的大小设置缓冲区大小,根据前面情况如果内存比较到那么缓冲区就搞大点,小的话就只能省点用了。
main_memory_start = buffer_memory_end;
将主内存起始地址 = 缓冲区的结束地址
#ifdef RAMDISK /* 如果定义了虚拟盘,则主内存将减少。*/
main_memory_start += rd_init (main_memory_start, RAMDISK * 1024);
#endif
这个时候还要看看有没有分配虚拟盘,我在最前面的代码加了一个define在这里得到发挥作用了。
前面对 rd_init这个函数做了如下定义 extern long rd_init (long mem_start, int length);
它的作用是:虚拟盘初始化,该函数在“kernel/blk_drv/ramdisk.c”中实现
/**//* 返回内存虚拟盘ramdisk 所需的内存量 */
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';/**//*ramdisk内存空间初始化全为'\0'(null)*/
return (length);
}
对 blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; 进行追源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';/**//*ramdisk内存空间初始化全为'\0'(null)*/
return (length);
}
可以在该目录下的 blk.h 中找到如下信息
#define NR_BLK_DEV 7 / 块设备的数量。*/
extern struct blk_dev_struct blk_dev[NR_BLK_DEV]; /* 块设备数组,每种块设备占用一项。*/
/* 块设备结构。*/
struct blk_dev_struct
{
void (*request_fn) (void); /* 请求操作的函数指针。*/
struct request *current_request; /* 请求信息结构。*/
};
那么这个地方的函数指针指向谁呢?
#define DEVICE_REQUEST do_fd_request /* 设备请求函数do_fd_request()。*/
那do_fd_request函数的实现在什么地方呢?
它在/blk_dev/Ramdisk.c中
/**//* 执行虚拟盘(ramdisk)读写操作。程序结构与do_hd_request()类似(kernel/blk_drv/hd.c,294)。*/
void
do_rd_request ( void )
{
int len;
char *addr;
INIT_REQUEST; /**//* 检测请求的合法性(参见kernel/blk_drv/blk.h,127)。*/
/**//* 下面语句取得ramdisk 的起始扇区对应的内存起始位置和内存长度。*/
/**//* 其中sector << 9 表示sector * 512,CURRENT 定义为(blk_dev[MAJOR_NR].current_request)。*/
addr = rd_start + (CURRENT->sector << 9);
len = CURRENT->nr_sectors << 9;
/**//* 如果子设备号不为1 或者对应内存起始位置>虚拟盘末尾,则结束该请求,并跳转到repeat 处*/
/**//* (定义在28 行的INIT_REQUEST 内开始处)。*/
if ((MINOR (CURRENT->dev) != 1) || (addr + len > rd_start + rd_length))
{
end_request (0);
goto repeat;
}
/**//* 如果是写命令(WRITE),则将请求项中缓冲区的内容复制到addr 处,长度为len 字节。*/
if (CURRENT->cmd == WRITE)
{
(void) memcpy (addr, CURRENT->buffer, len);
/**//* 如果是读命令(READ),则将addr 开始的内容复制到请求项中缓冲区中,长度为len 字节。*/
}
else if (CURRENT->cmd == READ)
{
(void) memcpy (CURRENT->buffer, addr, len);
/**//* 否则显示命令不存在,死机。*/
}
else
panic ("unknown ramdisk-command");
/**//* 请求项成功后处理,置更新标志。并继续处理本设备的下一请求项。*/
end_request (1);
goto repeat;
}
void
do_rd_request ( void )
{
int len;
char *addr;
INIT_REQUEST; /**//* 检测请求的合法性(参见kernel/blk_drv/blk.h,127)。*/
/**//* 下面语句取得ramdisk 的起始扇区对应的内存起始位置和内存长度。*/
/**//* 其中sector << 9 表示sector * 512,CURRENT 定义为(blk_dev[MAJOR_NR].current_request)。*/
addr = rd_start + (CURRENT->sector << 9);
len = CURRENT->nr_sectors << 9;
/**//* 如果子设备号不为1 或者对应内存起始位置>虚拟盘末尾,则结束该请求,并跳转到repeat 处*/
/**//* (定义在28 行的INIT_REQUEST 内开始处)。*/
if ((MINOR (CURRENT->dev) != 1) || (addr + len > rd_start + rd_length))
{
end_request (0);
goto repeat;
}
/**//* 如果是写命令(WRITE),则将请求项中缓冲区的内容复制到addr 处,长度为len 字节。*/
if (CURRENT->cmd == WRITE)
{
(void) memcpy (addr, CURRENT->buffer, len);
/**//* 如果是读命令(READ),则将addr 开始的内容复制到请求项中缓冲区中,长度为len 字节。*/
}
else if (CURRENT->cmd == READ)
{
(void) memcpy (CURRENT->buffer, addr, len);
/**//* 否则显示命令不存在,死机。*/
}
else
panic ("unknown ramdisk-command");
/**//* 请求项成功后处理,置更新标志。并继续处理本设备的下一请求项。*/
end_request (1);
goto repeat;
}
(其中sector << 9 表示sector * 512这个问题在我前前一篇文章已做了详细的讲解)
代码中的INIT_REQUEST在blk_dev/blk.h中被 定义初始化请求宏。
#define INIT_REQUEST \
repeat: \
if (!CURRENT) \ /**//* 如果当前请求结构指针为null 则返回。*/
return ;
if (MAJOR (CURRENT->dev) != MAJOR_NR)
\ /**//* 如果当前设备的主设备号不对则死机。*/
panic (DEVICE_NAME ": request list destroyed");/**//*调用内核的Kernel/printk.c中的printk函数*/
if (CURRENT-> bh)
{
if (!CURRENT->bh->b_lock)
\ /**//* 如果在进行请求操作时缓冲区没锁定则死机。*/
panic (DEVICE_NAME ": block not locked");
}
#endif
repeat: \
if (!CURRENT) \ /**//* 如果当前请求结构指针为null 则返回。*/
return ;
if (MAJOR (CURRENT->dev) != MAJOR_NR)
\ /**//* 如果当前设备的主设备号不对则死机。*/
panic (DEVICE_NAME ": request list destroyed");/**//*调用内核的Kernel/printk.c中的printk函数*/
if (CURRENT-> bh)
{
if (!CURRENT->bh->b_lock)
\ /**//* 如果在进行请求操作时缓冲区没锁定则死机。*/
panic (DEVICE_NAME ": block not locked");
}
#endif
对于这个do_rd_request涉及到对块设备的操作,这里不做详细讲解。大家只要知道,这个函数对rd_init这个函数是对ramdisk进行初始化便可。
终于快完了,也许你会感觉看得很累,哎!我写得更累,
mem_init (main_memory_start, memory_end);
终于到内存初始化了,嘿嘿
假设我们现在的内存比较到,扩展内存超过16M
经过前面的操作我们大概知道内存是这样的
______ 16M(在该版本的linux最多支持16M内存,多余的内存将被视而不见)
| . | 前面的问题在这里得到解释
| . |
|主内存|
|———| 4M+32K(前面我定义了32)
|虚拟盘|
|———| 4M
|高速缓|
|冲区 |
|———| 1M(其实内核大小之后640K)
|内核 |
——— 0M
mem_init (main_memory_start, memory_end);最关键的函数出来了,
该函数在mm/memory.c中被实现,实现主内存初始化;
void
mem_init ( long start_mem, long end_mem)
{
int i;
HIGH_MEMORY = end_mem;
for (i = 0; i < PAGING_PAGES; i++)
mem_map[i] = USED;
i = MAP_NR (start_mem);
end_mem -= start_mem;
end_mem >>= 12;
while (end_mem-- > 0)
mem_map[i++] = 0;
}
mem_init ( long start_mem, long end_mem)
{
int i;
HIGH_MEMORY = end_mem;
for (i = 0; i < PAGING_PAGES; i++)
mem_map[i] = USED;
i = MAP_NR (start_mem);
end_mem -= start_mem;
end_mem >>= 12;
while (end_mem-- > 0)
mem_map[i++] = 0;
}
static long HIGH_MEMORY = 0; /* 全局变量,存放实际物理内存最高端地址16M。在该版本的linux最多支持16M内存,多余的内存将被视而不见*/
接下来的for循环用到如下定义:
#define PAGING_MEMORY (15*1024*1024) /*除内核占用的那1M内存,其他的内存都会被分页,中共为15MB(前面已假设内存大于16M)。即,被分页的内存最多15M(这里的15M指的是缓冲区+主内存)*/
#define PAGING_PAGES (PAGING_MEMORY>>12) /* 偏移之后恰好为分页后的物理内存页数*/
/*内存映射字节图(1 字节代表1 页内存),每个页面对应的字节用于标志页面当前被引用(占用)次数。*/
static unsigned char mem_map[PAGING_PAGES] = { 0, };
#define USED 100 /* 页面被占用标志*/
#define LOW_MEM 0x100000 /* 低端内核内存空间(1MB)。*/
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) /* 这里定义了一个函数,指定内存地址映射为页号 i。*/
end_mem -= start_mem; /* 再计算可分页处理的内存空间。*/
end_mem >>= 12; /* 从而计算出可用于分页处理的页面数。*/
while (end_mem-- > 0) /* 最后将这些可用页面对应的页面映射数组清零。*/
mem_map[i++] = 0;
到这里内存初始化真正完成,让我们回顾一个整个过程吧!
设置缓冲区地址(不小与1M)==> 如果定义了ramdisk那么设置它,并将该部分内存的值全搞成'\0' ==>将主内存设置在它后面,然后对除内核(也许缓冲区在此内存区域中)占用的那空间进行分页(每页4K),再对内存内存映射字节图进行初始化。
也许你现在在想,我该找出去散下心,我快疯了!!! 呵呵 慢慢来
地震让大伙知道:居安思危,才是生存之道。