以Linux-v0.11为例。
目录结构
memory.c
1 | volatile void do_exit(long code); |
/mm/memory.c
中首先声明了这两个函数,一个为do_exit(long code)用于进程的退出,函数定义在/kernel/exit.c
中,如下:
1 | int do_exit(long code) |
此处的volatile是告诉GNU编译器该函数无返回值,用以在编译时优化代码。另一个为oom(void),用以处理无可用内存的情况。首先打印out of memory,然后调用do_exit()并传入参数为SIGSEGV。此处SIGSEGV在/include/sys/signal.h
中定义:#define SIGSEGV 11/* segmentation violation */
,值为11的出错代码含义为“资源暂时不可用”。
接下来,通过#define
定义了如下一些函数及常量标识符:
1 |
|
第一行,通过#define
定义了一个函数invalidate()
,并用内联汇编作为函数的实现。内联汇编语句在之前fork()函数的文章中讲过了,这里不再赘述。此处汇编操作语句为movl %eax,%cr3
,无输出,输入则是通过eax寄存器传入值0,"a"(0)
语法为:操作约束(输入表达式)。此处的值0表示页目录的基地址。此操作通过eax寄存器刷新cr3寄存器(页变换高速缓冲寄存器)。
然后就是定义了低端内存(应为OS内存区的位置),以及用于分页的内存大小和页数。MAP_NR(addr)
函数用于将内存地址映射为页号。USED为页面被占用标志,此标志仅用于OS内存页面。
CODE_SPACE(addr)
函数用于判断给定的线性地址是否位于当前进程的代码段中,即是否出现访问越界的情况。HIGH_MEMORY表示实际物理内存最高端地址。
copy_page(from,to)
这个函数执行的操作为:从from处复制一页内存到to处。此处用了多条汇编语句:cld将标志寄存器flag的方向标志位df清零,使变址寄存器si或di的地址指针随字串处理自增。rep为字串操作指令movsl的前置,在cx不等于0的情况下对字符串执行重复movsl操作。此处Input的S和D指代movsl的两个操作数,汇编语句为movsl S, D
,即将from中的字串赋值给to,并重复操作1024次即复制一字节(一页)内存。后面的cx,si,di则是向编译器声明当前汇编语句可能会修改这三个寄存器的值,使编译器在编译时能考虑相关优化。
mem_map为物理内存映射字节图,用以表示页面是否被使用,OS内存区会被置为100,而用户内存区的页面则是以增一和减一表示使用中和空闲。
然后定义了函数get_free_page(void):
1 | /* |
函数注释表明,该函数用于获取最后一个空闲页面,并标记为已使用,若没有空闲页面则返回0。
函数体第一句register unsigned long __res asm("ax");
为gcc内联汇编特有的定义变量的方式,此处定义了一个局部寄存器变量__res,并该变量保存在eax寄存器中,以便高效访问和操作。然后就是用内联汇编写了一段汇编代码,大致如下:
1 | std |
std
表示置方向位DF为1,即di、si的值随字串处理递减(与cld相反)。
repne
表示repeat not equal,即不相等时重复
scasb
的作用为,比较AL和ES:DI处的字段值是否相同,根据比较结果改变标志位(OF、SF、ZF、AF、PF、CF),并根据DF方向位的值使DI自增(DF=0)或自减(DF=1)。
jne 1f
,jne表示jump if not equal,不相等时跳转,1为标识符,对应此段代码最后的1:
,f表示forward向前跳转,与之对应的为backword向后跳转,向前跳转表示按代码执行顺序向前,即跳转到仍未执行的代码段,向后跳转表示按执行顺序往回跳转,即跳转到已经执行过的代码段。
所以此处std; repne; scasb; jne 1f
四句执行的操作为寻找空页面(DI值为0),若无空页面则结束此段汇编代码的执行。剩下的5~13行这一段代码则是执行找到空页面之后的处理操作。
movb $1, 1(%edi)
,把立即数1放到物理映射内存字节图的edi+1位置处,即置位字节图表示该内存页面现在被使用。
sall $12, %ecx
,在内联汇编定义中的"c"(PAGING_PAGES)
声明了将页面数存放在ecx寄存器中,sal表示shift left左移,最后的l表示操作数类型long,sall $12, %ecx
表示将ecx中的值左移12位即乘以2的12次方为4096,用以获得相对页面起始地址。
addl %2, %ecx
,此处的2前面为%,并非立即数,此处%2表示内联汇编的第二个操作数,即定义中的"i"(LOW_MEM)
,此处将低端内存地址与相对页面起始地址相加,获得实际的页面物理起始地址。
movl %ecx, %edx
,将实际的页面物理起始地址放入edx寄存器中保存。
movl $1024, %ecx
,将ecx寄存器的值置为1024。
leal 4092(%edx), %edi
,将页面物理起始地址+4096(一个页面4096字节),即将该页面的物理结束地址放入edi寄存器中(edi为变址寄存器,存放段内偏移量,此处是将段内地址指向该空页面末端)。
rep
作用为按ecx中的值执行n次的重复,当前ecx中的值为1024,即重复1024次。
stosl
作用为将eax中的值保存到ES:DI指向的位置中,并根据DF位使DI自增4(DF=0)或自减4(DF=1),由于前面已经将DF置位为1,所以此处就是使DI自减4,重复1024次则正好一个页面全部填0(eax中的值为0)。
所以此处leal ; rep ; stosl
三句的作用为将逻辑上为空(字节图值为0)的页面变为物理上为空(整个页面所有值为0)。从此处也可以看出来,页面闲置(由使用中转为空)时,仅置位字节图,不对其中数据执行擦除操作。仅当重新使用该页面时才执行数据擦除操作。
最后一句movl %edx, %eax
将保存在edx寄存器中的实际的页面物理起始地址放入到eax寄存器中用作整个函数的返回值("=a"(__res)
)。
再回到整一段函数中:
1 | /* |
汇编语句部分解释完了,后面:"=a" (__res)
表示使用eax寄存器存放返回值并赋值给__res寄存器变量。:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),"D" (mem_map+PAGING_PAGES-1)
,“0"表示内联汇编的第一个操作数,即eax,此处"0” (0)表示向eax寄存器中赋初值0。“i” 表示立即整型操作数,仅为类型约束,传入值LOW_MEM,"c"表示ecx寄存器,传入值PAGING_PAGES,"D"表示edi寄存器,传入值mem_map+PAGING_PAGES-1。后面的:"di","cx",dx"
则是向编译器声明此段内联汇编语句可能会修改di、cx、dx寄存器的值用以优化代码。
整段代码执行完成后,若无空页面,eax寄存器值仍为0,则__res值为0,return __res
返回0表示无空页面。若存在空页面,则eax寄存器值为空页面实际物理起始地址,函数返回值即为该空页面的实际物理起始地址。
接下来一个函数为free_page(unsigned long addr):
1 | /* |
首先判断传入的物理页面地址addr是否小于LOW_MEM,如果小于则表示该地址位于OS操作系统内存区中,不执行操作直接返回。再判断addr是否大于等于HIGH_MEMORY,若是则通过panic函数输出错误提示信息并陷入死循环(panic函数体最后一句为for(;;);
)。
正常通过两次判断后(页面存在且非系统页面),先将给定的地址减去低端页面地址获得相对于用户页面起始地址的页面地址。然后将该地址左移12位即除以4096获得页面号。if(mem_map[addr]--)
先判断mem_map[addr]是否为1(除了系统页面以外的其他页面表示被占用时,物理映射内存字节图均为1,无其他值),然后再执行自减操作。若为1则自减后正常返回,页面逻辑上置为空闲中。若原值为0(空闲),则重新赋值为0(自减后为-1),并通过panic函数输出错误提示信息并陷入死循环。
接下来的函数为free_page_tables:
1 | /* |
首先,在函数内定义了一个页表地址指针*pg_table
、一个页目录项指针*dir
,和一个临时变量nr
用作循环条件。然后通过if (from & 0x3fffff)
判断传入的源地址是否处于4Mb的边界,因为此函数只能处理4Mb长度的内存块,通过按位与判断,若不为0x000000、0x400000、0x800000此类4Mb边界数,则通过panic输出错误信息并停机。但当from为0x000000时,表示试图释放内核所在区域页面,同样输出错误信息并停机。
然后通过size传入的长度计算所占的页目录项数,加0x3fffff是为了进位,使得size能正常输出1页而非0页,右移22位表示除以4MB,因为一页可以表示4MB的物理内存空间,所以这里用右移22位的方式把需要复制的内存长度值除以4MB。
dir为页目录项指针,因为页目录项每项占4字节,所以实际目录项指针等于size << 2也即from >> 20,因为只移动了20位,因此最后2位是页表索引的内容,用与操作置零屏蔽。
for(;size --> 0; dir++)
循环操作页目录项,size是释放的页表个数,即页目录项数,dir是起始目录项指针。
if (!(1 & *dir))
判断页目录指针对应的数据项是否为0,为0则表示该目录项没有使用,直接continue跳过。若不为0则从该目录项中取出页表地址pg_table
,并处理该页表对应的1024个表项(for(nr = 0; nr < 1024; nr++)
),释放有效页表项(*pg_table==1
)对应的物理内存页面,清零页表项,继续处理下一个页表项。最后释放该页表(页表不是页表项,前面处理的是页表项)自身占据的物理内存页面,并继续处理下一个页目录项对应的页表。全部处理完毕后,调用函数invalidate()
刷新页变换高速缓冲寄存器并返回值0。
下一个函数为copy_page_tables,与free_page_tables大同小异:
1 | int copy_page_tables(unsigned long from,unsigned long to,long size) |
首先是定义了两个页表地址指针*from_page_table
和*to_page_table
,分别用于存放用于复制的源页表和目的页表的页表地址。this_page
和nr
均为临时变量,*from_dir
和*to_dir
分别为源地址和目的地址的页目录指针。
接下来的if语句判断页面是否位于4K边缘,并输出错误停机提示,同free_page_tables。
同free_page_tables中的dir指针和size变量,此处同样对页目录项指针from_dir和to_dir和size变量进行操作。
然后for(; size --> 0; from_dir++, to_dir++)
循环进行复制操作。先进行两个if语句判断,若目的目录项指定的页表已存在则出错停机,若源目录项为空则跳过处理。然后取出源目录项中的页表地址from_page_table
,通过if判断调用get_free_page()
函数获取空内存页面,若返回值为0表示无空闲内存,copy_page_tables函数返回-1并退出。
若有空闲内存,将目的目录项的最后三位或上7,给予该内存页面用户级可读可写的权限。然后针对当前页目录项对应的页表,设置nr变量为需要复制的页面项数。如果from等于0表示是在为第一次fork()调用复制内核空间,此时有用的页面仅有起始的160个,所以并不复制整个页目录项对应的内存页面1024个。
然后通过for(; nr-- >0; from_page_table++, to_page_table++ )
循环进行页表项的复制。先取出源页表项内容this_page = *from_page_table
,然后判断是否为0,若为0则跳过此页表项进行下一页表项的操作。先复位页表项中R/W标志(第1位置0,或一个01即~2),设置该内存页面只读,然后将该页表项复制到目的页表中,即目的页表中的内存页面只读。
如果源页表项所指的物理页面大于低端内存地址,说明正在操作复制的是用户区内存页面,需要设置mem_map[]物理内存映射字节图来管理内存页面。先将源页表中的内存页面也设置为只读,然后计算当前页表项相对于用户区内存页面起始地址的偏移量,并右移12位计算页号,再修改mem_map[]物理内存映射字节图的值。
此时由于源页表和目的页表分别归两个应用使用,操作同一块物理内存区域,因此均设置为只读,若一个进程尝试对该内存区域进行写操作,则会触发页异常写保护中断处理,进行写时复制操作,此时才真正将实际的物理内存页面进行了复制,而非仅复制页表的共享。
复制操作结束后,调用invalidate()
刷新页变换高速缓冲寄存器,并返回值0正常退出。
下一个函数为put_page:
1 | unsigned long put_page(unsigned long page,unsigned long address) |
传入的参数为page和address,address表示线性地址空间上的指定地址,page表示主内存页面的指定位置。
先定义了一个临时变量tmp
和一个页表指针*page_table
,然后就是一句注释,提示我们这里使用了页目录基址_pg_dir=0
的条件。
然后对传入的参数page进行合法性判断,是否位于正常的用户区内存中,若不合法则调用printk()
函数输出警告信息。printk()
函数在/kernel/printk.c
中定义如下:
1 | int printk(const char *fmt, ...) |
然后再判断该page页面是否已申请过,即判断其在物理内存映射字节图中是否已被置位,若没有则调用printk()
函数输出警告信息。
接着根据参数address指定的线性地址计算其对应的页目录项指针,并通过与操作取得二级页表地址。
if((*page_table)&1)
判断该页目录项是否有效,即该页表是否在内存中,若页表在内存中则直接从中取得指定的页表地址存放到page_table
变量中,若页表不在内存中则直接申请一页空闲页面,并在对应页目录项中设置相应的用户权限标志位,再将该页表地址存放到page_table
变量中。
最后在找到的页表page_table
中设置相关页表项内容,把物理页面page的地址填入表项同时置位后三位用户权限标志位。该页表项在页表中的索引值相当于线性地址21到12位组成的10比特的值(故address >> 12
)。每个页表共1024项(0~0x3ff)。此处不需要刷新页变换高速缓冲寄存器cr3,因为在调用此函数前,对应的页面为无效页项,不会被缓冲。
un_wp_page,用于取消页面写保护的函数,用于处理页异常中断过程中的写保护异常:
1 | void un_wp_page(unsigned long * table_entry) |
输入参数为*table_entry
,为页表项指针,物理地址指针。声明了旧页面和新页面两个临时变量。
old_page = 0xfffff000 & *table_entry
取参数指定的页表项中的物理页面地址。然后通过if语句判断该页面是否位于用户内存中,且是否被共享(物理映射字节图值为1表示仅引用一次,没有被共享)。若位于用户内存中且没有被共享,则置R/W标志为可写,并刷新页变换高速缓冲寄存器cr3然后返回。
若条件不满足(页面位于系统内存中或已被共享),则需要在用户内存(主内存区)中申请一页空闲页面给需要写页面的进程单独使用(new_page = get_free_page()
,oom()
函数表示out of memory内存不够申请一页空闲页面),并取消页面共享。如果原页面大于内存低端(位于用户内存中),则意味着页面是共享的,将原页面的物理映射字节图值减一(不是置为1,因为可能多个进程共享,置为1可能引发误写操作);如果原页面小于内存低端(位于系统内存中),则直接进行复制操作(因为系统内存没有用mem_map[]
管理),然后将指定的页表项内容更新为新的页面地址,并置位可读可写等权限标志。刷新页变换高速缓冲寄存器,最后调用copy_page()
将原页面内容复制到新页面上(写时复制)。
do_wp_page,处理写保护时已存在的内存页面(写时复制),调用上面的un_wp_page()函数:
1 | void do_wp_page(unsigned long error_code,unsigned long address) |
此处可以看到,注释中说明,estdio库会在代码空间执行写操作,而这句if语句就是用来判断地址是否位于代码空间并停止操作的。可以猜想一下,do_exit操作是否会在代码空间执行写操作而破坏代码呢?此处暂时不清楚。
若地址并非位于代码空间,则调用un_wp_page函数,并传入参数(((address>>10) & 0xffc) + (0xfffff000 & *((unsigned long *) ((address>>20) &0xffc))))
作为*table_entry
。
此处((address >> 10) & 0xffc)
用于计算指定线性地址中页表项在页表中的偏移地址,根据线性地址结构,address >> 12
就是页表项中的索引,但每项占4个字节,因此((address >> 12) << 2) & 0xffc
就得到了页表项在表中的偏移地址。与操作用于屏蔽线性地址低12位中最高的2位(右移10位之后的低2位就是原先的11、12位)。
(0xfffff000 & *((address >> 20) & 0xffc))
用于取目录项中页表的地址值。((address >> 20) & 0xffc)
用于取线性地址中的目录索引项在目录表中的偏移位置。address >> 22
是目录项索引值,但每项占4个字节,因此address >> 20
就是指定项在目录表中的偏移地址。& 0xffc
同样是用于屏蔽目录项索引值中最后的两位,因为最后两位仍为页表索引的内容。而*((address >> 20) & 0xffc)
则是取指定页目录表项内容中对应页表的物理地址,最后和0xfffff000进行与操作用于屏蔽掉页目录项内容中第12位的全部标志位。
页表项在页表中的偏移地址加上页目录表项内容中对应页表的物理地址就得到了参数所需的页表项的物理地址指针,将其传入un_wp_page函数中对共享的页面进行复制。
写页面时的验证函数write_verify(若不可写则复制页面,写时复制):
1 | void write_verify(unsigned long address) |
首先定义了一个变量page用于保存页目录项,然后在if语句中将address对应的页目录项的值赋值给page变量并判断是否为0,为0表示页面不存在。对于不存在的页面没有共享和写时复制可言,并且若程序对此不存在的页面执行写操作时,系统就会因为缺页异常而去执行do_no_page(),并使用put_page()为此处映射一个物理页面。
然后通过page &= 0xfffff000
从页目录项中取出页表地址。page += ((address>>10) & 0xffc)
将页表地址加上指定页面的页表项偏移量,获得对应的页表项指针。
然后通过if语句判断该页表项中的R/W位和存在位P。如果页面不可写且存在,则执行写时复制操作,否则直接退出。
取得一页空闲内存页并映射到指定线性地址处的函数get_empty_page:
1 | void get_empty_page(unsigned long address) |
传入的参数address是指定页面的线性地址。然后定义了一个临时变量tmp用于存放申请到的空闲页面在主内存页面的位置。
if语句判断是否能正常获得一页空闲页面,或者能否将取得的空闲页面放到指定的线性地址,如果有一个操作不能完成则显示out of memory,并且释放申请的页面(注释说明若tmp为0没有申请到一页空闲页面,free_page函数也能正常退出)。
try_to_share函数,尝试对当前进程指定地址处的页面进行共享:
1 | static int try_to_share(unsigned long address, struct task_struct * p) |
首先定义了from
、to
、from_page
、to_page
、phys_addr
五个变量,from_page
和to_page
用于存放指定进程和当前进程中逻辑地址address对应的页目录项。然后from
和to
用于存放页目录项的内容,phys_addr
用作临时变量。
分别求得指定进程p中和当前进程中逻辑地址address对应的页目录项。为了计算方便先求出指定逻辑地址address处的“逻辑”页目录项号,即以进程空间(0~64MB)算出的页目录项号。该“逻辑”页目录项号加上进程p在CPU的4G线性空间中起始地址对应的页目录项,即得到进程p中地址address处页面所对应的4G线性空间中的实际页目录项from_page。而“逻辑”页目录项号加上当前进程CPU的4G线性地址空间中起始地址对应的页目录项,即可最后得到当前进程中地址address处页面所对应的4G线性空间中的实际页目录项to_page。
得到了p进程和当前进程address对应的目录项后,分别对进程p和当前进程进行处理。
首先对p进程的表项进行操作(from),先取目录项内容,如果该目录项无效,表示目录项对应的二级页表不存在,于是返回。否则取该目录项对应页表地址from,用于计算逻辑地址address对应的页表项指针,并取出该页表项内容临时保存在phys_addr中。
接着判断页表项映射的物理页面是否存在且干净,0x41对应页表项中的dirty和present标志。如果页面不干净或无效则返回。然后我们从该表项中取出物理页面地址再保存在phys_addr中。最后我们再检查一下这个物理页面地址的有效性,即它不应该超过机器最大物理地址值,也不应该小于内存低端(1MB)。
然后对当前进程进行处理(to),先对当前进程的表项进行操作,以取得address对应的页表地址,并且该页表项还未映射物理页面,present位为0。
先取当前进程页目录项内容存放到变量to中。如果该页目录项无效,即目录项对应的二级页表不存在,则申请一空闲页面来存放页表,并且更新目录项to_page内容,让其指向该内存页面。
否则取目录项中的页表地址存放到变量to中,加上页表项索引值<<2,即页表项在表中偏移地址,得到页表项地址存放到变量to_page中。如果该页表项对应的物理页面已存在,说明原本想共享进程p中对应的物理页面,但现在我们已经占有了物理页面,说明内核出错,用panic打印错误信息并死机。
找到进程p中逻辑地址address处对应的干净且存在的物理页面,而且也确定了当前进程中逻辑地址address所对应的二级页表项地址之后,对其进行共享操作。首先对p进程的页表项进行修改设置写保护标志,然后让当前进程复制p进程的这个页表项。此时当前逻辑地址address处页面即被映射到p进程逻辑地址address处映射的物理页面上(两个虚拟页面同时映射同一个物理页面)。
随后刷新页变换高速缓冲。计算所操作物理页面的页面号,并将对应页面映射字节数组项中的引用递增1。最后返回1,表示共享页面成功。
share_page(),尝试找到一个进程可以与当前进程共享页面:
1 | static int share_page(unsigned long address) |
在发生缺页异常时,首先尝试能否与运行同一个执行文件的其他进程进行页面共享处理。函数先判断系统中是否存在另一个进程运行与当前进程同样的执行文件。若存在,则尝试与其共享指定地址处的页面。若系统中没有其他任务正在运行与当前进程相同的执行文件,那么共享页面的前提条件不成立,函数直接退出。
判断是否存在另一个进程也在运行同一个执行文件的方法是利用进程任务数据结构中的executable字段。该字段指向进程正在执行程序在内存中的i节点。根据该i节点的引用次数i_count可以进行这种判断。若executable->i_count
值大于1,则表明系统中可能有两个进程在运行同一个执行文件,然后再对任务结构数组中所有任务比较是否有相同的executable字段来最后确定多个进程运行着相同执行文件的情况。
函数的参数address是进程中的逻辑地址,即是当前进程欲与p进程共享页面的逻辑页面地址,**p
表示任务项指针。
首先判断当前进程的executable字段是否指向某执行文件的i节点,以判断本进程是否有对应的执行文件。如果没有,则返回0。如果executable的确指向某个i节点,则检查该i节点引用计数值。如果当前进程运行的执行文件的内存i节点引用计数等于1(executable->i_count=1
),表示当前系统中只有1个进程(即当前进程)在运行该执行文件,共享条件不满足,函数直接退出。
否则搜索任务数组中所有任务,寻找与当前进程可共享页面的进程,即运行相同执行文件的另一个进程,并尝试对指定地址的页面进行共享。如果找到某个进程p,其executable字段值与当前进程相同,则调用try_to_share()尝试进行页面共享。若共享操作成功,则函数返回值1,否则返回0,表示共享页面失败。
缺页处理函数do_no_page(),在页异常中断处理过程中被调用:
1 | void do_no_page(unsigned long error_code,unsigned long address) |
参数error_code和address是进程在访问页面时由CPU因缺页异常而自动生成的。error_code指出出错类型,address是产生该异常的页面线性地址。
该函数首先尝试与已加载的相同文件进行页面共享,或者只是由于进程动态申请页面而只需映射一页物理内存页即可。若共享操作不成功,那么只能从相应文件中读入所缺的页面到指定线性地址处。
首先取线性空间中指定地址address处页面地址,用以计算出指定线性地址在进程空间中相对于进程基址的偏移长度值tmp,即对应的逻辑地址。
若当前进程的executable节点指针空,或指定地址超出(代码 + 数据)的长度,则申请一页物理内存,并映射到指定的线性地址处。executable是进程正在运行的执行文件的i节点结构。由于任务0和任务1的代码在内核中,因此任务0、任务1以及任务1派生的没有调用过execve()的所有任务的executable都为0。若该值为0,或者参数指定的线性地址超出代码加数据长度,则表明进程在申请新的内存页面存放堆或栈中数据。因此直接调用取空闲页面函数get_empty_page()为进程申请一页物理内存并映射到指定线性地址处。进程任务结构字段start_code是线性地址空间中进程代码段地址,字段end_data是代码加数据长度。对于Linux-v0.11内核,它的代码段和数据段起始基址相同。
否则说明所缺页面在进程执行映像文件范围内,尝试进行共享页面操作,成功则退出,不成功则申请一页物理内存页面page,然后从存储设备上读取执行文件中的相应页面并映射到进程页面逻辑地址tmp处。
因为块设备上存放的执行文件映像第1块数据是程序头结构,因此在读取该文件时需要跳过第1块数据。所以需要首先计算缺页所在的数据块号。因为每块数据长度为BLOCK_SIZE = 1KB,因此一页内存可存放4个数据块。进程逻辑地址tmp除以数据块大小再加1即可得出缺少的页面在执行映像文件中的起始块号block。根据这个块号和执行文件的i节点,我们就可以从映射位图中找到对应块设备中对应的设备逻辑块号(保存在nr[]数组中)。利用bread_page()即可把这4个逻辑块读入到物理页面page中。
在读设备逻辑块操作时,可能会碰到读取位置距离文件尾部不到一个页面长度的情况,因此就可能读入一些无用的信息,i = tmp +4096 - current->end_data
到if
之前的语句就是用来处理这种情况的,此部分将超出执行文件end_data以后的部分进行清零。
最后if()
把引起缺页异常的一页物理页面映射到指定线性地址address处。若操作成功就返回。否则释放内存页,显示out of memory。
物理内存管理初始化函数mem_init():
1 | void mem_init(long start_mem, long end_mem) |
…未完待续…
page.s
…未完待续…
发布时间: 2021-06-03
最后更新: 2024-05-12
本文标题: Linux内存管理
本文链接: https://cloudflare.luhawxem.com/2021/06/03/Linux-Memory-Management/
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!