1. 程式人生 > >Linux 內核源碼情景分析 chap 2 存儲管理 (四)

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

void and lis turn tin fsm img 自身 swapon

物理頁面的使用和周轉

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 頁面交換策略

  1. 最簡單的策略就是即用即分配, 可是可想而知效率很低
  2. 使用LRU。 ie。 近期最少用到的頁面交換策略, 可是可能會引起 頁面 抖動。

  3. 為了降低抖動。 引入暫存隊列
  4. 增加頁面 臟, 幹凈 等狀態, 進一步優化

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

  1. 空暇, 此時page 在 某個zone 管理區的free_area 隊列中。

    頁面引用計數為 0.

  2. 分配。 分配頁面。 引用計數 為 1, page 不在處於 free_area隊列中。
  3. 活躍狀態, 通過 lru 結構連入 active_list, 遞增引用計數
  4. 不活躍狀態(臟), 利用lru 連入 inactive_dirty_list, 遞減引用計數
  5. 將不活躍臟內容寫入交換設備。 並將其移動到 inactive_clean_list 中
  6. 不活躍狀態(幹凈)
  7. 假設在轉入不活躍狀態後一段時間內收到訪問, 轉入活躍狀態。 恢復映射
  8. 假設須要, 能夠從幹凈隊列中回收頁面, 或者回到空暇隊列。 或者另行分配。

用我自己的語言來解釋一下:
我們先分配了一個頁面, 然後這個頁面處於 活動狀態 active, 然後。 我們臨時不去訪問它了, 他就開始老化, 進入inactive 不活動(臟)狀態, 但這時候, 我們不是馬上寫入交換設備。 等再過一段時間, 確實沒人訪問, 我們將它寫入交換設備, 可是這部分頁面, 我們還是沒有釋放哦。 他被標記為 inactive 不活動(幹凈) 狀態, 如今是由相應的存儲區 zone 來管理了, 之前是由全局隊列管理的。

假設在這個頁面被用作其它用途之前, 又被訪問了, 直接建立映射就好了, 通過這樣的方法, 降低了頁面的抖動現象

3.2.3 策略實現

  1. 全局LRU 隊列, active_list 和 inactive_dirty_list
  2. 每一個頁面管理區設置 inactive_clean_list
  3. 全局 address_space 數據結構 swapper_space
  4. 為加快搜索。 引入 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 參與存儲管理等。

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