Linux-0.11内存管理模块是源码中比較难以理解的部分,如今把笔者个人的理解发表

先发Linux-0.11内核内存管理get_free_page()函数分析

有时间再写其它函数或者文件的:)

/*
 *Author  : DavidLin
 *Date    : 2014-11-11pm
 *Email   : linpeng1577@163.com or linpeng1577@gmail.com
 *world   : the city of SZ, in China
 *Ver     : 000.000.001
 *history :     editor      time            do
            1)LinPeng       2014-11-11      created this file!
            2)
 */

下面是Linus的源代码:

/*
 * Get physical address of first (actually last :-) free page, and mark it
 * used. If no free pages left, return 0.
*/
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax"); __asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
"sall $12,%%ecx\n\t"
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
"movl %%edx,%%eax\n"
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
:"di","cx","dx");
return __res;
}

1.函数目的:

    寻找mem_map[0..(PAGING_PAGES-1)]中的空暇项。即mem_map[i]==0的项,假设找到。

    就返回物理地址,找不到返回0

2.技巧:

    这段代码为何用C嵌套汇编实现?

    笔者个人觉得C函数会开辟栈帧。可能会污染任务堆栈。

    同一时候该函数须要常常频繁的调用,汇编中,寄存器级别的汇编指令操作的效率比C更高:)

3.代码分析:

(1)register unsigned long __res asm("ax");

    __res是寄存器级变量,值保存在ax寄存器中,就是说对__res的操作等于ax寄存器的操作,为效率考虑

(2)__asm__("std ; repne ; scasb\n\t"

    循环比較。找出mem_map[i]==0的页;

    std设置DF=1,所以scasb运行递减操作,涉及寄存器al, ecx, es:(e)di三个寄存器,在函数尾部的定义中

    :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

    "D" (mem_map+PAGING_PAGES-1)

    :"di","cx","dx");

    即有

al       = 0;    //假设mem_map[i] == 0,表示为空暇页,否则为已分配占用,al保存0值,用于比較

ecx    = PAGING_PAGES;                                 //主内存叶表个数

es:di =  (mem_map+PAGING_PAGES-1);   //内存管理数组最后一项

    这句指令的意思是从数组mem_map[0..(PAGING_PAGES-1)]的最后一项

    mem_map[PAGING_PAGES-1]開始,比較mem_map[i]是否等于0(0值保存在al寄存器中);

    每比較一次,es:di值减1,假设不相等,es:di值减1,即mem_map[i--],继续比較,直到ecx == 0;

    假设相等,则跳出循环

C语言实现例如以下:

    ......
index_ = 0; for (i = PAGING_PAGES-1; i != 0; i--)
{
if(0 != mem_map[i]) {
continue; //继续循环
}
else {
index_ = i; //跳出循环
break;
}
} if(0 == index_) {
goto Label_1;
} Label_1:
return index_;
......

(3)"jne 1f\n\t"

    假设mem_map[0..(PAGING_PAGES-1)]均不等于0,

    跳转到标签1f处运行,Nf表示向前标签,Nb表示向后标签,N是取值1-10的十进制数字

(4)"movb $1,1(%%edi)\n\t"

    mem_map[i]==0是mem_map[0..(PAGING_PAGES-1)]中逆序第一个找到的等于0的目标,

    将edi的最低位置1。即mem_map[i]=1,标志为该页已被占用,不是空暇位

(5)"sall $12,%%ecx\n\t"

    此时ecx保存的是mem_map[i]的下标i,即相对页面数,

    举例:

        如果mem_map[0..(PAGING_PAGES-1)]最后一个參数

    mem_map[PAGING_PAGES-1] == 0。即i == (PAGING_PAGES-1),

    所以此时*ecx == PAGING_PAGES-1;

    此时相对页面地址是4k*(PAGING_PAGES-1),

    每一页1024个4字节物理页,左移12位等于4096(2的12次方),

(6)    "addl %2,%%ecx\n\t"

    加上低端内存地址,得到实际物理地址

    %2等于LOW_MEM,在例如以下语句中定义

    "0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

    提问:

        为什么4k*(PAGING_PAGES-1)不是实际物理地址呢?

        答案是初始化的时候例如以下:

        mem_map[0..(PAGING_PAGES)]是主内存管理数组

        管理的仅仅是1-16M的空间,即PAGING_MEMORY = ((16-1)*1024*1024)

