/*
* linux/mm/memory.c
*
* (C) 1991 Linus Torvalds
*/
#include <signal.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <linux/kernel.h>
// 进程退出处理函数
volatile void do_exit(long code); // 在 kernel/exit.c,102 行
// 函数名前的关键字volatile用于告诉编译器gcc该函数不会返回。这样可以让gcc产生更
// 好一些的代码,更重要的是使用这个关键字可以避免产生某些(未初始化变量的)假警告信息。
// 显示内存已用完出错信息,并退出。
static inline volatile void oom(void)
{
printk("out of memory\n\r");
do_exit(SIGSEGV); // do_exit应该使用退出代码,这里用了信号值SIGSEGV(11)相同值的出错码含义是
// “资源暂时不可用”,正好同义。
}
// 刷新页变换高速缓冲宏函数。
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0)) // 为了提高地址转换的效率,CPU将最近使用的页表数据存放在芯片中高速缓冲中。在修
// 改过页表信息之后,就需要刷新该缓冲区。这里使用重新加载页目录基地址寄存器cr3
// 的方法来进行刷新。下面eax=0,是页目录的基址。
// linux0.11内核默认支持的最大内存容量是16MB
// 可以修改这些定义适合更多的内存
// PAGING_MEMORY是除了1MB剩下的 [主内存区域]
// MAP_NR 也得除去1MB 那就说明主内存区域开始是0号页
#define LOW_MEM 0x100000 // 内存低端(1MB) [内核所占空间]
#define PAGING_MEMORY (15*1024*1024) // 分页内存15 MB,主内存区最多15M. 转换成字节
#define PAGING_PAGES (PAGING_MEMORY>>12) // 分页后的物理内存页面数(3840) 一页的大小4kb[4 * 1024 = 2的12次方]
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 指定地址映射为页号 [物理地址]
#define USED 100 // 页面被占用标志.
// 该宏用于判断给定线性地址是否位于当前进程的代码段中
#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \ // 这个是要计算addr线性地址所在页末尾的线性地址
current->start_code + current->end_code) // &~ 4095 的意思是将末尾三位取0 [因为一个页的开始地址末尾三位就是0]
// ~ 取反操作 4095 = 0x0FFF [ 取反之后:0x1000]
// 如果是addr末尾三位取0,那么是开始的线性地址,所以要加4095
// 全局变量,存放实际物理内存最高端地址
static long HIGH_MEMORY = 0; // 在下面mem_init中有设置
// 从from处复制1页内存到to处(4K字节)
#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024))
// 物理内存映射字节图(1字节代表1页内存)
// 每个页面对应的字节用于标志页面当前引用(占用)次数
static unsigned char mem_map [ PAGING_PAGES ] = {0,}; // 它最大可以映射15MB的内存空间。在初始化函数mem_init()中,对于
// 不能用做主内存页面的位置均都预先被设置成USED(100)
// 获取首个空闲页面[物理地址],并标记为已使用(设置mem_map[])。如果没有可用内存,就返回0
// 输入:%1(ax = 0)
// %2(LOW_MEM) 内存字节位图(mem_map[])管理的起始位置
// %3(cx = PAGING_PAGES); 分页后的物理内存页面数
// %4(edi = mem_map + PAGING_PAGES - 1) 指向mem_map[]内存字节位图的最后一个字节
// 输出:返回%0(ax = 物理内存页面起始地址)
// 本函数从位图末端开始向前扫描所有页面标志(页面总数PAGING_PAGE),
// 若有页面空闲(内存位图字节为0)则返回页面地址。注意!本函数只是指
// 出在主内存区的一页空闲物理内存页面,但并没有映射到某个进程的地址
// 空间中去。后面的put_page()函数即用于把指定页面映射到某个进程地址
// 空间中。当然对于内核使用本函数并不需要再使用put_page()进行映射,
// 因为内核代码和数据空间(16MB)已经对等地映射到物理地址空间。
unsigned long get_free_page(void){
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t" // 置方向位,al(0)与对应每个页面的(di)内容比较
"jne 1f\n\t" // 如果没有等于0的字节,则跳转结束(返回0).
"movb $1,1(%%edi)\n\t" // 1 => [1+edi],将对应页面内存映像bit位置1.
"sall $12,%%ecx\n\t" // 页面数*4k = 相对页面起始地址
"addl %2,%%ecx\n\t" // 再加上低端内存地址,得页面实际物理起始地址
"movl %%ecx,%%edx\n\t" // 将页面实际起始地址->edx寄存器。
"movl $1024,%%ecx\n\t" // 寄存器ecx置计数值1024
"leal 4092(%%edx),%%edi\n\t" // 将4092+edx的位置->edi(该页面的末端地址)
"rep ; stosl\n\t" // 将edi所指内存清零(反方向,即将该页面清零)
"movl %%edx,%%eax\n" // 将页面起始地址->eax(返回值)
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
);
return __res; // 返回空闲物理页面地址(若无空闲页面则返回0).
}
// 释放物理地址addr开始的1页面内存。
void free_page(unsigned long addr)
{
if (addr < LOW_MEM) // 物理地址1MB以下的内容空间用于内核程序和缓冲
return;
if (addr >= HIGH_MEMORY)
panic("trying to free nonexistent page"); // 大于最大内存显示出错信息
addr -= LOW_MEM; // 物理地址减去低端内存地址,再除以4KB,得页面号
addr >>= 12;
if (mem_map[addr]--) // 当前引用次数大于0,减1返回
return;
mem_map[addr]=0; // 置为0 报错
panic("trying to free free page");
}
// 释放页表连续得内存块,该函数仅处理4Mb的内存块
// 根据指定的线性地址和限长(页表个数),释放对应内存页表指定的内存块并置表项为
// 空闲。页目录位于物理地址0开始处,共1024项,每项4字节,共占4K字节。每个目录项
// 指定一个页表。内核页表从物理地址0x1000处开始(紧接着目录空间),共4个页表。每
// 个页表有1024项,每项4字节。因此占4K(1页)内存。各进程(除了在内核代码中的进
// 程0和1)的页表所占据的页面在进程被创建时由内核为其主内存区申请得到。每个页表
// 项对应1耶物理内存,因此一个页表最多可映射4MB的物理内存。
// 参数:from - 起始线性基地址;size - 释放的字节长度。
int free_page_tables(unsigned long from,unsigned long size)
{
unsigned long *pg_table;
unsigned long * dir, nr;
// 首先检测参数from给出的线性基地址是否在4MB的边界处。因为该函数只能处理这
// 种情况。若from=0,则出错。说明视图释放内核和缓冲所占空间。
if (from & 0x3fffff)
panic("free_page_tables called with wrong alignment");
if (!from)
panic("Trying to free up swapper memory space");
// 计算所占页目录项数,也即所占页表数
size = (size + 0x3fffff) >> 22; // 当为4k的时候,会进1
// 0x3fffff 是22位
// 计算起始目录项
dir = (unsigned long *) ((from>>20) & 0xffc); // from>>22得到的是目录项编号
// 每一项都是4字节,即from>>22之后乘以4就得到该项的相对于页目录指针(CR3)的偏移了
// 释放页目录表和页表之间的关系[*dir = 0]
// 释放页表所占用的内存空间[free_page(0xfffff000 & *dir)]
// 释放页表和实际对应的物理基址之间的关系[*pg_table = 0]
// 释放在这个物理基址下的页面[free_page(0xfffff000 & *pg_table)]
for ( ; size-- > 0 ; dir++) { // size 现在是需要被释放内存的目录项数
if (!(1 & *dir)) // 如果该目录项无效(P位=0),则继续
continue; // 目录项的位0(P位)表示对应页表是否存在
pg_table = (unsigned long *) (0xfffff000 & *dir); // 取页表地址
for (nr=0 ; nr<1024 ; nr++) { // 每个页表有1024个页项
if (1 & *pg_table) // 若该项有效(p位=1),则释放对应页。
free_page(0xfffff000 & *pg_table)
*pg_table = 0; // 该页表项内容清零。
pg_table++; // 指向页表中下一项。
}
free_page(0xfffff000 & *dir); // 释放该页表所占内存页面。
*dir = 0; // 对应页表的目录项清零
}
invalidate(); // 在修改过页表信息之后,就需要刷新该缓冲区
// 这里使用重新加载页目录基地址寄存器cr3的方法来进行刷新
return 0;
}
// 将从from开始的大小为size的页目录对应页表的值复制到以to开始的
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;
// 源地址和目的地址都需要在4Mb内存边界地址上
if ((from & 0x3fffff) || (to & 0x3fffff))
panic("copy_page_tables called with wrong alignment");
// from_dir 和 to_dir 是在页表中的物理地址
from_dir = (unsigned long *) ((from >> 20) & 0xffc);
to_dir = (unsigned long *) ((to >> 20) & 0xffc);
size = ((unsigned) (size + 0x3fffff)) >> 22; // 计算要复制的内存块占用的页表数(也即页目录项数)
// 下面开始对每个占用的页表依次进行复制操作
for( ; size-- > 0; from_dir++, to_dir++) {
if (1 & *to_dir) // 如果目的目录项指定的页表已经存在(P=1),则出错,死机
panic("copy_page_tables: already exist");
if (!(1 & *from_dir)) // 如果此源目录项未被使用,则不用复制对应页表,跳过
continue;
// 设置源页表的属性值和给目的页表分配页面
// 所以之后的任务就是初始化目的页表中的值
from_page_table = (unsigned long *) (0xfffff000 & *from_dir); // 取当前源目录项中页表的地址
if (!(to_page_table = (unsigned long *) get_free_page())) // 为目的页表取一页空闲内存,如果返回0表示没有申请到空闲页面内存
return -1;
*to_dir = ((unsigned long) to_page_table) | 7; // 设置目的目录项信息[最后三位] 7[Usr,R/W,Present]
// 表示对应页表映射的内存页面是用户级的,并且可读写、存在
// 针对当前处理的页表,设置需复制的页面数
nr = (from == 0) ? 0xA0 : 1024; // 如果是在内核空间,则仅需复制头160页(640KB)
// 否则需要复制一个页表的所有1024页面
// 复制每个页表里面的页面
// nr是复制的页面数
// 将数据[*to_page_table &= 2][只读]从 from_page_table 复制到 to_page_table
// 如果 from_page_table 是在主存,则也要设置为[*to_page_table &= 2][只读]
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
this_page = *from_page_table;
if (!(1 & this_page)) // 如果当前源页面没有使用,则不用复制
continue;
// 如果U/S位是0,则R/W就没有作用
// 如果U/S是1,而R/W是0,那么运行在用户层的代码就只能读页面
// 如果U/S和R/W都置位,则就有写的权限
this_page &= ~2; // ~2: 1111 11101 只读
*to_page_table = this_page;
// 对于内核移动到任务0中并且调用fork()创建任务1时(用于运行init())
// 由于此时复制的页面还仍然都在内核代码区域,因此以下判断中的语句不会执行
// 任务0的页面仍然可以随时读写。只有当调用fork()的父进程代码处于主内存区
// (页面位置大于1MB)时才会执行。这种情况需要在进程调用execve(),并装载执
// 行了新程序代码时才会出现
if (this_page > LOW_MEM) {
*from_page_table = this_page; // 令源页表项所指内存页也为只读
// 因为现在开始有两个进程公用内存区了。若其中1个进程需要进行写操作,
// 则可以通过页异常写保护处理为执行写操作的进程匹配1页新空闲页面,也
// 即进行写时复制(copy on write)操作
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++; // mem_map[]仅用于管理主内存区中的页面使用情况
}
}
}
invalidate();
return 0;
}
// 把一物理内存页面映射到线性地址空间指定处。
unsigned long put_page(unsigned long page,unsigned long address) {
unsigned long tmp, *page_table;
if (page < LOW_MEM || page >= HIGH_MEMORY)
printk("Trying to put page %p at %p\n",page,address);
if (mem_map[(page-LOW_MEM)>>12] != 1) // 如果申请的页面在内存页面映射字节图中没有置位,则显示警告信息
printk("mem_map disagrees with %p at %p\n",page,address);
// 如果该目录项有对应的页表,取出来
// 否则 get_free_page,将获取到的页表地址赋给该目录项
page_table = (unsigned long *) ((address>>20) & 0xffc); // 计算指定地址在页目录表中对应的目录项指针
if ((*page_table)&1) // 如果该目录项[目录项中的值]有效(P=1)(也即指定的页表在内存中)
page_table = (unsigned long *) (0xfffff000 & *page_table); // 从中取出指定页表的地址
else {
if (!(tmp=get_free_page()))
return 0;
*page_table = tmp|7; // [User, U/S, R/W]
page_table = (unsigned long *) tmp;
}
// 将页表中的某个位置设为address
page_table[(address>>12) & 0x3ff] = page | 7; // 0x3ff[11 1111 1111] 10位,在页表中的索引值
return page;
}
取消写保护页面函数。用于页异常中断过程中写保护异常的处理(写时复制)。
// 在内核创建进程时,新进程与父进程被设置成共享代码和数据内存页面,并且所有这些
// 页面均被设置成只读页面。而当新进程或原进程需要向内存页面写数据时,CPU就会检测
// 到这个情况并产生页面写保护异常。于是在这个函数中内核就会首先判断要写的页面是
// 否被共享。若没有则把页面设置成可写然后退出。若页面是出于共享状态,则需要重新
// 申请一新页面并复制被写页面内容,以供写进程单独使用。共享被取消。本函数供下面
// do_wp_page()调用。
// 输入参数为页表项指针[就是已经确定了页表中的哪一项]。
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page, new_page;
// 即如果该内存页面此时只被一个进程使用,并且不是内核中的进程
// 就直接把属性改为可写即可,不用再重新申请一个新页面。
old_page = 0xfffff000 & *table_entry; // 取指定页表项内物理页面位置(地址)
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) { // == 1 表示页面仅被引用1次,页面没有被共享
*table_entry |= 2; // 在该页面的页表项中置R/W标志(可写)
invalidate(); // 刷新页变换高速缓冲
return;
}
// 否则就需要在主内存区申请一页空闲页面给执行写操作的进程单独使用
// 取消页面共享
// 原来父进程[XXX]和子进程使[*table_entry]用同一页面[old_page]
// 使子进程使用get_free_page(这个函数中设置了mem_map)
// 父进程的mem_map - 1
if (!(new_page=get_free_page()))
oom(); // 显示内存已用完出错信息,并退出
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]--; // 将原页面的页面映射字节数组递减1
*table_entry = new_page | 7; // 置可读写等标志(U/S、R/W、P)
invalidate();
copy_page(old_page,new_page);
}
执行写保护页处理。
// 页异常中断处理过程中调用的C函数。在page.s程序中被调用。
// 参数error_code是进程在写写保护页面时由CPU自动产生,address是页面线性地址。
void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
if (CODE_SPACE(address))
do_exit(SIGSEGV);
#endif
// do_wp_page(输入参数为页表项指针[就是已经确定了页表中的哪一项])
// ffc 就是10个1
// *((unsigned long *) ((address>>20) &0xffc)) 页目录项中的值,就是页表的地址
// 0xfffff000 & 意思是取页表的基址
// (address>>10) & 0xffc) 页目录项中的偏移地址
un_wp_page((unsigned long *)
(((address>>10) & 0xffc) + (0xfffff000 &
*((unsigned long *) ((address>>20) &0xffc)))));
}
写页面验证
// 若页面不可写,则复制页面。
// 参数address是指定页面的线性地址。
void write_verify(unsigned long address)
{
unsigned long page;
// 判断指定地址所对应页目录项的页表是否存在(P), 若不存在(P=0)则返回
if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
return;
page &= 0xfffff000; // 取页表的地址
page += ((address>>10) & 0xffc); // 在页表中的页表项偏移值
if ((3 & *(unsigned long *) page) == 1) // 如果该页面不可写(标志R/W没有置位)
un_wp_page((unsigned long *) page); // 写时复制
return;
}
取得一页空闲内存页并映射到指定线性地址处。
void get_empty_page(unsigned long address)
{
unsigned long tmp;
// 若不能取得一空闲页面,或者不能将页面放置到指定地址处,则显示内存不够的信息
// 即使执行 get_free_page() 返回0也无所谓,因为 put_page()
// 中还会对此情况再次申请空闲物理页面的
if (!(tmp=get_free_page()) || !put_page(tmp,address)) {
free_page(tmp);
oom();
}
}
尝试对当前进程指定地址处的页面进行共享处理。
// 当前进程与进程p是同一执行代码,也可以认为当前进程是由p进程执行fork操作产生的
// 进程,因此它们的代码内容一样。所以让当前进程和进程P指向同一页面
// address: 线性地址在进程空间中相对于进程基址的偏移长度值
static int try_to_share(unsigned long address, struct task_struct * p)
{
unsigned long from;
unsigned long to;
unsigned long from_page;
unsigned long to_page;
unsigned long phys_addr;
// 有点晕 大概就是取p进程和当前进程 address 处对应的页目录项
// 是否可以看成:from_page = ((address + p->start_code) >> 20) & 0xffc
from_page = to_page = ((address>>20) & 0xffc); // 求指定内存地址的页目录项
from_page += ((p->start_code>>20) & 0xffc); // 计算进程p的代码起始地址所对应的页目录项
to_page += ((current->start_code>>20) & 0xffc); // 计算当前进程中代码起始地址所对应的页目录项
// 取P进程的页表项
from = *(unsigned long *) from_page; // 取页目录项内容
if (!(from & 1)) // 若该目录项无效(P=0), 则返回
return 0;
from &= 0xfffff000; // 取该目录项对应页表地址
from_page = from + ((address>>10) & 0xffc); // 在页表里偏移指针
// 取P进程实际的物理地址
phys_addr = *(unsigned long *) from_page; // 实际的物理地址
// 检查P进程物理页面是否存在并且干净
// 0x41对应页表项中的D(dirty) 和P(present)标志
// 如果页面不干净或无效则返回
if ((phys_addr & 0x41) != 0x01)
return 0;
// 取页面地址 -> phys_addr
// 如果该页面地址不存在或小于内存低端(1M)也返回退出
phys_addr &= 0xfffff000;
if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM)
return 0;
// 取当前进程的页目录项
to = *(unsigned long *) to_page; // 取页目录项内容
if (!(to & 1)) { // 如果该目录项无效(P=0),则取空闲页面,并更新 to_page 所指的目录项
if ((to = get_free_page()))
*(unsigned long *) to_page = to | 7;
else
oom();
}
// 取当前进程的页表项
to &= 0xfffff000; // 取页表地址
to_page = to + ((address>>10) & 0xffc); // 页表地址 + 偏移地址
if (1 & *(unsigned long *) to_page) // 如果对应的页面已经存在,则出错,死机
panic("try_to_share: to_page already exists");
*(unsigned long *) from_page &= ~2; // 对p进程中页面置写保护标志(置R/W只读)
*(unsigned long *) to_page = *(unsigned long *) from_page; // 当前进程中的对应页表项指向它
invalidate(); // 刷新页变换高速缓冲
phys_addr -= LOW_MEM;
phys_addr >>= 12;
mem_map[phys_addr]++; // 对应页面映射字节数组项中的引用递增1
return 1; // 最后返回1,表示共享处理成功
}
共享页面处理。
// 在发生缺页异常时,首先看看能否与运行同一个执行文件的其他进程进行页面共享处理
// address: 线性地址在进程空间中相对于进程基址的偏移长度值
// 返回:1 - 共享操作成功,0 - 失败。
static int share_page(unsigned long address)
{
struct task_struct ** p;
// 首先检查一下当前进程的executable字段是否指向某执行文件的i节点,以判断本
// 进程是否有对应的执行文件。如果当前进程运行的执行文件的内存i节点引用
// 计数等于1(executable->i_count=1),表示当前系统中只有1个进程(即当前进程)在
// 运行该执行文件。因此无共享可言,直接退出函数。
if (!current->executable)
return 0;
if (current->executable->i_count < 2)
return 0;
// 否则搜索任务数组中所有任务, 寻找与当前进程可共享页面的进程
for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
if (!*p) // 如果该任务项空闲,则继续寻找
continue;
if (current == *p) // 如果就是当前任务,也继续寻找
continue;
if ((*p)->executable != current->executable) // 如果 executable 不等,也继续
continue;
if (try_to_share(address,*p)) // 尝试共享页面
return 1;
}
return 0;
}
// 执行缺页处理
void do_no_page(unsigned long error_code,unsigned long address) // address 是引起页面异常的线性地址
{
int nr[4];
unsigned long tmp;
unsigned long page;
int block,i;
address &= 0xfffff000; // 访问某一页中的某一位置引发缺页异常,说明这个页面[address &= 0xfffff000]没有
tmp = address - current->start_code; // 线性地址在进程空间中相对于进程基址的偏移长度值
// executable 是进程的 i 节点结构,该值为0,表明进程刚开始设置,需要内存
// 如果指定的线性地址超出代码加数据长度,表明进程在申请新的内存空间,需要内存
if (!current->executable || tmp >= current->end_data) { // current -> executable 可执行文件的i节点
// current -> end_data 代码段长度 + 数据段长度
get_empty_page(address); // 申请一页内存
return;
}
// 如果尝试分享页面成功,则退出
if (share_page(tmp))
return;
// 取空闲页面,如果内存不够了,则显示内存不够,终止进程
if (!(page = get_free_page()))
oom();
block = 1 + tmp/BLOCK_SIZE; // (程序) 头要使用1个数据块
for (i=0 ; i<4 ; block++,i++)
nr[i] = bmap(current->executable, block); // 根据 i 节点信息,取数据块在设备上对应的逻辑块号
bread_page(page, current->executable -> i_dev, nr); // 读设备上一个页面的数据(4个逻辑块)
// 到指定物理地址 page 处
// 在增加了一页内存后,该页内存的部分可能会超过进程的 end_data 位置
// 将页面超出的部分清零
i = tmp + 4096 - current->end_data;
tmp = page + 4096;
while (i-- > 0) {
tmp--;
*(char *)tmp = 0;
}
// 最后把引起缺页异常的一页物理页面映射到指定线性地址address处
if (put_page(page,address))
return;
// 释放内存页,显示内存不够
free_page(page);
oom();
}
// 物理内存管理初始化
// 该函数对1MB以上的内存区域以页面为单位进行管理前的初始化设置工作。一个页面长度
// 为4KB bytes.该函数把1MB以上所有物理内存划分成一个个页面,并使用一个页面映射字节
// 数组mem_map[]来管理所有这些页面。对于具有16MB内存容量的机器,该数组共有3840
// 项((16MB-1MB)/4KB),即可管理3840个物理页面。每当一个物理内存页面被占用时就把
// mem_map[]中对应的字节值增1;若释放一个物理页面,就把对应字节值减1。若字节值为0,
// 则表示对应页面空闲;若字节值大于或等于1,则表示对应页面被占用或被不同程序共享占用。
// 在该版本的Linux内核中,最多能管理16MB的物理内存,大于16MB的内存将弃之不用。
// 对于具有16MB内存的PC机系统,在没有设置虚拟盘RAMDISK的情况下start_mem通常是4MB,
// end_mem是16MB。因此此时主内存区范围是4MB-16MB,共有3072个物理页面可供分配。而
// 范围0-1MB内存空间用于内核系统(其实内核只使用0-640Kb,剩下的部分被部分高速缓冲和
// 设备内存占用)。
// 参数start_mem是可用做页面分配的主内存区起始地址(已去除RANDISK所占内存空间)。
// end_mem是实际物理内存最大地址。而地址范围start_mem到end_mem是主内存区。
void mem_init(long start_mem, long end_mem) {
int i;
// 首先将1MB到16MB范围内所有内存页面对应
// 的内存映射字节数组项置为已占用状态
HIGH_MEMORY = end_mem; // 初始化 HIGH_MEMORY: 最大物理内存
for (i=0 ; i<PAGING_PAGES ; i++)
mem_map[i] = USED; // 100
// 对于具有16MB物理内存的系统
// mem_map[]中对应4MB-16MB主内存区的项被清零。
i = MAP_NR(start_mem); // 主内存区起始位置处页面号[] i是在mem_map中的位置
// MAP_NR(addr) (((addr)-LOW_MEM)>>12)
end_mem -= start_mem;
end_mem >>= 12; // 主内存区中的总页面数
while (end_mem-->0)
mem_map[i++]=0; // 主内存区页面对应字节值清零
}
// 计算内存空闲页面数并显示
// [内核中没有其他地方调用该函数,Linus调试过程中用的]
void calc_mem(void) {
int i, j, k, free = 0;
long * pg_tbl;
for(i = 0; i < PAGING_PAGES; i++)
if (!mem_map[i]) free++;
printk("%d pages free (of %d)\n\r", free, PAGING_PAGES);
for(i = 2; i < 1024; i++) { // i应该等于4 页目录项0-3被内核使用
if (1 & pg_dir[i]) { // 第一位 P 位,用于确定一个表项是否可以用于地址转换过程
pg_tbl=(long *)(0xfffff000 & pg_dir[i]); // 去除 pg_dir[i] 中的属性位,页表的位置
for(j = k = 0; j < 1024; j++)
if (pg_tbl[j] & 1)
k++; // k 是 在这个页表中,有多少项是使用的
printk("Pg-dir[%d] uses %d pages\n",i,k);
}
}
}
Linux 0.11 内存管理 代码注释
最新推荐文章于 2024-10-03 12:33:50 发布