首页 >> 大全

内存换出和缓冲区释放

2023-09-19 大全 33 作者:考证青年

与.11将普通内存和缓冲区分成不同的物理内存来处理不同,.4.0将这两者抽象成统一的内存管理接口。而在阅读.4.0源码的过程中,发现内存的换出和缓冲区的释放在结构上似乎具有很强的相似性,具体体现在以下几点:

内存换出时,要考虑到内存是不是最近有被使用(LRU Last Used)。而缓冲区也不会在进程释放完就马上释放,会停留在内存一段时间(因为从硬盘读数据到内存会影响到OS的效率),也需要通过LRU去将缓冲区释放内存换出是将暂时不用的内存写入到交换盘,待以后需要用到时通过换入到内存中;而缓冲区也需要将进程写入到内存的数据存储到硬盘中

1.Page 的LRU管理

关于page的LRU管理,有三个链表: 、、(与前两者全局的链表不同,这个在zone的管理结构中,因为这个已经向硬盘写入,但是没有回收而已)。

Page一般在使用的时候是存在于;如果普通内存或者缓冲区因为长时间没使用(识别长时间没使用的方法--page老化),会将page从移至;在内存缺少的时候,当前进程或者一些周期性线程(例如)将page洗净(),将page从移至;最后线程会将page从中移除(之前能从hash表中查到,之后就再也查不到page的数据了),释放至buddy系统中,供其他的进程内存分配使用。

2.Page的老化

Page的老化是LRU算法的基础,在.4.0中,函数会查看page是否有受到访问,如果受到了访问,;如果没有受到,就y。直到page.age为0时,会ck,将page从移至。

refill_inactive-->refill_inactive_scan
refill_inactive_scan
{
....
//缓冲区的递增在这里做,普通内存的递增在swap_out(2.4.16将两者都抽象到Referenced)
//但是递减,普通内存和缓冲区都在这做if (PageTestandClearReferenced(page)) {age_page_up_nolock(page);page_active = 1;} else {age_page_down_ageonly(page);
....
}

2.1普通内存的老化

查看普通内存是否受到访问,需要借助硬件机制。在MMU中,将page映射到虚拟内存,每次通过虚拟地址访问,都会通过pgd-->pmd-->pte去访问page。在MMU访问这些pgd/pmd/pte时,都会将置上,再通过young来检查page是否受到访问。

会通过链表..next来遍历所有进程的结构体中的所有vma,从而确保遍历进程申请的所有虚拟地址,来检查page是否受到了访问,如下图。

(注:这样遍历似乎在效率上存在很大的问题,例如vma申请了,但是没有映射内存;还比如说page被映射到了MMU中,但是因为效率的原因不允许被换出。出现了这些情况,.4.0似乎还是一如既往的遍历了所有的vma。所以后期似乎对这一块有所优化,将page映射的pte通过红黑树放在page的管理结构中,这样就可以通过page去查看pte是否被访问,或者将pte对应的page移至其他内存上,从而减少内存碎片,具体参考深入剖析Linux内核反向映射机制 to the World-CSDN博客)