不包含0-1M(0-1M,事实上是0-640K已经被内核占用)

        #define LOW_MEM 0x100000
#define PAGING_MEMORY (15*1024*1024)
#define PAGING_PAGES (PAGING_MEMORY>>12)
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)
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;
}

(7)"movl %%ecx,%%edx\n\t"

    将ecx寄存器的值保存到edx寄存器中。即将实际物理地址保存到edx寄存器中。

(8)"movl $1024,%%ecx\n\t"

    将1024保存到ecx寄存器中,由于每一页占用4096字节(4K),

    实际物理内存,每项占用4字节,有1024项。

(9)"leal 4092(%%edx),%%edi\n\t"

    由于依照4字节对齐,所以每项占用4字节,

    取当前物理页最后一项4096 = 4096-4 = 1023*4 = (1024-1)*4 。

将该物理页面的末端保存在edi寄存器中,

    即ecx+4092处的地址保存在edi寄存器中。

(10)"rep ; stosl\n\t"

    从ecx+4092处開始,反方向,步进4,反复1024次,

    将该物理页1024项所有填入eax寄存器的值。

    在例如以下代码定义中,eax初始化为0(al=0,eax =0,ax =0)

    :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

    所以该物理页1024项所有清零。

(11)"movl %%edx,%%eax\n"

    将该物理页面起始地址放入eax寄存器中。

    Intel的EABI规则中,

    eax寄存器用于保存函数返回值

(12)"1:"

    标签1,用于"jne 1f\n\t"语句跳转返回0值。

    注意:

        eax寄存器仅仅在"movl %%edx,%%eax\n"中被赋值,

        eax寄存器初始值是'0',假设跳转到标签"1:"处,

        返回值是0。表示没有空暇物理页。

(13):"=a" (__res)

    输出寄存器列表。这里仅仅有一个,当中a表示eax寄存器

(14):"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

    "0"表示与上面同个位置的输出同样的寄存器,即"0"等于输出寄存器eax。

    即eax既是输出寄存器,同一时候也是输入寄存器,

    当然,在时间颗粒度最小的情况小,eax不能同一时候作为输入或者输出寄存器,

    仅仅能作为输入或者输出寄存器;



    "i" (LOW_MEM)是%2,从输出寄存器到输入寄存器依次编号%0,%1,%2.....%N,

    当中"i"表示马上数。不是edi的代号,edi的代号是"D";



    "c" (PAGING_PAGES)表示将ecx寄存器存入PAGING_PAGES,

    ecx寄存器代号"c"。



(15)"D" (mem_map+PAGING_PAGES-1)

    "D"使用edi寄存器,即edi寄存器保存的值是(mem_map+PAGING_PAGES-1)

    即%%edi = &mem_map[PAGING_PAGES-1]。

(16):"di","cx","dx");

    保留寄存器,告诉编译器"di","cx","dx"三个寄存器已经被分配,

    在编译器编译中,不会将这三个寄存器分配为输入或者输出寄存器。

(17)return __res;

    返回__res保存的值,

    相当于汇编的ret,隐含将eax寄存器返回。

    C语言中是显式返回。



4.汇编指令及语法规则解析。參照Intel官方文档《Volume 2A Instruction Set Reference (A-M)》

《Volume 2B Instruction Set Reference (N-Z)》,GNU汇编规则

(1)std:

    主要将ESI and/or EDI方向设置为递减。相应cld(用于方向设置为递增)

    1)Operation

    Sets the DF flag in the EFLAGS register. When the DF flag is set to 1, string operations

    decrement the index registers (ESI and/or EDI).

    This instruction’s operation is the same in non-64-bit modes and 64-bit mode.

    2)Operation

    DF -> 1;

(2)repne:

    1)Description

    Repeats a string instruction the number of times specified in the count register or

    until the indicated condition of the ZF flag is no longer met. The REP (repeat), REPE

    (repeat while equal), REPNE (repeat while not equal), REPZ (repeat while zero), and

    REPNZ (repeat while not zero) mnemonics are prefixes that can be added to one of

    the string instructions. The REP prefix can be added to the INS, OUTS, MOVS, LODS,

    and STOS instructions, and the REPE, REPNE, REPZ, and REPNZ prefixes can be

    added to the CMPS and SCAS instructions. (The REPZ and REPNZ prefixes are synonymous

    forms of the REPE and REPNE prefixes, respectively.) The behavior of the REP

    prefix is undefined when used with non-string instructions.

    The REP prefixes apply only to one string instruction at a time. To repeat a block of

    instructions, use the LOOP instruction or another looping construct. All of these

    repeat prefixes cause the associated instruction to be repeated until the count in

    register is decremented to 0. See Table 4-13.

2)Operation

    IF AddressSize = 16
THEN
Use CX for CountReg;
ELSE IF AddressSize = 64 and REX.W used
THEN Use RCX for CountReg; FI;
ELSE
Use ECX for CountReg;
FI;
WHILE CountReg = 0
DO
Service pending interrupts (if any);
Execute associated string instruction;
CountReg <- (CountReg – 1);
IF CountReg = 0
THEN exit WHILE loop; FI;
IF (Repeat prefix is REPZ or REPE) and (ZF = 0)
or (Repeat prefix is REPNZ or REPNE) and (ZF = 1)
THEN exit WHILE loop; FI;
OD;

(3)scasb:

    GNU汇编

    在汇编语言中SCASB是一条字符串操作指令,源自“SCAN String Byte”的缩写。该指令的详细操作是 :

    ---------------------------------------------------------------------------------------
Code | Mnemonic | Description
---------------------------------------------------------------------------------------
AE | SCAS m8 | Compare AL with byte at ES:(E)DI and set status flags
---------------------------------------------------------------------------------------
AF | SCAS m16 | Compare AX with word at ES:(E)DI and set status flags
---------------------------------------------------------------------------------------
AF | SCAS m32 | Compare EAX with doubleword at ES(E)DI and set status flags
---------------------------------------------------------------------------------------
AE | SCASB | Compare AL with byte at ES:(E)DI and set status flags
---------------------------------------------------------------------------------------
AF | SCASW | Compare AX with word at ES:(E)DI and set status flags
---------------------------------------------------------------------------------------
AF | SCASD | Compare EAX with doubleword at ES:(E)DI and set status flags
---------------------------------------------------------------------------------------

计算 AL - byte of [ES:EDI] , 设置对应的标志寄存器的值。

    改动寄存器EDI的值:假设标志DF为0,则 inc EDI;假设DF为1。则 dec EDI。

    SCASB指令常与循环指令REPZ/REPNZ合用。

比如。REPNZ SCASB 语句表示当 寄存器ECX>0 且 标志寄存器ZF=0,则再运行一次SCASB指令。

    比較寄存器AL的值不相等则反复查找的字

(4)sall

    如sall $12, %ecx.

    这个指令是算法左移,相当于c语言中的左移操作符<<.

    intel汇编指令中的SAL,(Shit Arithmetic left).

    依据AT&T的语法规则,

    由于是一个长型的操作(ecx),

    所以在intel汇编指令sal上加一个"l",

    即转换成sall。

(5)stosl

    STOSL指令相当于将EAX中的值保存到ES:EDI指向的地址中,

    若设置了EFLAGS中的方向位置位(即在STOSL指令前使用STD指令)

    则EDI自减4。否则(使用CLD指令)EDI自增4。

(6)eax,ax,ah,al

        00000000 00000000 00000000 00000000
|===============EAX===============|--32个0,4个字节,2个字,1个双字
|======AX=======|--16个0,2个字节,1个字
|==AH===|-----------8个0,1个字节
|===AL==|---8个0,1个字节

EAX是32位的寄器,仅仅是在原有的8086CPU的寄存器AX上添加了一倍的数据位数。

故而EAX与AX根本不可能独立,二者是总体与部分的关系。

        对EAX直接赋值,若更改了低16位自然会改变了AX值,

        而AX又能够影响EAX总体。而AH,AL寄存器和AX之间的关系也是如此。



转载请注明出处,谢谢:-)

林鹏!加油。向李云和陈皓看齐。

MyBlog   : http://blog.csdn.net/linpeng12358

MyMail   : linpeng1577@163.com or linpeng1577@gmail.com

MyGithub : DavilLin1577

    welcome everybody!

    :-)

Linux-0.11内核源代码分析系列:内存管理get_free_page()函数分析的更多相关文章

  1. Linux 0.11源码阅读笔记-内存管理

    内存管理 Linux内核使用段页式内存管理方式. 内存池 物理页:物理空闲内存被划分为固定大小(4k)的页 内存池:所有空闲物理页组成内存池,以页为单位进行分配回收.并通过位图记录了每个物理页是否空闲 ...

  2. 利用bochs调试Linux 0.11内核

    引导程序调试软件bochs,跟配套的linux0.11内核img下载地址分别是: http://sourceforge.net/projects/bochs/http://www.oldlinux.o ...

  3. cflow察看工程函数调用关系+Linux 0.11 内核实验环境

    http://savannah.gnu.org/projects/cflow http://tinylab.org/linux-0.11-lab/ http://ftp.gnu.org/gnu/cfl ...

  4. Linux 0.11源码阅读笔记-总结

    总结 Linux 0.11主要包含文件管理和进程管理两个部分.进程管理包括内存管理.进程管理.进程间通信模块.文件管理包含磁盘文件系统,打开文件内存数据.磁盘文件系统包括空闲磁盘块管理,文件数据块的管 ...

  5. Linux 0.11下信号量的实现和应用

    Linux 011下信号量的实现和应用 生产者-消费者问题 实现信号量 信号量的代码实现 关于sem_wait和sem_post sem_wait和sem_post函数的代码实现 信号量的完整代码 实 ...

  6. 【从头到脚品读 Linux 0.11 源码】第一回 最开始的两行代码

    从这一篇开始,您就将跟着我一起进入这操作系统的梦幻之旅! 别担心,每一章的内容会非常的少,而且你也不要抱着很大的负担去学习,只需要像读小说一样,跟着我一章一章读下去就好. 话不多说,直奔主题.当你按下 ...

  7. Linux 0.11源码阅读笔记-文件管理

    Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...

  8. Linux 0.11源码阅读笔记-中断过程

    Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...

  9. Linux 0.11源码阅读笔记-总览

    Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...

随机推荐

  1. UVa 10256 The Great Divide,推断两个凸包是否相离

    先从给出的两个点集中分别计算出两个凸包, 然后推断两个凸包是否相离. #include<cstdio> #include<vector> #include<cmath&g ...

  2. 程序猿都是project师吗?

    全部的程序猿都是project师吗?当然不是.project师是必修课.程序猿则是选修.project师为自己的事业工作,而程序猿做他们喜欢做的事情.project是实实在在的,编程是抽象的. 为了吸 ...

  3. webservice 远程调试配置

    在.NET 中已经默认将WEBSERVICE的远程调试功能关闭,有的时候我们需要远程调试程序的时候,就需要打开此功能我们只需在WEBSERVICE的项目的中添web.config的<system ...

  4. hdu1166 经典线段入门

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  5. poj2243 bfs

    O - 上一个题的加强版 Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:65536KB     6 ...

  6. poj3581Sequence(后缀数组)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Sequence Time Limit: 5000MS   Memory Limi ...

  7. C++语法报错收集

    1. error C2864: "OuterClass::m_outerInt": 只有静态常量整型数据成员才可以在类中初始化 class OuterClass { public: ...

  8. java使用dom4j和XPath解析XML与.net 操作XML小结

    最近研究java的dom4j包,使用 dom4j包来操作了xml 文件 包括三个文件:studentInfo.xml(待解析的xml文件), Dom4jReadExmple.java(解析的主要类), ...

  9. Java中操作时间比较好用的类

    项目中经常用到日期的操作,包括日期的格式化.下面是几个比较常用的工具类. import java.text.SimpleDateFormat; import java.util.Date; impor ...

  10. ASP.NET MVC5 生成验证码

    1 ValidateCode.cs using System; using System.Drawing; using System.Drawing.Drawing2D; using System.D ...