History

mm, swap: fix race between swapoff and some swap operations

When swapin is performed, after getting the swap entry information from the page table, system will swap in the swap entry, without any lock held to prevent the swap device from being swapoff. This may cause the race like below, CPU 1 CPU 2 ----- ----- do_swap_page swapin_readahead __read_swap_cache_async swapoff swapcache_prepare p->swap_map = NULL __swap_duplicate p->swap_map[?] /* !!! NULL pointer access */ Because swapoff is usually done when system shutdown only, the race may not hit many people in practice. But it is still a race need to be fixed. To fix the race, get_swap_device() is added to check whether the specified swap entry is valid in its swap device. If so, it will keep the swap entry valid via preventing the swap device from being swapoff, until put_swap_device() is called. Because swapoff() is very rare code path, to make the normal path runs as fast as possible, rcu_read_lock/unlock() and synchronize_rcu() instead of reference count is used to implement get/put_swap_device(). >From get_swap_device() to put_swap_device(), RCU reader side is locked, so synchronize_rcu() in swapoff() will wait until put_swap_device() is called. In addition to swap_map, cluster_info, etc. data structure in the struct swap_info_struct, the swap cache radix tree will be freed after swapoff, so this patch fixes the race between swap cache looking up and swapoff too. Races between some other swap cache usages and swapoff are fixed too via calling synchronize_rcu() between clearing PageSwapCache() and freeing swap cache data structure. Another possible method to fix this is to use preempt_off() + stop_machine() to prevent the swap device from being swapoff when its data structure is being accessed. The overhead in hot-path of both methods is similar. The advantages of RCU based method are, 1. stop_machine() may disturb the normal execution code path on other CPUs. 2. File cache uses RCU to protect its radix tree. If the similar mechanism is used for swap cache too, it is easier to share code between them. 3. RCU is used to protect swap cache in total_swapcache_pages() and exit_swap_address_space() already. The two mechanisms can be merged to simplify the logic.

Huang Ying committed 7 years ago

Commit eb085571 parent a4985834 files · +146 −39Browse files

Showing the changes to include/linux (1 of 4 files). Show all

Unified · Split

include/linux/swap.h+10−3
@@ -175,8 +175,9 @@ enum { · 174 unmodified lines
175 SWP_PAGE_DISCARD = (1 << 10), /* freed swap page-cluster discards */175 SWP_PAGE_DISCARD = (1 << 10), /* freed swap page-cluster discards */
176 SWP_STABLE_WRITES = (1 << 11), /* no overwrite PG_writeback pages */176 SWP_STABLE_WRITES = (1 << 11), /* no overwrite PG_writeback pages */
177 SWP_SYNCHRONOUS_IO = (1 << 12), /* synchronous IO is efficient */177 SWP_SYNCHRONOUS_IO = (1 << 12), /* synchronous IO is efficient */
178 SWP_VALID = (1 << 13), /* swap is valid to be operated on? */
178 /* add others here before... */179 /* add others here before... */
179 SWP_SCANNING = (1 << 13), /* refcount in scan_swap_map */180 SWP_SCANNING = (1 << 14), /* refcount in scan_swap_map */
180};181};
181 182
182#define SWAP_CLUSTER_MAX 32UL183#define SWAP_CLUSTER_MAX 32UL
@@ -460,7 +461,7 @@ extern unsigned int count_swap_pages(int, int); · 277 unmodified lines
460extern sector_t map_swap_page(struct page *, struct block_device **);461extern sector_t map_swap_page(struct page *, struct block_device **);
461extern sector_t swapdev_block(int, pgoff_t);462extern sector_t swapdev_block(int, pgoff_t);
462extern int page_swapcount(struct page *);463extern int page_swapcount(struct page *);
463extern int __swap_count(struct swap_info_struct *si, swp_entry_t entry);464extern int __swap_count(swp_entry_t entry);
464extern int __swp_swapcount(swp_entry_t entry);465extern int __swp_swapcount(swp_entry_t entry);
465extern int swp_swapcount(swp_entry_t entry);466extern int swp_swapcount(swp_entry_t entry);
466extern struct swap_info_struct *page_swap_info(struct page *);467extern struct swap_info_struct *page_swap_info(struct page *);
@@ -470,6 +471,12 @@ extern int try_to_free_swap(struct page *); · 3 unmodified lines
470struct backing_dev_info;471struct backing_dev_info;
471extern int init_swap_address_space(unsigned int type, unsigned long nr_pages);472extern int init_swap_address_space(unsigned int type, unsigned long nr_pages);
472extern void exit_swap_address_space(unsigned int type);473extern void exit_swap_address_space(unsigned int type);
474extern struct swap_info_struct *get_swap_device(swp_entry_t entry);
475
476static inline void put_swap_device(struct swap_info_struct *si)
477{
478 rcu_read_unlock();
479}
473 480
474#else /* CONFIG_SWAP */481#else /* CONFIG_SWAP */
475 482
@@ -576,7 +583,7 @@ static inline int page_swapcount(struct page *page) · 100 unmodified lines
576 return 0;583 return 0;
577}584}
578 585
579static inline int __swap_count(struct swap_info_struct *si, swp_entry_t entry)586static inline int __swap_count(swp_entry_t entry)
580{587{
581 return 0;588 return 0;
582}589}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t