歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux 內核源代碼情景分析 chap 2 存儲管理 (四)

物理頁面的使用和周轉

1. 幾個術語

1.1 虛存頁面

指虛擬地址空間中一個固定大小, 邊界與頁面大小 4KB 對齊的區間及其內容

1.2 物理頁面

與虛存頁面相對的, 需要映射到某種物理存儲介質上面的頁面。 根據他是否在內存中, 我們可以分為 內存頁面 和 盤上頁面。
另外, 通常說物理內存頁面的分配和釋放是指 物理介質, 而談及頁面的換入和換出的時候, 是指他的內容。

1.3 交換技術

當系統內存不夠用的時候,我們可以把暫時不用的信息 放到磁盤上, 為其他急用的信息騰出空間, 到需要的時候, 再從磁盤上讀進來。
(linux 中 主要使用swap 分區, windows 中使用虛擬內存技術)
早期是基於段式交換的, 可是效率太低, 於是發展成按需頁面交換技術。這是一種典型的用時間換空間的做法。

2. 對物理頁面的抽象描述

2.1 內存物理頁面

在系統的初始化階段, 內核會根據檢測到的物理內存的大小, 為每一個頁面都建立一個page結構, 形成一個page數組, 並使用一個全局量 mem_map 指向這個數組。(不過個人感覺, 這是對於UMA 均勻介質而言的, 對於NUMA page 數組應該是從屬於 某個node 的
這裡寫圖片描述
同時, 又按照需要將這些頁面拼合成物理地址連續的許多內存頁面塊, 然後根據塊的大小建立起若干管理區 zone, 而在每個管理區中則設置了一個空閒隊列, 以便物理內存頁面的分配使用

2.2 交換設備物理頁面

2.2.1 swap_info_struct

內核中定義了一個swap_info_struct 數據結構, 用來描述和管理用於頁面交換的文件和設備。

==================== include/linux/swap.h 49 64 ====================
49  struct swap_info_struct {
50      unsigned int flags;
51      kdev_t swap_device;
52      spinlock_t sdev_lock;
53      struct dentry * swap_file;
54      struct vfsmount *swap_vfsmnt;
55      unsigned short * swap_map;
56      unsigned int lowest_bit;
57      unsigned int highest_bit;
58      unsigned int cluster_next;
59      unsigned int cluster_nr;
60      int prio; /* swap priority */
61      int pages;
62      unsigned long max;
63      int next; /* next entry on swap list */
64  };

其中, swap_map 指向一個數組, 數組中的每個值代表了盤上的一個物理頁面, 數組下標決定了頁面在盤或者文件中的位置。數組大小與pages 相關。
感覺這個swap_map 和 我們的 mem_map 指針指向一個page 數組的效果非常相似=_=!! <~~ ~.~

特別需要注意的是, 設備上的第一個頁面, ie, swap_map[0]所代表的頁面時不用於做頁面交換的, 他包含了該設備或者文件自身的一些信息, 以及表明哪些頁面是可以使用的位圖

我們利用 lowest_bit 和 highest_bit 字段,標記文件從什麼地方開始到什麼地方結束。利用 max 字段, 標記設備的物理大小。

由於, 我們的磁盤通常都是轉動的, 所以在分配盤面空間的時候, 盡可能按照集群cluster 的方式進行, cluster_next 和 cluster_nr 就是為這個來設計的。

由於 linux 允許使用多個頁面交換設備(文件), 所以在內核中定義了一個 swap_info_struct 數組

    struct swap_info_struct swap_info[MAX_SWAPFILES];

同時, 內核還建立了一個隊列 swap_list, 將各個可以分配物理頁面的磁盤設備或者文件的 swap_info_struct 結構按優先級高低連接在一起。

==================== mm/swapfile.c 23 23 ====================
23  struct swap_list_t swap_list = {-1, -1};

==================== include/linux/swap.h 153 156 ====================
153  struct swap_list_t {
154 int head; /* head of priority-ordered swapfile list */
155 int next; /* swapfile to be used next */
156  };

2.2.2 swap_entry_t 頁面交換項

類似於 內存中 的pte_t 數據結構, 把物理內存頁面和虛存頁面建立聯系一樣, 盤上頁面也有一個swp_entry_t 數據結構, 實現類似功能。

==================== include/linux/shmem_fs.h 8 18 ====================
8  /*
9  * A swap entry has to fit into a "unsigned long", as
10   * the entry is hidden in the "index" field of the
11   * swapper address space.
12   *
13   * We have to move it here, since not every user of fs.h is including
14   * mm.h, but m.h is including fs.h via sched .h :-/
15   */
16  typedef struct {
17      unsigned long val;
18  } swp_entry_t;

這裡寫圖片描述

在這裡, offset 表示頁面在 某個磁盤設備或者文件中的位置, ie, 文件中的邏輯頁面號。 直白點講, 他對應著swap_map 所指向的數組中的下標。
而 type 則是指 該頁面在哪個文件中, 是個序號。 直白點來講, 對應的是swap_info, 這個表征多個頁面交換設備的數組中的下標

另外, swp_entry_t 結構和 pte_t 結構關系非常密切。他們有著相同大小的數據結構。
當一個頁面在內存中的時候, 最低位 P 為 1, 其余各位描述該物理內存頁面的地址和頁面屬性。
而當這個頁面在磁盤上的時候, 最低位P 為 0, 其余位表示這個頁面的去向

3. 磁盤周轉

3.1 物理空間管理 __swap_free

==================== mm/swapfile.c 141 182 ====================
141  /*
142   * Caller has made sure that the swapdevice corresponding to entry
143   * is still around or has not been recycled.
144   */
145  void __swap_free(swp_entry_t entry, unsigned short count)
146  {
147     struct swap_info_struct * p;
148     unsigned long offset, type;
149
150     if (!entry.val)
151         goto out;
152
153     type = SWP_TYPE(entry);
154     if (type >= nr_swapfiles)
155         goto bad_nofile;
156     p = & swap_info[type];
157     if (!(p->flags & SWP_USED))
158         goto bad_device;
159     offset = SWP_OFFSET(entry);
160     if (offset >= p->max)
161         goto bad_offset;
162     if (!p->swap_map[offset])
163         goto bad_free;
164     swap_list_lock();
165     if (p->prio > swap_info[swap_list.next].prio)
166         swap_list.next = type;
167     swap_device_lock(p);
168     if (p->swap_map[offset] < SWAP_MAP_MAX) {
169         if (p->swap_map[offset] < count)
170             goto bad_count;
171         if (!(p->swap_map[offset] -= count)) {
172             if (offset < p->lowest_bit)
173                 p->lowest_bit = offset;
174             if (offset > p->highest_bit)
175                 p->highest_bit = offset;
176             nr_swap_pages++;
177         }
178     }
179     swap_device_unlock(p);
180     swap_list_unlock();
181  out:
182     return;

需要注意的是, 釋放磁盤頁面內容的操作, 實際上並不涉及磁盤操作, 只是內存中的 “賬面操作”, 表示磁盤上那個頁面的內容已經作廢了。 因而, 花費是非常小的。

3.2 內存頁面周轉的含義

含義有兩方面:
1. 頁面分配,使用和回收, 並不一定涉及頁面的盤區交換
2. 盤區交換, 最終目的是為了頁面的回收。

對於用戶空間中的頁面, 及涉及分配, 使用和回收, 還涉及頁面的換入和換出, 即使是進程的代碼段, 從系統角度看待, 都是動態分配的。

對於映射到系統空間的頁面都不會被換出。只會有用完了之後, 需要釋放的問題, 有些頁面獲取比較費勁, 可能還會采用 LRU 隊列。

3.2.1 頁面交換策略

最簡單的策略就是即用即分配, 但是可想而知效率很低 使用LRU, ie, 最近最少用到的頁面交換策略, 但是可能會引起 頁面 抖動。 為了減少抖動, 引入暫存隊列 加入頁面 髒, 干淨 等狀態, 進一步優化

3.2.2 物理內存頁面換入換出的周轉要點

空閒, 此時page 在 某個zone 管理區的free_area 隊列中。 頁面引用計數為 0. 分配, 分配頁面, 引用計數 為 1, page 不在處於 free_area隊列中。 活躍狀態, 通過 lru 結構連入 active_list, 遞增引用計數 不活躍狀態(髒), 利用lru 連入 inactive_dirty_list, 遞減引用計數 將不活躍髒內容寫入交換設備, 並將其移動到 inactive_clean_list 中 不活躍狀態(干淨) 如果在轉入不活躍狀態後一段時間內收到訪問, 轉入活躍狀態, 恢復映射 如果需要, 可以從干淨隊列中回收頁面, 或者回到空閒隊列, 或者另行分配。

用我自己的語言來解釋一下:
我們先分配了一個頁面, 然後這個頁面處於 活動狀態 active, 然後, 我們暫時不去訪問它了, 他就開始老化, 進入inactive 不活動(髒)狀態, 但這時候, 我們不是立即寫入交換設備, 等再過一段時間, 確實沒人訪問, 我們將它寫入交換設備, 但是這部分頁面, 我們還是沒有釋放哦, 他被標記為 inactive 不活動(干淨) 狀態, 現在是由相應的存儲區 zone 來管理了, 之前是由全局隊列管理的。如果在這個頁面被用作其他用途之前, 又被訪問了, 直接建立映射就好了, 通過這種方法, 減少了頁面的抖動現象

3.2.3 策略實現

全局LRU 隊列, active_list 和 inactive_dirty_list 每個頁面管理區設置 inactive_clean_list 全局 address_space 數據結構 swapper_space 為加快搜索, 引入 page_hash_table

下面來看下, 內核中交換的代碼

3.2.3.1 code

==================== mm/swap_state.c 54 70 ====================
54  void add_to_swap_cache(struct page *page, swp_entry_t entry)
55  {
56      unsigned long flags;
57
58  #ifdef SWAP_CACHE_INFO
59      swap_cache_add_total++;
60  #endif
61      if (!PageLocked(page))
62          BUG();
63      if (PageTestandSetSwapCache(page))
64          BUG();
65      if (page->mapping)
66          BUG();
67      flags = page->flags & ~((1 << PG_error) | (1 << PG_arch_1));
68      page->flags = flags | (1 << PG_uptodate);
69      add_to_page_cache_locked(page, &swapper_space, entry.val);
70  }

==================== mm/filemap.c 476 494 ====================
476  /*
477   * Add a page to the inode page cache.
478   *
479   * The caller must have locked the page and
480   * set all the page flags correctly..
481   */
482  void add_to_page_cache_locked(struct page * page, struct address_space *mapping, unsigned long index)
483  {
484     if (!PageLocked(page))
485         BUG();
486
487     page_cache_get(page);
488     spin_lock(&pagecache_lock);
489     page->index = index;
490     add_page_to_inode_queue(mapping, page);
491     add_page_to_hash_queue(page, page_hash(mapping, index));
492     lru_cache_add(page);
493     spin_unlock(&pagecache_lock);
494  }

==================== include/linux/fs.h 365 375 ====================
365  struct address_space {
366     struct list_head  clean_pages;  /* list of clean pages */
367     struct list_head  dirty_pages;  /* list of dirty pages */
368     struct list_head  locked_pages; /* list of locked pages */
369     unsigned long nrpages;  /* number of total pages */
370     struct address_space_operations *a_ops;  /* methods */
371     struct inode *host; /* owner: inode, block_device */
372     struct vm_area_struct  *i_mmap;  /* list of private mappings */
373     struct vm_area_struct  *i_mmap_shared; /* list of shared mappings */
374     spinlock_t i_shared_lock;  /* and spinlock protecting it */
375  };

==================== mm/swap_state.c 31 37 ====================
31  struct address_space swapper_space = {
32      LIST_HEAD_INIT(swapper_space.clean_pages),
33      LIST_HEAD_INIT(swapper_space.dirty_pages),
34      LIST_HEAD_INIT(swapper_space.locked_pages),
35      0, /* nrpages */
36      &swap_aops,
37  };

==================== include/linux/mm.h 150 150 ====================
150  #define get_page(p) atomic_inc(&(p)->count)

==================== include/linux/pagemap.h 31 31 ====================
31  #define page_cache_get(x)  get_page(x)

==================== mm/filemap.c 72 79 ====================
72  static inline void add_page_to_inode_queue(struct address_space *mapping, struct page * page)
73  {
74      struct list_head *head = &mapping->clean_pages;
75
76      mapping->nrpages++;
77      list_add(&page->list, head);
78      page->mapping = mapping;
79  }

==================== mm/filemap.c 58 70 ====================
58  static void add_page_to_hash_queue(struct page * page, struct page **p)
59  {
60      struct page *next = *p;
61
62      *p = page;
63      page->next_hash = next;
64      page->pprev_hash = p;
65      if (next)
66          next->pprev_hash = &page->next_hash;
67      if (page->buffers)
68          PAGE_BUG(page);
69      atomic_inc(&page_cache_size);
70  }

==================== include/linux/pagemap.h 68 68 ====================
68  #define page_hash(mapping,index) (page_hash_table+_page_hashfn(mapping,index))

==================== mm/swap.c 226 241 ====================
226  /**
227   * lru_cache_add: add a page to the page lists
228   * @page: the page to add
229   */
230  void lru_cache_add(struct page * page)
231  {
232     spin_lock(&pagemap_lru_lock);
233     if (!PageLocked(page))
234         BUG();
235     DEBUG_ADD_PAGE
236     add_page_to_active_list(page);
237     /* This should be relatively rare */
238     if (!page->age)
239         deactivate_page_nolock(page);
240     spin_unlock(&pagemap_lru_lock);
241  }

==================== include/linux/swap.h 209 215 ====================
209  #define add_page_to_active_list(page) { \
210     DEBUG_ADD_PAGE \
211     ZERO_PAGE_BUG \
212     SetPageActive(page); \
213     list_add(&(page)->lru, &active_list); \
214     nr_active_pages++; \
215  }

從add_to_page_cache_locked 函數中, 我們可以知道, 頁面page 被加入到了 3 個隊列中:
1. 利用 list 加入暫存隊列 swapper_space
2. 利用next_hash 和 pprev_hash 加入 hash_queue
3. 利用 lru 加入 LRU 隊列 active_list

3.3 用戶參與內存管理

特權用戶可以通過 swapon, swapoff 參與存儲管理等。

Copyright © Linux教程網 All Rights Reserved