您的位置:首页 > 其它

物理页面的分配

2017-11-06 20:06 211 查看

物理页面的分配分为:均质(UMA)和非均质(NUMA);

当CONFIG_DISCONTIGMEM定义时采用NUMA的alloc_pages(),否则采用UMA的alloc_pages();

两者都采用_ _alloc_pages( )进行具体页面的分配。

struct page * fastcall
__alloc_pages(unsigned int gfp_mask, unsigned int order,
struct zonelist *zonelist)
{
const int wait = gfp_mask & __GFP_WAIT;
struct zone **zones, *z;
struct page *page;
struct reclaim_state reclaim_state;
struct task_struct *p = current;
int i;
int classzone_idx;
int do_retry;
int can_try_harder;
int did_some_progress;

might_sleep_if(wait);


分配内存时,扫描包含在zonelist数据结构中的每个内存管理区, 对于每个内存管理区,该函数将空闲页框的个数与一个阀值进行比较; 该值取决于内存分配标志、当前进程的类型及管理区被函数检查的次数。(实际上,如果空闲内存不足,那么每个内存管理区一般会被检查几次。每一次在所请求的空闲内存最低量的基础上使用更低的值进行扫描)

当空闲页面的总量在“低水位”以下,就通过rmqueue()试图从管理区中分配。他首先在恰好满足大小要求的队列里分配,如果不行,从更大的队列中分配;成功后,把剩余的部分分解成小块而链入相应的队列。失败后,循环分配下一管理区,直到成功或遇到空指针而最终失败。
要是给定分配策略中所有的页面管理区都失败了,那就只好“加大力度”再试,一是降低对页面管理区中保持“水位”的要求,二是把缓冲在管理区中的“不活跃干净页面”也考虑进去。就以PAGES_LOW再调用一次_ _alloc_pages_limit()。
还不可以 就首先唤醒内核线程 kswapd,让他设法换出一些页面。当分配策略允许等待(当前进程让出路来,其他进程可能释放内存),如果不允许等待,就以参数PAGES_MIN再调用一次_ _alloc_pages_limit( )。
当再次分配失败时,考虑失败的原因(1、一种是可分配页面的总量太少 2、总量不少,但是所要求的页面块大小却不能满足)针对于第二种,通过page_launder()把“脏页”洗净进行回收。在通过_ _free_page()释放页面时会把空闲页面拼装起尽可能大的页面块。
每回收一个页面以后都要调用rmqueue()试一下。
还是不可以,将不惜用老本在调用_ _alloc_pages( ),如果还不可以就一定时系统出现了问题。

页面的定期换出

在Linux内核中设置了一个专司定期将页面换出的“守护神”kswapd。(决定多久换出一次,每次换出多少页面等)
首先通过kswapd_init(在系统初始化的期间受到调用的)。他做两件事,第一件在swap_steup()中根据内存大小设定一个全局量page_cluster;这与磁盘设备有关,由于读磁盘时要先寻道,并且寻道是一个比较费时间的操作。(所以每次都进行预读,每次都暂存更多的内存页面);第二件是创建线程kswapd,由kernel_thread()完成,还创建另一个线程kreclaimd,也与存储管理有关。 
kswapd循环运行,运行到末尾会进入睡眠。一定时间后再次从开始处运行。(一定时间由常数HZ决定,HZ决定了内核每秒钟有多少次时钟中断)kswapd分为两部分。一部分在发现物理页面短缺时进行,目的在于预先找出是若干页面,将映射断开,将活跃的状态转入不活跃状态,为页面换出做好准备。第二部分时每次都要执行,将不活跃的“脏”页面写入设备,变成不活跃“干净”页面继续缓冲,或进一步回收成为空闲页面。
如果发现可供分配的内存页面短缺,那就设法释放和换出若干页面,通过do_try_to_free_pages( )完成的。
在do_try_to_free_pages( )中一般按照LRU(最近最少用到)为准则。首先调用page_launder( )(页面洗衣工),将不活跃“脏”页面洗净,使其变成可立即分配的页面。
对不活跃的“脏”页面队列的扫描是通过while循环进行的。并有maxscan变量对扫描的数量加以控制。
- -页面扫描,首先检查PG_inactive_dirty的标志位为1 ;
当TryLockPage()返回1,表明正在对此页面进行操作,如输入/输出,将其移到队列尾部(通过计数不会再被扫描)。
如果第二次还是脏页,将页面写出之前,首先通过ClearPageDirty()将页面的PG_dirty标志位清0,然后通过由所属address_space数据结构所提供的函数将页面写出去。
如果页面不再是脏页,并且又是用作文件读/写缓冲页面,则先使他脱离不活跃“脏”页面队列,再通过try_to_free_buffers()将页面释放,如果不能释放则根据返回值将其退回“脏”队列,或链入活跃页面队列,或不活跃“干净”页面队列。
如果页面不短缺,则扫描结束,否则继续扫描。
如果前面的操作都无法处理,则将其链入活跃页面队列中。

当do_try_to_free_pages( )回收的页面还是不足,将进一步回收,(shrink_dcache_memrory()、shrink_icache_memrory()、refill_inactive)shrink_dcache_memrory()、shrink_icache_memrory()回收放在LRU队列中作为后备的inode,dentry数据结构。

refill_inactive( )通过循环回收页面(从最低级6级到最高级0级)

【在特殊的情况下,可能最终也还是达不到要求,此时就调用oom_kill()从系统中杀死一个进程,通过牺牲区部来保留全区】

再循环中,每次开头检查一下task_struct结构中的need_reached是否为1。是的话,就说明某个中断服务程序要求调度,所以调用schedule()让内核调度,将状态设置成TASK_RUNNING,表达要继续运行的意愿。
在循环中主要有二件事;一件通过refill_inactive_scan( )扫描活跃页面队列,试图找到可以转到不活跃状态的页面。另一件通过swap_out()找出一个进程,扫描其映射表,从中找出可以转入不活跃状态的页面。
--refill_inactive_scan( ),首先检查页面寿命(页面是否受到访问,决定增加或减少页面的寿命),寿命为0或许可以移到不活跃状态;但还要看是否有用户空间映射(使用计数是否为0),不为0将其移到队列尾部(swap_out扫描相应进程的映射表时才能转到不活跃状态),为0可以,成功将一个页面转入不活跃状态,根据参数oneshot的值决定是否继续扫描。

--swap_out()函数,首先是一个for循环,循环次数取决于counter,counter根据内核中进程(包括线程)的个数和调用swap_out()优先级(最初为6级,逐次上升至0级)计算而得。(这个次数决定循环多少次)
在每次循环中,程序试图从所有的进程中找到一个最合适的进程best。找到了就扫描这个进程的页面映射表,将符合条件的页面暂时断开对内存页面的映射,或进一步将页面转入不活跃状态,为把这些页面换出到交换设备上做准备。
第二个for循环表示从第二个进程开始扫描所有的进程,(init_task是内核中第一个进程,是其他进程的祖宗)。扫描从中找出mm->swap_cnt为最大的进程。每次考察和处理这个进程的一个页面就将mm->swap_cnt减1。(mm->res反映了一个进程占用的内存页面数量,而mm->swap_cnt反映了该进程在一轮换出内存页面的努力中尚未受到考察的页面数量。)
当到了所有进程的mm->swap_cnt都变成了0,从而扫描下来竟找不到一个“best”时,将assign置为1,将每个进程当前的mm->res拷贝到mm->swap_cnt中,然后从最富有的进程开始。
找到一个“最佳对象”’best以后,就要依次考察该进程的映射表。将符合条件的页面交换出去。通过swap_out_mm( )完成。
--swap_out_mm( )通过mm->swap_address找到相应的虚存空间Vma然后通过swap_out_vma( ),swap_out_pgd( ),swap_out_pmd( ),一直到try_to_swap_out( ),试图换出一个页面表项pte所指向的内存页面。
--try_to_swap_out( )会通过pte_present()来测试该表项是否所指的物理页面是否在内存中,如果不在就转到out_faild,返回0,然后函数返回上一层继续向下寻找。
   如果物理页面的确存在内存中 ,就通过pte_page( )将页面表项的内容换算成指向该物理内存页面的page结构的指针。并判断page结构指针是否有效。然后将mm->swap_cnt减1;然后检查标志位查看可换的物理页面在哪个队列,并判断最近是否受过访问(是否年轻)。如果页面不年轻,就再查看page->age的值,当page->age尚未达到0,就不能换出页面,要转到out_failed。
 然后就进行加锁,(下面对page的操作要互斥),通过ptep_get_and_clear()再读按一次页面表项的内容(防止进程在另一个CPU上运行,并更改了内容)并将其置为0,暂时撤销该页面的映射。
   1)如果页面的page数据结构已经在为页面换入/换出而设置的队列中,然后查看page的标志位是否已经受过写访问,是否要将其转入“脏”队列,接着swap_duplicate()对内容做一些检索,并递增相应盘上的共享计数。再调用set_pte()把指向内存的索引变成指向盘上的索引。res减1,把活跃状态变成不活跃状态,将页面的page结构从活跃的页面队列转到不活跃的页面队列。
  2)如果页面的page数据结构不在swapper_space队列中,(尚未建立相应盘上页面(页面表项为0))此时先将其映射到空白页面,以后需要写的时候才为之分配一个页面。如果页面来自通过mmap()建立起的文件映射,则在需要时可以根据虚拟地址计算出页面在文件中的位置。(相比之下,交换设备上的页面位置不能通过计算得到,所以要把页面的去向存储在页面表项中)。如果所考察的是mmap()建立起的文件映射,将page结构的PG_dirty标志位置为1,并将页面转移到该文件的“脏”页面队列中。
、、、、、为脏页面分配一个盘上页面,将内容写到盘上,然后通过add_to_swap_cache()将页面链入swapper_space的队列中,以及活跃队列中,在通过set_page_dirty()将页面转入不活跃“脏”页面队列中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: