📄 memory.c
字号:
/** linux/mm/memory.c** (C) 1991 Linus Torvalds*//** demand-loading started 01.12.91 - seems it is high on the list of* things wanted, and it should be easy to implement. - Linus*//** 需求加载是从01.12.91 开始编写的 - 在程序编制表中是呼是最重要的程序,* 并且应该是很容易编制的 - linus*//** Ok, demand-loading was easy, shared pages a little bit tricker. Shared* pages started 02.12.91, seems to work. - Linus.** Tested sharing by executing about 30 /bin/sh: under the old kernel it* would have taken more than the 6M I have free, but it worked well as* far as I could see.** Also corrected some "invalidate()"s - I wasn't doing enough of them.*//** OK,需求加载是比较容易编写的,而共享页面却需要有点技巧。共享页面程序是* 02.12.91 开始编写的,好象能够工作 - Linus。** 通过执行大约30 个/bin/sh 对共享操作进行了测试:在老内核当中需要占用多于* 6M 的内存,而目前却不用。现在看来工作得很好。** 对"invalidate()"函数也进行了修正 - 在这方面我还做的不够。*/#include <signal.h> // 信号头文件。定义信号符号常量,信号结构以及信号操作函数原型。#include <asm/system.h> // 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。#include <linux/sched.h> // 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。#include <linux/head.h> // head 头文件,定义了段描述符的简单结构,和几个选择符常量。#include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。volatile void do_exit (long code); // 进程退出处理函数,在kernel/exit.c,102 行。//// 显示内存已用完出错信息,并退出。static inline volatile voidoom (void){ printk ("out of memory\n\r"); do_exit (SIGSEGV); // do_exit()应该使用退出代码,这里用了信号值SIGSEGV(11)} // 相同值的出错码含义是“资源暂时不可用”,正好同义。// 刷新页变换高速缓冲宏函数。// 为了提高地址转换的效率,CPU 将最近使用的页表数据存放在芯片中高速缓冲中。在修改过页表// 信息之后,就需要刷新该缓冲区。这里使用重新加载页目录基址寄存器cr3 的方法来进行刷新。// 下面eax = 0,是页目录的基址。#define invalidate() \__asm__( "movl %%eax,%%cr3":: "a" (0))/* these are not to be changed without changing head.s etc *//* 下面定义若需要改动,则需要与head.s 等文件中的相关信息一起改变 */// linux 0.11 内核默认支持的最大内存容量是16M,可以修改这些定义以适合更多的内存。#define LOW_MEM 0x100000 // 内存低端(1MB)。#define PAGING_MEMORY (15*1024*1024) // 分页内存15MB。主内存区最多15M。#define PAGING_PAGES (PAGING_MEMORY>>12) // 分页后的物理内存页数。#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 指定内存地址映射为页号。#define USED 100 // 页面被占用标志,参见405 行。// CODE_SPACE(addr) ((((addr)+0xfff)&~0xfff) < current->start_code + current->end_code)。// 该宏用于判断给定地址是否位于当前进程的代码段中,参见252 行。#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \current->start_code + current->end_code)static long HIGH_MEMORY = 0; // 全局变量,存放实际物理内存最高端地址。// 复制1 页内存(4K 字节)。#define copy_page(from,to) \__asm__( "cld ; rep ; movsl":: "S" (from), "D" (to), "c" (1024): "cx", "di", "si")// 内存映射字节图(1 字节代表1 页内存),每个页面对应的字节用于标志页面当前被引用(占用)次数。static unsigned char mem_map[PAGING_PAGES] = { 0, };/** Get physical address of first (actually last :-) free page, and mark it* used. If no free pages left, return 0.*//** 获取首个(实际上是最后1 个:-)空闲页面,并标记为已使用。如果没有空闲页面,* 就返回0。*///// 取空闲页面。如果已经没有可用内存了,则返回0。// 输入:%1(ax=0) - 0;%2(LOW_MEM);%3(cx=PAGING PAGES);%4(edi=mem_map+PAGING_PAGES-1)。// 输出:返回%0(ax=页面起始地址)。// 上面%4 寄存器实际指向mem_map[]内存字节图的最后一个字节。本函数从字节图末端开始向前扫描// 所有页面标志(页面总数为PAGING_PAGES),若有页面空闲(其内存映像字节为0)则返回页面地址。// 注意!本函数只是指出在主内存区的一页空闲页面,但并没有映射到某个进程的线性地址去。后面// 的put_page()函数就是用来作映射的。unsigned longget_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。 "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), "i" (LOW_MEM), "c" (PAGING_PAGES), "D" (mem_map + PAGING_PAGES - 1):"di", "cx", "dx"); return __res; // 返回空闲页面地址(如果无空闲也则返回0)。}/** Free a page of memory at physical address 'addr'. Used by* 'free_page_tables()'*//** 释放物理地址'addr'开始的一页内存。用于函数'free_page_tables()'。*///// 释放物理地址addr 开始的一页面内存。// 1MB 以下的内存空间用于内核程序和缓冲,不作为分配页面的内存空间。voidfree_page (unsigned long addr){ if (addr < LOW_MEM) return; // 如果物理地址addr 小于内存低端(1MB),则返回。 if (addr >= HIGH_MEMORY) // 如果物理地址addr>=内存最高端,则显示出错信息。 panic ("trying to free nonexistent page"); addr -= LOW_MEM; // 物理地址减去低端内存位置,再除以4KB,得页面号。 addr >>= 12; if (mem_map[addr]--) return; // 如果对应内存页面映射字节不等于0,则减1 返回。 mem_map[addr] = 0; // 否则置对应页面映射字节为0,并显示出错信息,死机。 panic ("trying to free free page");}/** This function frees a continuos block of page tables, as needed* by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.*//** 下面函数释放页表连续的内存块,'exit()'需要该函数。与copy_page_tables()* 类似,该函数仅处理4Mb 的内存块。*///// 根据指定的线性地址和限长(页表个数),释放对应内存页表所指定的内存块并置表项空闲。// 页目录位于物理地址0 开始处,共1024 项,占4K 字节。每个目录项指定一个页表。// 页表从物理地址0x1000 处开始(紧接着目录空间),每个页表有1024 项,也占4K 内存。// 每个页表项对应一页物理内存(4K)。目录项和页表项的大小均为4 个字节。// 参数:from - 起始基地址;size - 释放的长度。intfree_page_tables (unsigned long from, unsigned long size){ unsigned long *pg_table; unsigned long *dir, nr; if (from & 0x3fffff) // 要释放内存块的地址需以4M 为边界。 panic ("free_page_tables called with wrong alignment"); if (!from) // 出错,试图释放内核和缓冲所占空间。 panic ("Trying to free up swapper memory space");// 计算所占页目录项数(4M 的进位整数倍),也即所占页表数。 size = (size + 0x3fffff) >> 22;// 下面一句计算起始目录项。对应的目录项号=from>>22,因每项占4 字节,并且由于页目录是从// 物理地址0 开始,因此实际的目录项指针=目录项号<<2,也即(from>>20)。与上0xffc 确保// 目录项指针范围有效。 dir = (unsigned long *) ((from >> 20) & 0xffc); /* _pg_dir = 0 */ 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); // 释放该页表所占内存页面。但由于页表在// 物理地址1M 以内,所以这句什么都不做。 *dir = 0; // 对相应页表的目录项清零。 } invalidate (); // 刷新页变换高速缓冲。 return 0;}/** Well, here is one of the most complicated functions in mm. It* copies a range of linerar addresses by copying only the pages.* Let's hope this is bug-free, 'cause this one I don't want to debug :-)** Note! We don't copy just any chunks of memory - addresses have to* be divisible by 4Mb (one page-directory entry), as this makes the* function easier. It's used only by fork anyway.** NOTE 2!! When from==0 we are copying kernel space for the first* fork(). Then we DONT want to copy a full page-directory entry, as* that would lead to some serious memory waste - we just copy the* first 160 pages - 640kB. Even that is more than we need, but it* doesn't take any more memory - we don't copy-on-write in the low* 1 Mb-range, so the pages can be shared with the kernel. Thus the* special case for nr=xxxx.*//** 好了,下面是内存管理mm 中最为复杂的程序之一。它通过只复制内存页面* 来拷贝一定范围内线性地址中的内容。希望代码中没有错误,因为我不想* 再调试这块代码了?。** 注意!我们并不是仅复制任何内存块 - 内存块的地址需要是4Mb 的倍数(正好* 一个页目录项对应的内存大小),因为这样处理可使函数很简单。不管怎样,* 它仅被fork()使用(fork.c 第56 行)。** 注意2!!当from==0 时,是在为第一次fork()调用复制内核空间。此时我们* 不想复制整个页目录项对应的内存,因为这样做会导致内存严重的浪费 - 我们* 只复制头160 个页面 - 对应640kB。即使是复制这些页面也已经超出我们的需求,* 但这不会占用更多的内存 - 在低1Mb 内存范围内我们不执行写时复制操作,所以
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -