系列文章目录
Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)
深入理解Linux虚拟内存管理(五)
深入理解Linux虚拟内存管理(六)
深入理解Linux虚拟内存管理(七)
深入理解Linux虚拟内存管理(八)
深入理解Linux虚拟内存管理(九)
文章目录
系列文章目录一、高端内存管理1、映射高端内存页面(1)kmap(2)kmap_nonblock(3)__kmap(4)kmap_high(5)map_new_virtual(6)flush_all_zero_pkmaps 2、自动映射高端内存页面(1)kmap_atomic 3、解除页面映射(1)kunmap(2)kunmap_high 4、自动解除高端内存页面映射(1)kunmap_atomic 5、弹性缓冲区(1)创建弹性缓冲区① create_bounce⑴ ⇒ alloc_bounce_bh⑵ ⇒ alloc_bounce_page⑶ ⇔ set_bh_page⑷ ⇒ copy_from_high_bh⑸ ⇒ bounce_end_io_write⑹ ⇒ bounce_end_io_read ② alloc_bounce_bh③ alloc_bounce_page (2)用弹性缓冲区复制① bounce_end_io_write⑴ ⇒ bounce_end_io ② bounce_end_io_read⑴ ⇒ copy_to_high_bh_irq⑵ ⇒ bounce_end_io ③ copy_from_high_bh⑴ ⇒ kmap_atomic⑵ ⇒ kunmap_atomic ④ copy_to_high_bh_irq⑴ ⇒ kmap_atomic⑵ ⇒ kunmap_atomic ⑤ bounce_end_io 6、 紧急池(1)init_emergency_pool 二、页面帧回收1、页面高速缓存操作2、LRU 链表操作3、重填充 inactive_list4、 从 LRU 链表回收页面5、收缩所有高速缓存(1)shrink_caches(2)try_to_free_pages(3)try_to_free_pages_zone 6、换出进程页面7、页面交换守护程序符号一、高端内存管理
1、映射高端内存页面
9.1 管理 PKMap 地址空间
(1)kmap
这个API用于进行阻塞的调用者。
// include/asm-i386/highmem.h// 核心函数 __kmap 的第2个参数表示调用者将进行阻塞。#define kmap(page) __kmap(page, 0)
(2)kmap_nonblock
// include/asm-i386/highmem.h// 核心函数 __kmap 的第2个参数表示调用者将不进行阻塞。#define kmap_nonblock(page) __kmap(page, 1)
(3)__kmap
// include/asm-i386/highmem.hstatic inline void *__kmap(struct page *page, int nonblocking){// 这个函数不会在中断时调用,因为它已睡眠。out_of_line_bug()调用do_exit并// 返回错误码来取代BUG()。之所以不调用BUG()是因为BUG()用极端方式杀掉进程,这将// 引起如寓言 “Aiee,杀掉中断句柄! ” 般的内核瘫痪。if (in_interrupt())out_of_line_bug();// 如果页面已在低端内存中,则返回一个直接映射。if (page < highmem_start_page)return page_address(page);// 调用kmap_high()(见L1. 4小节)完成与体系结构无关的工作。return kmap_high(page, nonblocking);}
(4)kmap_high
// mm/highmem.cvoid *kmap_high(struct page *page, int nonblocking){unsigned long vaddr;/** For highmem pages, we can't trust "virtual" until* after we have the lock.** We cannot call this from interrupts, as it may block*/// kmap_lock 保护页面的 virtual 字段和 pkmap_count 数组。spin_lock(&kmap_lock);// 获取页面的虚拟地址。vaddr = (unsigned long) page->virtual;// 如果尚未映射,则调用map_new_virtual(),进行页面映射并返回其虚拟地址。// 如果失败,则跳转到 out 处,释放自旋锁并返回 NULL。if (!vaddr) {vaddr = map_new_virtual(page, nonblocking);if (!vaddr)goto out;}// 增加页面映射的引用计数。pkmap_count[PKMAP_NR(vaddr)]++;// 如果计数现在小于2,则这是个严重的bug。在实际运行中,严重的系统问题可能会引起这个bug。if (pkmap_count[PKMAP_NR(vaddr)] < 2)BUG();out:// 释放 kmap_lock。spin_unlock(&kmap_lock);return (void*) vaddr;}
(5)map_new_virtual
这个函数分为3个主要部分:查找一个空闲槽;如果没有可用的槽则在队列上等待;然后映射该页面。
// mm/highmem.cstatic inline unsigned long map_new_virtual(struct page *page, int nonblocking){unsigned long vaddr;int count;start:// 从最后一个可能的槽开始扫描。count = LAST_PKMAP;/* Find an empty entry */// 持续扫描并等待直至有一个槽空闲。这里可能导致某些进程进入死循环。for (;;) {// last_pkmap_nr 是扫描中最后一个pkmap。为阻止搜索同一个页面,记录该值就可以// 进行循环搜索。如果达到LAST_PKMAP,则折返为0。last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;// 在 last_pkmap_nr 折返为 0 时,调用 flush_all_zero_pkmaps() (I. 1, 6 小节),在// 刷新TLB之前设置pkmap_count数组中所有的项从1改为0。接着重新设置LAST_PKMAP// 表示再次开始扫描。if (!last_pkmap_nr) {flush_all_zero_pkmaps();count = LAST_PKMAP;}// 如果元素为0,则表示为页面找到了一个可用槽。if (!pkmap_count[last_pkmap_nr])break;/* Found a usable entry */// 转到下一个下标,开始扫描。if (--count)continue;// 下一块代码将睡眠,等待一个槽变空闲。如果调用者要求不阻塞该函数,则直接返回if (nonblocking)return 0;/** Sleep for somebody else to unmap their entries*/// 如果在扫描完所有页面一遍后仍没有可用槽,则在pkmap_map_wait队列上睡眠直至在// 解除映射后被唤醒。{// 声明该等待队列。DECLARE_WAITQUEUE(wait, current);// 设置队列可中断,因为是在内核空间中睡眠。current->state = TASK_UNINTERRUPTIBLE;// 添加自己到pkmap_map_wait队列。add_wait_queue(&pkmap_map_wait, &wait);// 释放kmap_lock自旋锁。spin_unlock(&kmap_lock);// 调用schedule(),使自己开始睡眠。在解除映射后如果有槽空闲,则自己被唤醒。schedule();// 从等待队列移除自己。remove_wait_queue(&pkmap_map_wait, &wait);// 重新获取kmap锁。spin_lock(&kmap_lock);/* Somebody else might have mapped it while we slept */// 如果在睡眠时有其他的事务映射了该页面,则仅仅返回地址,并由kmap_high()增加引用计数。if (page->virtual)return (unsigned long) page->virtual;/* Re-start */// 重新开始扫描。goto start;}}// 这块代码在找到一个槽时进行处理,用于映射页面。// 获取被找到槽的虚拟地址。vaddr = PKMAP_ADDR(last_pkmap_nr);// 确保PTE与页面对应且已获得必需的保护,将其放置在页表中被找到槽的地方。set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));// 初始化pkmap_count数组的值为1。该计数在父函数中增加,如果这是第一次在该// 函数处出现,可以保证这是第一次映射。pkmap_count[last_pkmap_nr] = 1;// 设置页面的虚拟字段。page->virtual = (void *) vaddr;// 返回虚拟地址。return vaddr;}
其核心就是,如果page是一个高端页面,则把其映射到从PKMAP_BASE到FIXADDR_START之间的虚拟地址。
(6)flush_all_zero_pkmaps
这个函数循环遍历pkmap_count数组并在刷新TLB之前设置所有的项从1变为0。
// mm/highmem.cstatic void flush_all_zero_pkmaps(void){int i;// 由于全局页表将变化,因此必须刷新所有处理器的CPU高速缓存。flush_cache_all();// 循环遍历整个pkmap_count数组。for (i = 0; i < LAST_PKMAP; i++) {struct page *page;/** zero means we don't have anything to do,* >1 means that it is still in use. Only* a count of 1 means that it is free but* needs to be unmapped*/// 如果元素不为1,则移到下一个元素。if (pkmap_count[i] != 1)continue;// 从1设置为0。pkmap_count[i] = 0;/* sanity check */// 确保PTE没有被映射过。if (pte_none(pkmap_page_table[i]))BUG();/** Don't need an atomic fetch-and-clear op here;* no-one has the page mapped, and cannot get at* its virtual address (and hence PTE) without first* getting the kmap_lock (which is held here).* So no dangers, even with speculative execution.*/// 从PTE解除对页面的映射,并清除PTE。page = pte_page(pkmap_page_table[i]);pte_clear(&pkmap_page_table[i]);// 更新虚拟字段为页面未被映射。page->virtual = NULL;}// 刷新TLB。flush_tlb_all();}
2、自动映射高端内存页面
下面是x86中km_type枚举类型的例子。这里列举了自动调用kmap的不同中断。由于KM_TYPE_NR是最后一个元素,所以它用作所有元素的计数器。
// include/asm-i386/kmap_types.henum km_type {KM_BOUNCE_READ,KM_SKB_SUNRPC_DATA,KM_SKB_DATA_SOFTIRQ,KM_USER0,KM_USER1,KM_BH_IRQ,KM_SOFTIRQ0,KM_SOFTIRQ1,KM_TYPE_NR};
(1)kmap_atomic
9.4 原子性的映射高端内存页面
这是kmap()的原子版本。请注意,不论在什么时候都不持有自旋锁或不睡眠。之所以不需要自旋锁是因为每个处理器都有它自己的保留空间。
// include/asm-i386/highmem.h/** The use of kmap_atomic/kunmap_atomic is discouraged - kmap/kunmap* gives a more generic (and caching) interface. But kmap_atomic can* be used in IRQ contexts, so in some (very limited) cases we need* it.*/// 这里的参数为映射的页面和处理所需的类型。一个槽的使用对应一个处理器。static inline void *kmap_atomic(struct page *page, enum km_type type){enum fixed_addresses idx;unsigned long vaddr;// 如果页面在低端内存,则返回直接映射。if (page < highmem_start_page)return page_address(page);// type给出了使用哪个槽。而 KM_TYPE_NR * smp_processor_id() 则给出了为处理// 器保留的槽的集合。idx = type + KM_TYPE_NR*smp_processor_id();// 获取虚拟地址。vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);#if HIGHMEM_DEBUG// 用于调试的代码。在实际运行中,PTE将退出。if (!pte_none(*(kmap_pte-idx)))out_of_line_bug();#endif// 为保留的槽设置PTE。set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));// 为槽刷新TLB。__flush_tlb_one(vaddr);// 返回虚拟地址。return (void*) vaddr;}
3、解除页面映射
9.3 解除页面映射
(1)kunmap
// include/asm-i386/highmem.hstatic inline void kunmap(struct page *page){// kunmap()不能在中断中进行调用,因此自然退出。if (in_interrupt())out_of_line_bug();// 如果页面已在低端内存中,则不需要进行解除映射的操作。if (page < highmem_start_page)return;// 调用与体系结构无关的函数kunmap_high()。kunmap_high(page);}
(2)kunmap_high
这是与体系结构无关的kunmap()操作部分。
// mm/highmem.cvoid kunmap_high(struct page *page){unsigned long vaddr;unsigned long nr;int need_wakeup;// 获取kmap锁, 用来保护虚拟字段和pkmap_count数组。spin_lock(&kmap_lock);// 获取虚拟页面vaddr = (unsigned long) page->virtual;// 如果没有设置虚拟字段,那这是两次解除映射的操作或是对没有映射页面的解// 除映射操作,则调用BUG。if (!vaddr)BUG();// 获取在pkmap_count数组中的下标。nr = PKMAP_NR(vaddr);/** A count must never go down to zero* without a TLB flush!*/// 在缺省情况下,并不需要唤醒进程来调用kmap()。need_wakeup = 0;// 在减小下标后检查其值。switch (--pkmap_count[nr]) {// 如果减为0,那这是个bug,因为需要刷新TLB以使0成为合法项。case 0:BUG();// 如果减小到1(项虽然已释放,但还需要刷新TLB),则检查是否有谁在// pkmap_map_wait_queue队列上睡眠。如果有必要,则在释放自旋锁后唤醒队列。case 1:/** Avoid an unnecessary wake_up() function call.* The common case is pkmap_count[] == 1, but* no waiters.* The tasks queued in the wait-queue are guarded* by both the lock in the wait-queue-head and by* the kmap_lock. As the kmap_lock is held here,* no need for the wait-queue-head's lock. Simply* test if the queue is empty.*/need_wakeup = waitqueue_active(&pkmap_map_wait);}// 释放 kmap_lock。spin_unlock(&kmap_lock);/* do wake-up, if needed, race-free outside of the spin lock */// 如果还有等待者在队列上而槽已被释放,则唤醒它们。if (need_wakeup)wake_up(&pkmap_map_wait);}
4、自动解除高端内存页面映射
(1)kunmap_atomic
整个函数是调试代码。其原因是页面只在此处自动映射,那么在解除映射以前只会在很小的范围且较短的时间内被使用。保留页面在那里是安全的,因为在解除映射之后不会再引用它们,而对同一个槽的另一个映射会简单地替换它。
// include/asm-i386/highmem.hstatic inline void kunmap_atomic(void *kvaddr, enum km_type type){#if HIGHMEM_DEBUG// 获取虚拟地址并确保它和单个页边界对齐。unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id();// 如果提供的地址不在定长区域内,则返回。if (vaddr < FIXADDR_START) // FIXMEreturn;// 如果地址与使用类型及处理器不对应,则声明它。if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx))out_of_line_bug();/** force other mappings to Oops if they'll try to access* this pte without first remap it*/// 现在解除页面映射,如果再次引用它,则会产生oops。pte_clear(kmap_pte-idx);__flush_tlb_one(vaddr);#endif}
5、弹性缓冲区
(1)创建弹性缓冲区
① create_bounce
9.5.2 创建弹性缓冲区
函数调用图如图 9.3 所示。它是创建弹性缓冲区的高层函数。它由两部分组成,即分配必要的资源和从模板复制数据。
// mm/highmem.c// 函数的参数如下所示://rw 如果是写缓冲区,则设置为1。//bh_orig 是模板缓冲区头,是复制数据源。struct buffer_head * create_bounce(int rw, struct buffer_head * bh_orig){struct page *page;struct buffer_head *bh;// 如果模板缓冲区头已在低端内存中,则简单地返回它。if (!PageHighMem(bh_orig->b_page))return bh_orig;// 从slab分配器分别缓冲区头,如果失败则从紧急池分配。bh = alloc_bounce_bh();/** This is wasteful for 1k buffers, but this is a stopgap measure* and we are being ineffective anyway. This approach simplifies* things immensly. On boxes with more than 4GB RAM this should* not be an issue anyway.*/// 从伙伴分配器分配页面,如果失败则从紧急池分配。page = alloc_bounce_page();// 将分配的页面与分配的缓冲区头关联起来。set_bh_page(bh, page, 0);// 这块产生新的缓冲区头。// 逐个复制必要的信息,除了 b_list 字段,因为该缓冲区并不与该链表上的其他部分直// 接连接。bh->b_next = NULL;bh->b_blocknr = bh_orig->b_blocknr;bh->b_size = bh_orig->b_size;bh->b_list = -1;bh->b_dev = bh_orig->b_dev;bh->b_count = bh_orig->b_count;bh->b_rdev = bh_orig->b_rdev;bh->b_state = bh_orig->b_state;#ifdef HIGHMEM_DEBUG// 仅用于调试的信息。bh->b_flushtime = jiffies;bh->b_next_free = NULL;bh->b_prev_free = NULL;/* bh->b_this_page */bh->b_reqnext = NULL;bh->b_pprev = NULL;#endif/* bh->b_page */if (rw == WRITE) {// 如果这个缓冲区将被写入,则用于结束I/O的回调函数是bounce_end_io_write()// (见L 5. 2.1节),它在设备接收所有信息后调用。由于数据在高端内存中,则从// copy_from_high_bh() (见 I. 5. 2. 3)复制 “下来” 。bh->b_end_io = bounce_end_io_write;copy_from_high_bh(bh, bh_orig);} else// 如果等待设备写数据到缓冲区中,则调用bounce_end_io_read() (见I. 5.2.2)// 回调函数。bh->b_end_io = bounce_end_io_read;// 从模板缓冲区头复制剩下的信息。bh->b_private = (void *)bh_orig;bh->b_rsector = bh_orig->b_rsector;#ifdef HIGHMEM_DEBUGmemset(&bh->b_wait, -1, sizeof(bh->b_wait));#endif// 返回新弹性缓冲区return bh;}
⑴ ⇒ alloc_bounce_bh
alloc_bounce_bh 函数
⑵ ⇒ alloc_bounce_page
alloc_bounce_page 函数
⑶ ⇔ set_bh_page
// fs/buffer.cvoid set_bh_page (struct buffer_head *bh, struct page *page, unsigned long offset){if (offset >= PAGE_SIZE)BUG();if (PageHighMem(page)) {bh->b_data = (char *)offset;} else {bh->b_data = page_address(page) + offset;}bh->b_page = page;}EXPORT_SYMBOL(set_bh_page);
⑷ ⇒ copy_from_high_bh
copy_from_high_bh 函数
⑸ ⇒ bounce_end_io_write
bounce_end_io_write 函数
⑹ ⇒ bounce_end_io_read
bounce_end_io_read 函数
② alloc_bounce_bh
这个函数首先尝试从slab分配器分配一个buffer_head,如果失败,则使用紧急池。
// mm/highmem.cstruct buffer_head *alloc_bounce_bh (void){struct list_head *tmp;struct buffer_head *bh;// 尝试从slab分配器分配一个新buffer_head。 请注意,如何产生不使用I/O操作的请// 求以避免递归,包括高级I/O。bh = kmem_cache_alloc(bh_cachep, SLAB_NOHIGHIO);// 如果分配成功,则返回。if (bh)return bh;/** No luck. First, kick the VM so it doesn't idle around while* we are using up our emergency rations.*/// 如果失败,则唤醒bdflush清洗页面。wakeup_bdflush();repeat_alloc:/** Try to allocate from the emergency pool.*/// 由于从slab分配失败,因此从紧急池中分配。// 取得紧急池头链表的尾部。tmp = &emergency_bhs;// 获取保护池的锁。spin_lock_irq(&emergency_lock);// 如果池不空,则从链表取得一个缓冲区并减小nr_emergency_bhs计数器。if (!list_empty(tmp)) {bh = list_entry(tmp->next, struct buffer_head, b_inode_buffers);list_del(tmp->next);nr_emergency_bhs--;}// 释放锁。spin_unlock_irq(&emergency_lock);// 如果分配成功,则返回它。if (bh)return bh;/* we need to wait I/O completion */// 如果失败,则表示内存不足,那么补充池的惟一方法便是完成高端内存I/O操作。就// 这样,请求从tq_disk开始,然后写数据到磁盘,接着释放进程中适当的页面。run_task_queue(&tq_disk);// 出让处理器。yield();// 再次尝试从紧急池分配。goto repeat_alloc;}
③ alloc_bounce_page
这个函数本质上与alloc_bounce_bh()一样。它首先尝试从伙伴分配器分配页面,如果失败则从紧急池中分配。
// mm/highmem.cstruct page *alloc_bounce_page (void){struct list_head *tmp;struct page *page;// 从伙伴分配器进行分配,如果成功则返回页面。page = alloc_page(GFP_NOHIGHIO);if (page)return page;/** No luck. First, kick the VM so it doesn't idle around while* we are using up our emergency rations.*/// 唤醒bdflush清洗页面。wakeup_bdflush();repeat_alloc:/** Try to allocate from the emergency pool.*/// 取得紧急池缓冲区头链表尾部。 tmp = &emergency_pages;// 获取保护池的锁。spin_lock_irq(&emergency_lock);// 如果池不空,则从链表取得页面并减小可用nr_emergency_pages的计数。if (!list_empty(tmp)) {page = list_entry(tmp->next, struct page, list);list_del(tmp->next);nr_emergency_pages--;}// 释放锁。spin_unlock_irq(&emergency_lock);// 如果分配成功,则返回它。if (page)return page;/* we need to wait I/O completion */// 运行I/O任务队列,尝试并补充紧急池。run_task_queue(&tq_disk);// 让出处理器。yield();// 再次尝试从紧急池中分配。goto repeat_alloc;}
(2)用弹性缓冲区复制
① bounce_end_io_write
在弹性缓冲区用于向设备写数据而完成I/O操作时,调用该函数。由于缓冲区用于从高端内存复制数据到设备,除了回收资源没有其他更多的任务待处理。
// mm/highmem.cstatic void bounce_end_io_write (struct buffer_head *bh, int uptodate){bounce_end_io(bh, uptodate);}
⑴ ⇒ bounce_end_io
bounce_end_io 函数
② bounce_end_io_read
在数据从设备读出且待复制到高端内存时,调用该函数。由于在中断中调用,所以要更加谨慎。
// mm/highmem.cstatic void bounce_end_io_read (struct buffer_head *bh, int uptodate){struct buffer_head *bh_orig = (struct buffer_head *)(bh->b_private);// 调用copy_to_high_bh_irq() 从弹性缓冲区复制数据并移至高端内存。if (uptodate)copy_to_high_bh_irq(bh_orig, bh);// 回收资源。bounce_end_io(bh, uptodate);}
⑴ ⇒ copy_to_high_bh_irq
copy_to_high_bh_irq 函数
⑵ ⇒ bounce_end_io
bounce_end_io 函数
③ copy_from_high_bh
这个函数用于从高端内存buffer_head复制数据到弹性缓冲区。
// mm/highmem.c/** Simple bounce buffer support for highmem pages.* This will be moved to the block layer in 2.5.*/static inline void copy_from_high_bh (struct buffer_head *to,struct buffer_head *from){struct page *p_from;char *vfrom;p_from = from->b_page;// 映射高端内存页面到低端内存。该执行路径由IRQ安全锁io_request_lock保护,这// 样可以安全地调用kmap_atomic()(见I. 2.1小节)。vfrom = kmap_atomic(p_from, KM_USER0);// 复制数据。memcpy(to->b_data, vfrom + bh_offset(from), to->b_size);// 解除页面映射。kunmap_atomic(vfrom, KM_USER0);}
⑴ ⇒ kmap_atomic
kmap_atomic 函数
⑵ ⇒ kunmap_atomic
kunmap_atomic 函数
④ copy_to_high_bh_irq
在设备完成写数据到弹性缓冲区后,从中断中调用该函数。这个函数复制数据到高端内存。
// mm/highmem.cstatic inline void copy_to_high_bh_irq (struct buffer_head *to,struct buffer_head *from){struct page *p_to;char *vto;unsigned long flags;p_to = to->b_page;// 保存标志位并禁止中断。__save_flags(flags);__cli();// 映射高端内存页面到低端内存。vto = kmap_atomic(p_to, KM_BOUNCE_READ);// 复制数据。memcpy(vto + bh_offset(to), from->b_data, to->b_size);// 解除页面映射。kunmap_atomic(vto, KM_BOUNCE_READ);// 恢复中断标志位。__restore_flags(flags);}
⑴ ⇒ kmap_atomic
kmap_atomic 函数
⑵ ⇒ kunmap_atomic
kunmap_atomic 函数
⑤ bounce_end_io
这个函数回收由弹性缓冲区使用的资源。如果紧急池耗尽,则添加资源给它。
// mm/highmem.cstatic inline void bounce_end_io (struct buffer_head *bh, int uptodate){struct page *page;struct buffer_head *bh_orig = (struct buffer_head *)(bh->b_private);unsigned long flags;// 为原始 buffer_head 调用I/O完成的回调函数。bh_orig->b_end_io(bh_orig, uptodate);// 获取指向待释放的缓冲区页面的指针。page = bh->b_page;// 获取紧急池的锁。spin_lock_irqsave(&emergency_lock, flags);// 如果页面池已满,则仅返回页面至伙伴分配器进行页面释放if (nr_emergency_pages >= POOL_SIZE)__free_page(page);else {// 否则,添加页面到紧急池。/** We are abusing page->list to manage* the highmem emergency pool:*/list_add(&page->list, &emergency_pages);nr_emergency_pages++;}if (nr_emergency_bhs >= POOL_SIZE) {// 如果 buffer_head 池已满,则仅返回它至slab分配器进行释放#ifdef HIGHMEM_DEBUG/* Don't clobber the constructed slab cache */init_waitqueue_head(&bh->b_wait);#endifkmem_cache_free(bh_cachep, bh);} else {// 否则,添加该 buffer_head 到紧急池。/** Ditto in the bh case, here we abuse b_inode_buffers:*/list_add(&bh->b_inode_buffers, &emergency_bhs);nr_emergency_bhs++;}// 释放锁。spin_unlock_irqrestore(&emergency_lock, flags);}
6、 紧急池
只有一个函数与紧急池相关,那就是其初始化函数。在系统启动时调用它,接着就删除它的代码,因为不再需要它。
(1)init_emergency_pool
这个函数为紧急页面和紧急缓冲区头创建一个池。
// mm/highmem.cstatic __init int init_emergency_pool(void){struct sysinfo i;si_meminfo(&i);si_swapinfo(&i);// 如果没有可用的高端内存,则返回。 if (!i.totalhigh)return 0;// 获取保护紧急池的锁。spin_lock_irq(&emergency_lock);// 从伙伴分配器分配POOL_SIZE个页面并添加它们到链接表中。然后用// nr_emergency_pages 记录紧急池中页面的数量。while (nr_emergency_pages < POOL_SIZE) {struct page * page = alloc_page(GFP_ATOMIC);if (!page) {printk("couldn't refill highmem emergency pages");break;}list_add(&page->list, &emergency_pages);nr_emergency_pages++;}// 从slab分配器分配POOL_SIZE个buffer_head对象,并添加它们到由b_inode_buffers链接// 的链表中。而且以nr_emergency_bhs记录池中缓冲区头的数量。while (nr_emergency_bhs < POOL_SIZE) {struct buffer_head * bh = kmem_cache_alloc(bh_cachep, SLAB_ATOMIC);if (!bh) {printk("couldn't refill highmem emergency bhs");break;}list_add(&bh->b_inode_buffers, &emergency_bhs);nr_emergency_bhs++;}// 释放用于保护池的锁。spin_unlock_irq(&emergency_lock);printk("allocated %d pages and %d bhs reserved for the highmem bounces\n",nr_emergency_pages, nr_emergency_bhs);// 返回成功。return 0;}
二、页面帧回收
1、页面高速缓存操作
2、LRU 链表操作
3、重填充 inactive_list
4、 从 LRU 链表回收页面
5、收缩所有高速缓存
(1)shrink_caches
(2)try_to_free_pages
(3)try_to_free_pages_zone
// mm/vmscan.cint try_to_free_pages_zone(zone_t *classzone, unsigned int gfp_mask){int priority = DEF_PRIORITY;int nr_pages = SWAP_CLUSTER_MAX;gfp_mask = pf_gfp_mask(gfp_mask);do {nr_pages = shrink_caches(classzone, priority, gfp_mask, nr_pages);if (nr_pages <= 0)return 1;} while (--priority);/** Hmm.. Cache shrink failed - time to kill something?* Mhwahahhaha! This is the part I really like. Giggle.*/out_of_memory();return 0;}
6、换出进程页面
7、页面交换守护程序
符号
⇐ ⇒ ⇔ ⇆ ⇒ ⟺
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿
⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑿⒀⒁⒂⒃⒄⒅⒆⒇
➊➋➌➍➎➏➐➑➒➓⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴
⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵
ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ
ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ
🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩
123