refill_inactive-->swap_out-->swap_out_mm-->swap_out_vma-->swap_out_pgd-->swap_out_pmd-->try_to_swap_out
try_to_swap_out
{...	onlist = PageActive(page);/* Don't look at this pte if it's been accessed recently. */
//普通内存受到访问age递增if (ptep_test_and_clear_young(page_table)) {age_page_up(page);goto out_failed;}
//如果之前不再active_list中,refill_inactive_scan就不会将age递减。所以需要在这里递减
//据《linux内核源代码情景分析》第125页说:在do_swap_page的时候,page不会立即添加到active_list,而是在page_launder时在做,从看代码可知:如果是从hash中获取page,则会在active_list中,但是如果要从盘上读取,需要经过add_to_swap_cache,这里有可能将其添加到inactive_dirty_list。if (!onlist)/* The page is still mapped, so it can't be freeable... */age_page_down_ageonly(page);...}

2.2缓冲区的老化

缓冲区的老化,从目前看的代码主要有三种情形:

1.通过/ 初次读写时

do_generic_file_read-->__add_to_page_cache-->lru_cache_add
generic_file_write-->__grab_cache_page-->add_to_page_cache_unique-->__add_to_page_cache-->lru_cache_addlru_cache_add
{
...add_page_to_active_list(page);
//这里作者说age为0的情况很少产生,估计是因为内存是脏的/* This should be relatively rare */if (!page->age)deactivate_page_nolock(page);
...
}

2.通过/ 再次读写时

do_generic_file_read-->__find_page_nolock-->age_page_up
generic_file_write-->__grab_cache_page-->__find_lock_page-->__find_page_nolock-->age_page_upage_page_up
{if (!page->age)activate_page(page);/* The actual page aging bit */page->age += PAGE_AGE_ADV;if (page->age > PAGE_AGE_MAX)page->age = PAGE_AGE_MAX;
}

3.通过block号读写缓冲区时:

getblk-->touch_buffer//设置标志位,然后在refill_inactive_scan中递增age
#define touch_buffer(bh)	SetPageReferenced(bh->b_page)

前两种方式主要是VFS在用,第三种方式主要是具体的文件系统(例如 ext2)用。

3.-->

当age在中减到0之后,page就彻底老化,需要将其和硬盘关联。所以linux对下面变量做了抽象:

page.index:page在盘(或者inode)内的页面偏移

:page到盘(或者inode)的映射,在这个结构体中记录了页面是否clean/dirty/,并且有将页面写入/读出的方法。另外;对于普通内存来说是,对于ext2缓冲区是inode.(实体是inode.,操作方法是)。

3.1普通内存

普通内存在(或者)中将page.age递减置0,会在将pte和page的映射断开,之后如果进程需要通过MMU访问内存,需要通过,然后在hash表中查找重新建立映射了。

refill_inactive-->swap_out-->swap_out_mm-->swap_out_vma-->swap_out_pgd-->swap_out_pmd-->try_to_swap_out
try_to_swap_out
{
...
//这里pte被写0,所有后面!pte_dirty(pte)跳到drop_pte时是0pte = ptep_get_and_clear(page_table);flush_tlb_page(vma, address);//初次进来,这个标志没置if (PageSwapCache(page)) {entry.val = page->index;if (pte_dirty(pte))set_page_dirty(page);
set_swap_pte:swap_duplicate(entry);set_pte(page_table, swp_entry_to_pte(entry));
drop_pte:UnlockPage(page);mm->rss--;//将page放入到inactive_ditry_listdeactivate_page(page);page_cache_release(page);
out_failed:return 0;}...
//虚拟内存和物理内存映射一般是在do_page_fault中建立,出现写时复制或者swap时,如果vma有write_access,会pte_mkdirty。
//有两种情况没dirty:1.这个pte没被写过,被映射到零页,直接释放pte,在do_page_fault时在重新申请0页2.如果这个页面是通过mmap,可以通过do_page_fault从硬盘中读取(另外pipe不是这种情况,因为pipe是通过file--inode的机制去访问page而不是MMU)if (!pte_dirty(pte))goto drop_pte;/** Ok, it's really dirty. That means that* we should either create a new swap cache* entry for it, or we should write it back* to its own backing store.*/
//通过mmap建立映射,set_page_dirty将page加入到address_space的脏队列中,之后通过同步写入因硬盘if (page->mapping) {set_page_dirty(page);goto drop_pte;}/** This is a dirty, swappable page.  First of all,* get a suitable swap entry for it, and make sure* we have the swap cache set up to associate the* page with that swap entry.*/
//这里是MMU首次老化的内存entry = get_swap_page();if (!entry.val)goto out_unlock_restore; /* No swap space left *//* Add it to the swap cache and mark it dirty */
//将page和swap_space映射,将其加入其脏队列中,并添加置address_space和page.index组成的hash表中。
//然后将pte设置成交换盘的索引,以便do_page_fault时能从hash表或者硬盘中读入add_to_swap_cache(page, entry);set_page_dirty(page);goto set_swap_pte;...
}

_缓冲区内存怎么清理_缓冲一下内存

将如下的结构写入到pte中,因为位是0,所以之后再访问该page的时候,会产生页面中断,执行,然后去hash表或者从交换盘中读取page到内存中。

(注:swap具体函数清参考和。这里记录一些变量含义:

type表示交换盘的编号;

表示page在交换盘中的偏移;

[type].[]表示盘上页面的计数;

.head指向的是按照prior排列的链表,

[type].next指向这个链表的下一个,

.next指向下一个分配时应该选择的。)

3.2缓冲区

缓冲区在中将page.age递减置0的时候,会立即将其放入

refill_inactive-->refill_inactive_scan
refill_inactive_scan
{if (PageTestandClearReferenced(page)) {age_page_up_nolock(page);page_active = 1;} else {age_page_down_ageonly(page);//在alloc_page中的rmqueue中,从buddy系统中获取page时,已经将page.count置1,如果只有单个进程MMU的引用或者只有buffer_head的引用,page.count为2;如果是单个进程mmap,那么这个page既被映射到MMU中,又被加载到bh中,所以page.count为3.
//这里只是单独处理缓冲区的部分,处理MMU引用在try_to_swap_out。所以如果存在page->buffers,说明其被bh引用,page.count最大是2,才能释放到inactive_dirty_list。if (page->age == 0 && page_count(page) <=(page->buffers ? 2 : 1)) {//将page加入到inactive_dirty_list中deactivate_page_nolock(page);page_active = 0;} else {page_active = 1;}
....
}

4.-->

-->,主要涉及到函数,以及其调用的两个函数和,其主要是用来识别哪些page是进程长期不使用的,可以将这些page腾出来,在内存短缺的时候供其他进程使用。

所以当内存短缺的时候,需要执行,将中不干净的页面洗净(同步到盘上),将其转入,待进一步的回收。

4.1普通内存

普通内存在因为已经,所以在会检查page这个标志位是否置上,如果置上再将其写入到盘上

page_launder
{
...
//遍历整个inactive_dirty_listlist,注意这里如果不移到其他管理链表中,还在inactive_dirty_list中时,会将其插入到最后
maxscan = nr_inactive_dirty_pages;
while ((page_lru = inactive_dirty_list.prev) != &inactive_dirty_list &&maxscan-- > 0) {
page = list_entry(page_lru, struct page, lru);
...
if (PageDirty(page)) {int (*writepage)(struct page *) = page->mapping->a_ops->writepage;int result;if (!writepage)goto page_active;//在第二次进来的时候再向盘上写/* First time through? Move it to the back of the list */if (!launder_loop) {list_del(page_lru);list_add(page_lru, &inactive_dirty_list);UnlockPage(page);continue;}/* OK, do a physical asynchronous write to swap.  */ClearPageDirty(page);page_cache_get(page);spin_unlock(&pagemap_lru_lock);
//page->mapping->a_ops->writepage,如果是交换盘是swap_writepage,如果是mmap,则是ext2_writepageresult = writepage(page);page_cache_release(page);/* And re-start the thing.. */spin_lock(&pagemap_lru_lock);
//写完,在这里退出此次page的操作if (result != 1)continue;/* writepage refused to do anything */set_page_dirty(page);goto page_active;
}
...
else if (page->mapping && !PageDirty(page)) {/** If a page had an extra reference in* deactivate_page(), we will find it here.* Now the page is really freeable, so we* move it to the inactive_clean list.*/
//从inactive_dirty_list移到inactive_clean_listdel_page_from_inactive_dirty_list(page);add_page_to_inactive_clean_list(page);UnlockPage(page);cleaned_pages++;
} 
...
}

swap_writepage-->rw_swap_page-->rw_swap_page_base
rw_swap_page_base
{...
//从swap_info中获取要写的设备(dev)或者inode(swapf)get_swaphandle_info(entry, &offset, &dev, &swapf);//获取底层的参数dev block(物理)if (dev) {zones[0] = offset;zones_used = 1;block_size = PAGE_SIZE;} else if (swapf) {int i, j;unsigned int block = offset<< (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits);block_size = swapf->i_sb->s_blocksize;for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size)if (!(zones[i] = bmap(swapf,block++))) {printk("rw_swap_page: bad swap file\n");return 0;}zones_used = i;dev = swapf->i_dev;} ...
//在其中提交申请submit_bhbrw_page(rw, page, dev, zones, block_size);..
}

4.2高速缓存

高速缓存不像普通内存换出那样在页面短缺的时候才同步,会周期性的(或者在发现dirty的bh过多时)通过去,以防止脏缓存的集中性写入。

balance_dirty-->wakeup_bdflush-->flush_dirty_buffers
bdflush-->flush_dirty_buffers
flush_dirty_buffers
{
...bh = lru_list[BUF_DIRTY];
...for (i = nr_buffers_type[BUF_DIRTY]; i-- > 0; bh = next) {next = bh->b_next_free;...
//提交申请submit_bh,写缓存ll_rw_block(WRITE, 1, &bh);...}
...
}

另外因为内存短缺而执行,会将这些干净页面放入到中,如果遇到页面短缺程度比较大,也会在中将一些老化但是没同步的内存同步到盘上,然后释放至。

page_launder
{
...
//遍历整个inactive_dirty_listlist,注意这里如果不移到其他管理链表中,还在inactive_dirty_list中时,会将其插入到最后
maxscan = nr_inactive_dirty_pages;
while ((page_lru = inactive_dirty_list.prev) != &inactive_dirty_list &&maxscan-- > 0) {
page = list_entry(page_lru, struct page, lru);
...
if (page->buffers) {
...
//从inactive_dirty_list移除
del_page_from_inactive_dirty_list(page);
...//释放bh,如果短缺(wait)严重会同步缓存
/* Try to free the page buffers. */
clearedbuf = try_to_free_buffers(page, wait);
...if (!clearedbuf) {...else/* page->mapping && page_count(page) == 2 */ {//添加至inactive_clean_listadd_page_to_inactive_clean_list(page);cleaned_pages++;}
...
}
}

try_to_free_buffers
{
...do {struct buffer_head *p = tmp;tmp = tmp->b_this_page;
//检查是否写到盘上if (buffer_busy(p))goto busy_buffer_page;} while (tmp != bh);
...//释放page对应的bh至unused_list,如果有足够的bh,则将其释放至slabif (p->b_dev != B_FREE) {remove_inode_queue(p);__remove_from_queues(p);} else__remove_from_free_list(p, index);__put_unused_buffer_head(p);//将bh对page的引用计数释放page->buffers = NULL;page_cache_release(page);
...
busy_buffer_page:if (wait) {//向盘上同步sync_page_buffers(bh, wait);/* We waited synchronously, so we can free the buffers. */if (wait > 1 && !loop) {loop = 1;goto cleaned_buffers_try_again;}}return 0;
}

注:bh和page是两套系统:

1.bh在使用时,存放在中

2.主要是用于一些磁盘的管理结构,例如super,inode.等,这些结构都是内核使用的,不需要通过page去访问。因为这个page所对应的多个block可能是不同设备中的不连续的block。

3.相比于,更偏重于进程的访问(/),一个page和inode中连续的block是相对应的,page.index指的是page在inode中的逻辑偏移。但是在的bh不足时,会向申请bh。

4.在内核释放一些不需要的bh时,是通过释放,存放在中;

如果内核在执行过程中出问题,需要将之前依赖的一些bh释放时,会使用接口,将其释放至,但是还能通过hash(dev,block)找到bh;

但是可能因为一些特殊情况(例如改变文件系统的逻辑块大小),会将彻底释放的bh放到中(因为已经改变,要重新将其分配)。因为bh已无效,hash(dev,block)是断开的;

如果要将bh释放给slab,需要通过head(一般是进程出错自己释放,或者由释放page的时候一起释放)

5.总结

是否在hash中

是否指向实际内存(page)

有内存

在(小概率不在)

有内存

不在

没内存

5.-->buddy系统

这主要是将page彻底回收(与断开),然后通过释放给buddy系统。

kreclaimd
{...//将page从address_space断开,并且将hash移除page = reclaim_page(zone);...//释放给buddy__free_page(page);...}reclaim_page
{...maxscan = zone->inactive_clean_pages;while ((page_lru = zone->inactive_clean_list.prev) !=&zone->inactive_clean_list && maxscan--) {page = list_entry(page_lru, struct page, lru);...//这两个分别从swapper_space,和inode.i_mapping中移除,解除hash。/* OK, remove the page from the caches. */if (PageSwapCache(page)) {__delete_from_swap_cache(page);goto found_page;}if (page->mapping) {__remove_inode_page(page);goto found_page;}...}...found_page://从inactive_clean_list中移除del_page_from_inactive_clean_list(page);...
}

6.总结

普通内存换出:

1.在函数中分别通过检查是否受到访问,并在将其老化。如果长时间没受到访问,会在后面将其和pte解除映射,存放至。

2.在内存短缺的时候,会通过将内存写到盘上,并添加至。

3.如果该zone的较少,会通过将其释放给buddy

注:当page释放给时,因为已经和pte断开,所以在此访问时只能通过中的将page换入。如果此时page还没释放给buddy,可以在hash中查找;如果已经完全释放,则需要通过(最后调用读)将page从盘上读进来

缓冲区的释放:

1.进程会通过/(内核会通过)增加page.age,并且在中的将其老化;如果彻底老化,后续会将其移至

2.平时会有(或者在发现dirty的bh过多时)去,将缓冲区同步,当内存短缺时,会通过将其移至(如果特别短缺,也会在中做脏缓存的同步)

3.如果该zone的较少,会通过将其释放给buddy,之后要读只能再次从盘上读取。

本文参考《Linux内核源代码情景分析》

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了