📄 shmem.c
字号:
ptr = shmem_swp_map(subdir); } } shmem_swp_unmap(ptr); return freed;}static void shmem_free_pages(struct list_head *next){ struct page *page; int freed = 0; do { page = container_of(next, struct page, lru); next = next->next; shmem_dir_free(page); freed++; if (freed >= LATENCY_LIMIT) { cond_resched(); freed = 0; } } while (next);}static void shmem_truncate_range(struct inode *inode, loff_t start, loff_t end){ struct shmem_inode_info *info = SHMEM_I(inode); unsigned long idx; unsigned long size; unsigned long limit; unsigned long stage; unsigned long diroff; struct page **dir; struct page *topdir; struct page *middir; struct page *subdir; swp_entry_t *ptr; LIST_HEAD(pages_to_free); long nr_pages_to_free = 0; long nr_swaps_freed = 0; int offset; int freed; int punch_hole; spinlock_t *needs_lock; spinlock_t *punch_lock; unsigned long upper_limit; inode->i_ctime = inode->i_mtime = CURRENT_TIME; idx = (start + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; if (idx >= info->next_index) return; spin_lock(&info->lock); info->flags |= SHMEM_TRUNCATE; if (likely(end == (loff_t) -1)) { limit = info->next_index; upper_limit = SHMEM_MAX_INDEX; info->next_index = idx; needs_lock = NULL; punch_hole = 0; } else { if (end + 1 >= inode->i_size) { /* we may free a little more */ limit = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; upper_limit = SHMEM_MAX_INDEX; } else { limit = (end + 1) >> PAGE_CACHE_SHIFT; upper_limit = limit; } needs_lock = &info->lock; punch_hole = 1; } topdir = info->i_indirect; if (topdir && idx <= SHMEM_NR_DIRECT && !punch_hole) { info->i_indirect = NULL; nr_pages_to_free++; list_add(&topdir->lru, &pages_to_free); } spin_unlock(&info->lock); if (info->swapped && idx < SHMEM_NR_DIRECT) { ptr = info->i_direct; size = limit; if (size > SHMEM_NR_DIRECT) size = SHMEM_NR_DIRECT; nr_swaps_freed = shmem_free_swp(ptr+idx, ptr+size, needs_lock); } /* * If there are no indirect blocks or we are punching a hole * below indirect blocks, nothing to be done. */ if (!topdir || limit <= SHMEM_NR_DIRECT) goto done2; /* * The truncation case has already dropped info->lock, and we're safe * because i_size and next_index have already been lowered, preventing * access beyond. But in the punch_hole case, we still need to take * the lock when updating the swap directory, because there might be * racing accesses by shmem_getpage(SGP_CACHE), shmem_unuse_inode or * shmem_writepage. However, whenever we find we can remove a whole * directory page (not at the misaligned start or end of the range), * we first NULLify its pointer in the level above, and then have no * need to take the lock when updating its contents: needs_lock and * punch_lock (either pointing to info->lock or NULL) manage this. */ upper_limit -= SHMEM_NR_DIRECT; limit -= SHMEM_NR_DIRECT; idx = (idx > SHMEM_NR_DIRECT)? (idx - SHMEM_NR_DIRECT): 0; offset = idx % ENTRIES_PER_PAGE; idx -= offset; dir = shmem_dir_map(topdir); stage = ENTRIES_PER_PAGEPAGE/2; if (idx < ENTRIES_PER_PAGEPAGE/2) { middir = topdir; diroff = idx/ENTRIES_PER_PAGE; } else { dir += ENTRIES_PER_PAGE/2; dir += (idx - ENTRIES_PER_PAGEPAGE/2)/ENTRIES_PER_PAGEPAGE; while (stage <= idx) stage += ENTRIES_PER_PAGEPAGE; middir = *dir; if (*dir) { diroff = ((idx - ENTRIES_PER_PAGEPAGE/2) % ENTRIES_PER_PAGEPAGE) / ENTRIES_PER_PAGE; if (!diroff && !offset && upper_limit >= stage) { if (needs_lock) { spin_lock(needs_lock); *dir = NULL; spin_unlock(needs_lock); needs_lock = NULL; } else *dir = NULL; nr_pages_to_free++; list_add(&middir->lru, &pages_to_free); } shmem_dir_unmap(dir); dir = shmem_dir_map(middir); } else { diroff = 0; offset = 0; idx = stage; } } for (; idx < limit; idx += ENTRIES_PER_PAGE, diroff++) { if (unlikely(idx == stage)) { shmem_dir_unmap(dir); dir = shmem_dir_map(topdir) + ENTRIES_PER_PAGE/2 + idx/ENTRIES_PER_PAGEPAGE; while (!*dir) { dir++; idx += ENTRIES_PER_PAGEPAGE; if (idx >= limit) goto done1; } stage = idx + ENTRIES_PER_PAGEPAGE; middir = *dir; if (punch_hole) needs_lock = &info->lock; if (upper_limit >= stage) { if (needs_lock) { spin_lock(needs_lock); *dir = NULL; spin_unlock(needs_lock); needs_lock = NULL; } else *dir = NULL; nr_pages_to_free++; list_add(&middir->lru, &pages_to_free); } shmem_dir_unmap(dir); cond_resched(); dir = shmem_dir_map(middir); diroff = 0; } punch_lock = needs_lock; subdir = dir[diroff]; if (subdir && !offset && upper_limit-idx >= ENTRIES_PER_PAGE) { if (needs_lock) { spin_lock(needs_lock); dir[diroff] = NULL; spin_unlock(needs_lock); punch_lock = NULL; } else dir[diroff] = NULL; nr_pages_to_free++; list_add(&subdir->lru, &pages_to_free); } if (subdir && page_private(subdir) /* has swap entries */) { size = limit - idx; if (size > ENTRIES_PER_PAGE) size = ENTRIES_PER_PAGE; freed = shmem_map_and_free_swp(subdir, offset, size, &dir, punch_lock); if (!dir) dir = shmem_dir_map(middir); nr_swaps_freed += freed; if (offset || punch_lock) { spin_lock(&info->lock); set_page_private(subdir, page_private(subdir) - freed); spin_unlock(&info->lock); } else BUG_ON(page_private(subdir) != freed); } offset = 0; }done1: shmem_dir_unmap(dir);done2: if (inode->i_mapping->nrpages && (info->flags & SHMEM_PAGEIN)) { /* * Call truncate_inode_pages again: racing shmem_unuse_inode * may have swizzled a page in from swap since vmtruncate or * generic_delete_inode did it, before we lowered next_index. * Also, though shmem_getpage checks i_size before adding to * cache, no recheck after: so fix the narrow window there too. * * Recalling truncate_inode_pages_range and unmap_mapping_range * every time for punch_hole (which never got a chance to clear * SHMEM_PAGEIN at the start of vmtruncate_range) is expensive, * yet hardly ever necessary: try to optimize them out later. */ truncate_inode_pages_range(inode->i_mapping, start, end); if (punch_hole) unmap_mapping_range(inode->i_mapping, start, end - start, 1); } spin_lock(&info->lock); info->flags &= ~SHMEM_TRUNCATE; info->swapped -= nr_swaps_freed; if (nr_pages_to_free) shmem_free_blocks(inode, nr_pages_to_free); shmem_recalc_inode(inode); spin_unlock(&info->lock); /* * Empty swap vector directory pages to be freed? */ if (!list_empty(&pages_to_free)) { pages_to_free.prev->next = NULL; shmem_free_pages(pages_to_free.next); }}static void shmem_truncate(struct inode *inode){ shmem_truncate_range(inode, inode->i_size, (loff_t)-1);}static int shmem_notify_change(struct dentry *dentry, struct iattr *attr){ struct inode *inode = dentry->d_inode; struct page *page = NULL; int error; if (S_ISREG(inode->i_mode) && (attr->ia_valid & ATTR_SIZE)) { if (attr->ia_size < inode->i_size) { /* * If truncating down to a partial page, then * if that page is already allocated, hold it * in memory until the truncation is over, so * truncate_partial_page cannnot miss it were * it assigned to swap. */ if (attr->ia_size & (PAGE_CACHE_SIZE-1)) { (void) shmem_getpage(inode, attr->ia_size>>PAGE_CACHE_SHIFT, &page, SGP_READ, NULL); if (page) unlock_page(page); } /* * Reset SHMEM_PAGEIN flag so that shmem_truncate can * detect if any pages might have been added to cache * after truncate_inode_pages. But we needn't bother * if it's being fully truncated to zero-length: the * nrpages check is efficient enough in that case. */ if (attr->ia_size) { struct shmem_inode_info *info = SHMEM_I(inode); spin_lock(&info->lock); info->flags &= ~SHMEM_PAGEIN; spin_unlock(&info->lock); } } } error = inode_change_ok(inode, attr); if (!error) error = inode_setattr(inode, attr);#ifdef CONFIG_TMPFS_POSIX_ACL if (!error && (attr->ia_valid & ATTR_MODE)) error = generic_acl_chmod(inode, &shmem_acl_ops);#endif if (page) page_cache_release(page); return error;}static void shmem_delete_inode(struct inode *inode){ struct shmem_inode_info *info = SHMEM_I(inode); if (inode->i_op->truncate == shmem_truncate) { truncate_inode_pages(inode->i_mapping, 0); shmem_unacct_size(info->flags, inode->i_size); inode->i_size = 0; shmem_truncate(inode); if (!list_empty(&info->swaplist)) { mutex_lock(&shmem_swaplist_mutex); list_del_init(&info->swaplist); mutex_unlock(&shmem_swaplist_mutex); } } BUG_ON(inode->i_blocks); shmem_free_inode(inode->i_sb); clear_inode(inode);}static inline int shmem_find_swp(swp_entry_t entry, swp_entry_t *dir, swp_entry_t *edir){ swp_entry_t *ptr; for (ptr = dir; ptr < edir; ptr++) { if (ptr->val == entry.val) return ptr - dir; } return -1;}static int shmem_unuse_inode(struct shmem_inode_info *info, swp_entry_t entry, struct page *page){ struct inode *inode; unsigned long idx; unsigned long size; unsigned long limit; unsigned long stage; struct page **dir; struct page *subdir; swp_entry_t *ptr; int offset; int error; idx = 0; ptr = info->i_direct; spin_lock(&info->lock); if (!info->swapped) { list_del_init(&info->swaplist); goto lost2; } limit = info->next_index; size = limit; if (size > SHMEM_NR_DIRECT) size = SHMEM_NR_DIRECT; offset = shmem_find_swp(entry, ptr, ptr+size); if (offset >= 0) goto found; if (!info->i_indirect) goto lost2; dir = shmem_dir_map(info->i_indirect); stage = SHMEM_NR_DIRECT + ENTRIES_PER_PAGEPAGE/2; for (idx = SHMEM_NR_DIRECT; idx < limit; idx += ENTRIES_PER_PAGE, dir++) { if (unlikely(idx == stage)) { shmem_dir_unmap(dir-1); if (cond_resched_lock(&info->lock)) { /* check it has not been truncated */ if (limit > info->next_index) { limit = info->next_index; if (idx >= limit) goto lost2; } } dir = shmem_dir_map(info->i_indirect) + ENTRIES_PER_PAGE/2 + idx/ENTRIES_PER_PAGEPAGE; while (!*dir) { dir++; idx += ENTRIES_PER_PAGEPAGE; if (idx >= limit) goto lost1; } stage = idx + ENTRIES_PER_PAGEPAGE; subdir = *dir; shmem_dir_unmap(dir); dir = shmem_dir_map(subdir); } subdir = *dir; if (subdir && page_private(subdir)) { ptr = shmem_swp_map(subdir); size = limit - idx; if (size > ENTRIES_PER_PAGE) size = ENTRIES_PER_PAGE; offset = shmem_find_swp(entry, ptr, ptr+size); shmem_swp_unmap(ptr); if (offset >= 0) { shmem_dir_unmap(dir); goto found; } } }lost1: shmem_dir_unmap(dir-1);lost2: spin_unlock(&info->lock); return 0;found: idx += offset; inode = igrab(&info->vfs_inode); spin_unlock(&info->lock); /* * Move _head_ to start search for next from here. * But be careful: shmem_delete_inode checks list_empty without taking * mutex, and there's an instant in list_move_tail when info->swaplist * would appear empty, if it were the only one on shmem_swaplist. We * could avoid doing it if inode NULL; or use this minor optimization. */ if (shmem_swaplist.next != &info->swaplist) list_move_tail(&shmem_swaplist, &info->swaplist); mutex_unlock(&shmem_swaplist_mutex); error = 1; if (!inode) goto out; /* * Charge page using GFP_KERNEL while we can wait. * Charged back to the user(not to caller) when swap account is used. * add_to_page_cache() will be called with GFP_NOWAIT. */ error = mem_cgroup_cache_charge(page, current->mm, GFP_KERNEL); if (error) goto out; error = radix_tree_preload(GFP_KERNEL); if (error) { mem_cgroup_uncharge_cache_page(page); goto out; } error = 1; spin_lock(&info->lock); ptr = shmem_swp_entry(info, idx, NULL); if (ptr && ptr->val == entry.val) { error = add_to_page_cache_locked(page, inode->i_mapping, idx, GFP_NOWAIT); /* does mem_cgroup_uncharge_cache_page on error */ } else /* we must compensate for our precharge above */ mem_cgroup_uncharge_cache_page(page); if (error == -EEXIST) { struct page *filepage = find_get_page(inode->i_mapping, idx); error = 1; if (filepage) { /* * There might be a more uptodate page coming down * from a stacked writepage: forget our swappage if so. */ if (PageUptodate(filepage)) error = 0; page_cache_release(filepage); } } if (!error) { delete_from_swap_cache(page); set_page_dirty(page); info->flags |= SHMEM_PAGEIN; shmem_swp_set(info, ptr, 0); swap_free(entry); error = 1; /* not an error, but entry was found */ } if (ptr) shmem_swp_unmap(ptr); spin_unlock(&info->lock); radix_tree_preload_end();out: unlock_page(page); page_cache_release(page); iput(inode); /* allows for NULL */ return error;}/* * shmem_unuse() search for an eventually swapped out shmem page. */int shmem_unuse(swp_entry_t entry, struct page *page){ struct list_head *p, *next; struct shmem_inode_info *info; int found = 0; mutex_lock(&shmem_swaplist_mutex);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -