Fix refcounting in hugetlbfs quota handling
Subsystems (3)
Linus, please apply
hugetlbfs tracks the current usage of hugepages per hugetlbfs mountpoint. To correctly track this when hugepages are released, it must find the right hugetlbfs super_block from the struct page available in free_huge_page().
It does this by storing a pointer to the hugepage's struct address_space in the page_private data. The hugetlb_{get,put}_quota functions go from this mapping to the inode and thence to the super_block.
However, this usage is buggy, because nothing ensures that the address_space is not freed before all the hugepages that belonged to it are. In practice that will usually be the case, but if extra page references have been taken by e.g. drivers or kvm doing get_user_pages() then the file, inode and address space may be destroyed before all the pages.
In addition, the quota functions use the mapping only to get the inode then the super_block. However, most of the callers already have the inode anyway and have to get the mapping from there.
This patch, therefore, stores a pointer to the inode instead of the address_space in the page private data for hugepages. More importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it from free_huge_page.
fs/hugetlbfs/inode.c +4 -4
@@ -874,10 +874,10 @@ out_free:return -ENOMEM ; } -int hugetlb_get_quota (struct address_space *mapping,long delta) +int hugetlb_get_quota (struct inode *inode,long delta) {int ret =0 ; -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); +struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (inode->i_sb);if (sbinfo->free_blocks >-1 ) {spin_lock (&sbinfo->stat_lock);
@@ -891,9 +891,9 @@ int hugetlb_get_quota(struct address_spareturn ret; } -void hugetlb_put_quota (struct address_space *mapping,long delta) +void hugetlb_put_quota (struct inode *inode,long delta) { -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); +struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (inode->i_sb);if (sbinfo->free_blocks >-1 ) {spin_lock (&sbinfo->stat_lock);
include/linux/hugetlb.h +2 -2
@@ -171,8 +171,8 @@ extern const struct file_operations hugeextern const struct vm_operations_struct hugetlb_vm_ops;struct file *hugetlb_file_setup (const char *name,size_t size,vm_flags_t acct,struct user_struct **user,int creat_flags); -int hugetlb_get_quota (struct address_space *mapping,long delta); -void hugetlb_put_quota (struct address_space *mapping,long delta); +int hugetlb_get_quota (struct inode *inode,long delta); +void hugetlb_put_quota (struct inode *inode,long delta);static inline int is_file_hugepages (struct file *file) {
mm/hugetlb.c +14 -11
@@ -533,10 +533,12 @@ static void free_huge_page(struct page **/ struct hstate *h =page_hstate (page);int nid =page_to_nid (page); -struct address_space *mapping; +struct inode *inode; - mapping = (struct address_space *)page_private (page); + inode = (struct inode *)page_private (page);set_page_private (page,0 ); +iput (inode); + page->mapping =NULL ;BUG_ON (page_count (page));BUG_ON (page_mapcount (page));
@@ -551,8 +553,8 @@ static void free_huge_page(struct page *enqueue_huge_page (h, page); }spin_unlock (&hugetlb_lock); -if (mapping) -hugetlb_put_quota (mapping,1 ); +if (inode) +hugetlb_put_quota (inode,1 ); }static void prep_new_huge_page (struct hstate *h,struct page *page,int nid)
@@ -1035,7 +1037,7 @@ static struct page *alloc_huge_page(struif (chg <0 )return ERR_PTR (-VM_FAULT_OOM );if (chg) -if (hugetlb_get_quota (inode->i_mapping, chg)) +if (hugetlb_get_quota (inode, chg))return ERR_PTR (-VM_FAULT_SIGBUS );spin_lock (&hugetlb_lock);
@@ -1045,12 +1047,12 @@ static struct page *alloc_huge_page(struif (!page) { page =alloc_buddy_huge_page (h,NUMA_NO_NODE );if (!page) { -hugetlb_put_quota (inode->i_mapping, chg); +hugetlb_put_quota (inode, chg);return ERR_PTR (-VM_FAULT_SIGBUS ); } } -set_page_private (page, (unsigned long ) mapping); +set_page_private (page, (unsigned long )igrab (inode));vma_commit_reservation (h, vma, addr);
@@ -2086,7 +2088,8 @@ static void hugetlb_vm_op_close(struct vif (reserve) {hugetlb_acct_memory (h, -reserve); -hugetlb_put_quota (vma->vm_file->f_mapping, reserve); +hugetlb_put_quota (vma->vm_file->f_mapping->host, + reserve); } } }
@@ -2884,7 +2887,7 @@ int hugetlb_reserve_pages(struct inode *return chg;/* There must be enough filesystem quota for the mapping */ -if (hugetlb_get_quota (inode->i_mapping, chg)) +if (hugetlb_get_quota (inode, chg))return -ENOSPC ;/*
@@ -2893,7 +2896,7 @@ int hugetlb_reserve_pages(struct inode **/ ret =hugetlb_acct_memory (h, chg);if (ret <0 ) { -hugetlb_put_quota (inode->i_mapping, chg); +hugetlb_put_quota (inode, chg);return ret; }
@@ -2922,7 +2925,7 @@ void hugetlb_unreserve_pages(struct inod inode->i_blocks -= (blocks_per_huge_page (h) * freed);spin_unlock (&inode->i_lock); -hugetlb_put_quota (inode->i_mapping, (chg - freed)); +hugetlb_put_quota (inode, (chg - freed));hugetlb_acct_memory (h, -(chg - freed)); }
Signature · 4 lines
David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
Quoted 7 lines · David Gibson
This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages. More importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.
Looks sane, but I *really* want some acks from people who use/know hugetlbfs. Who would that be? I'm adding random people who have acked/signed-off patches to hugetlbfs recently..
Linus
--- patch left quoted for new people ---
Quoted 149 lines
Signed-off-by David Gibson <[email protected]>Index:
working-2.6/fs/hugetlbfs/inode.c
===================================================================fs/hugetlbfs/inode.c 2011-08-10 17:22:21.899638039 +1000 renamed from fs/hugetlbfs/inode.c 2011-08-10 16:45:47.864758406 +1000 +4
-4@@ -874,10 +874,10 @@ out_free: return -ENOMEM; } -int hugetlb_get_quota(struct address_space *mapping, long delta) +int hugetlb_get_quota(struct inode *inode, long delta) { int ret = 0; - struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(mapping->host->i_sb); + struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(inode->i_sb); if (sbinfo->free_blocks > -1) { spin_lock(&sbinfo->stat_lock);@@ -891,9 +891,9 @@ int hugetlb_get_quota(struct address_spa return ret; } -void hugetlb_put_quota(struct address_space *mapping, long delta) +void hugetlb_put_quota(struct inode *inode, long delta) { - struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(mapping->host->i_sb); + struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(inode->i_sb); if (sbinfo->free_blocks > -1) { spin_lock(&sbinfo->stat_lock);Index:
working-2.6/include/linux/hugetlb.h
===================================================================include/linux/hugetlb.h 2011-08-10 17:22:08.723572707 +1000 renamed from include/linux/hugetlb.h 2011-08-10 16:58:27.952527484 +1000 +2
-2@@ -171,8 +171,8 @@ extern const struct file_operations huge extern const struct vm_operations_struct hugetlb_vm_ops; struct file *hugetlb_file_setup(const char *name, size_t size, vm_flags_t acct, struct user_struct **user, int creat_flags); -int hugetlb_get_quota(struct address_space *mapping, long delta); -void hugetlb_put_quota(struct address_space *mapping, long delta); +int hugetlb_get_quota(struct inode *inode, long delta); +void hugetlb_put_quota(struct inode *inode, long delta); static inline int is_file_hugepages(struct file *file) {Index:
working-2.6/mm/hugetlb.c
===================================================================mm/hugetlb.c 2011-08-10 17:21:49.603477888 +1000 renamed from mm/hugetlb.c 2011-08-10 16:44:12.212284092 +1000 +14
-12@@ -533,10 +533,12 @@ static void free_huge_page(struct page * */ struct hstate *h = page_hstate(page); int nid = page_to_nid(page); - struct address_space *mapping; + struct inode *inode; - mapping = (struct address_space *) page_private(page); + inode = (struct inode *) page_private(page); set_page_private(page, 0); + iput(inode); + page->mapping = NULL; BUG_ON(page_count(page)); BUG_ON(page_mapcount(page));@@ -551,8 +553,8 @@ static void free_huge_page(struct page * enqueue_huge_page(h, page); } spin_unlock(&hugetlb_lock); - if (mapping) - hugetlb_put_quota(mapping, 1); + if (inode) + hugetlb_put_quota(inode, 1); } static void prep_new_huge_page(struct hstate *h, struct page *page, int nid)@@ -1035,7 +1037,7 @@ static struct page *alloc_huge_page(stru if (chg < 0) return ERR_PTR(-VM_FAULT_OOM); if (chg) - if (hugetlb_get_quota(inode->i_mapping, chg)) + if (hugetlb_get_quota(inode, chg)) return ERR_PTR(-VM_FAULT_SIGBUS); spin_lock(&hugetlb_lock);@@ -1045,12 +1047,12 @@ static struct page *alloc_huge_page(stru if (!page) { page = alloc_buddy_huge_page(h, NUMA_NO_NODE); if (!page) { - hugetlb_put_quota(inode->i_mapping, chg); + hugetlb_put_quota(inode, chg); return ERR_PTR(-VM_FAULT_SIGBUS); } } - set_page_private(page, (unsigned long) mapping); + set_page_private(page, (unsigned long) igrab(inode)); vma_commit_reservation(h, vma, addr);@@ -2086,7 +2088,8 @@ static void hugetlb_vm_op_close(struct v if (reserve) { hugetlb_acct_memory(h, -reserve); - hugetlb_put_quota(vma->vm_file->f_mapping, reserve); + hugetlb_put_quota(vma->vm_file->f_mapping->host, + reserve); } } }@@ -2884,7 +2887,7 @@ int hugetlb_reserve_pages(struct inode * return chg; /* There must be enough filesystem quota for the mapping */ - if (hugetlb_get_quota(inode->i_mapping, chg)) + if (hugetlb_get_quota(inode, chg)) return -ENOSPC; /*@@ -2893,7 +2896,7 @@ int hugetlb_reserve_pages(struct inode * */ ret = hugetlb_acct_memory(h, chg); if (ret < 0) { - hugetlb_put_quota(inode->i_mapping, chg); + hugetlb_put_quota(inode, chg); return ret; }@@ -2922,7 +2925,7 @@ void hugetlb_unreserve_pages(struct inod inode->i_blocks -= (blocks_per_huge_page(h) * freed); spin_unlock(&inode->i_lock); - hugetlb_put_quota(inode->i_mapping, (chg - freed)); + hugetlb_put_quota(inode, (chg - freed)); hugetlb_acct_memory(h, -(chg - freed)); } --David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_!
http://www.ozlabs.org/~dgibson
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Quoted 10 lines · Linus Torvalds
Quoted 7 lines · David Gibson
This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages. More importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.
Quoted 3 lines
Looks sane, but I *really* want some acks from people who use/know hugetlbfs. Who would that be? I'm adding random people who have acked/signed-off patches to hugetlbfs recently..
At least, code itself looks good to me but your random choice was failed. Maybe people you want are as follows.
http://marc.info/?t=126928975800003&r=1&w=2
Ccing right persons.
Signature · 2 lines
Kind regards, Minchan Kim
Quoted 16 lines · Minchan Kim
Quoted 10 lines · Linus Torvalds
Quoted 7 lines · David Gibson
This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages. More importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.Quoted 3 lines
Looks sane, but I *really* want some acks from people who use/know hugetlbfs. Who would that be? I'm adding random people who have acked/signed-off patches to hugetlbfs recently..
Quoted 5 lines
At least, code itself looks good to me but your random choice was failed. Maybe people you want are as follows.
http://marc.info/?t=126928975800003&r=1&w=2Ccing right persons.
I don't know much about hugetlbfs these days, but I think the patch is very wrong.
The real change is where alloc_huge_page() does igrab(inode) and free_huge_pages() does iput(inode)?
That makes me very nervous, partly because a final iput() is a complex operation, which we wouldn't expect to be doing when "freeing" a page.
My first worry was that free_huge_page() could actually get called at interrupt time (when it's in a pagevec of pages to be freed as a batch, then another put_page is done at interrupt time which frees that batch):
I worried that we use spin_lock not spin_lock_irqsave on inode->i_lock. To be honest though, I've not followed up whether that's actually a possibility, the compound page path is too twisty for a quick answer; and even if it's a possibility, it's one that's already ignored in the case of hugetlb_lock.
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
A quick experiment with your patch versus without confirmed that:
meminfo HugePages_Free stayed down with your patch, but went back to HugePages_Total without it. Please check, perhaps I'm just mistaken.
Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
On a side note, very good description - thank you, but I wish you'd split the patch into two, the fix and then the inode-instead-of-mapping cleanup. Though personally I'd prefer not to make that "cleanup": it's normal for a struct address space * to be used in struct page (if I delved I guess I'd find good reason why this one is in page->private instead of page->mapping: perhaps because it's needed after page->mapping is reset to NULL, perhaps because it's needed on COWed copies of hugetlbfs pages).
Hugh
Quoted 41 lines · Hugh Dickins
Quoted 16 lines · Minchan Kim
Quoted 10 lines · Linus Torvalds
Quoted 7 lines · David Gibson
This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages. More importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.Quoted 3 lines
Looks sane, but I *really* want some acks from people who use/know hugetlbfs. Who would that be? I'm adding random people who have acked/signed-off patches to hugetlbfs recently..
Quoted 5 lines
At least, code itself looks good to me but your random choice was failed. Maybe people you want are as follows.
http://marc.info/?t=126928975800003&r=1&w=2Ccing right persons.
I don't know much about hugetlbfs these days, but I think the patch is very wrong.
The real change is where
alloc_huge_page()does igrab(inode) andfree_huge_pages()does iput(inode)?That makes me very nervous, partly because a final
iput()is a complex operation, which we wouldn't expect to be doing when "freeing" a page.My first worry was that
free_huge_page()could actually get called at interrupt time (when it's in a pagevec of pages to be freed as a batch, then anotherput_pageis done at interrupt time which frees that batch):
I worried that we usespin_locknotspin_lock_irqsaveoninode->i_lock. To be honest though, I've not followed up whether that's actually a possibility, the compound page path is too twisty for a quick answer; and even if it's a possibility, it's one that's already ignored in the case ofhugetlb_lock.
Quoted 4 lines
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
Quoted 7 lines
On a side note, very good description - thank you, but I wish you'd split the patch into two, the fix and then the inode-instead-of-mapping cleanup. Though personally I'd prefer not to make that "cleanup": it's normal for a struct address space * to be used in struct page (if I delved I guess I'd find good reason why this one is in
page->privateinstead ofpage->mapping: perhaps because it's needed afterpage->mappingis reset to NULL, perhaps because it's needed on COWed copies of hugetlbfs pages).
That is an interesting question. But it doesn't address the basic point. mappings aren't refcounted themselves, and as far as I can tell their lifetime is bound to that of their inode.
Signature · 4 lines
David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
Quoted 19 lines · David Gibson
Quoted 5 lines · Hugh Dickins
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Quoted 8 lines
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
That does sound closer to a reaonable approach, but my guess is that it will suck you into a world of superblock mutexes and semaphores, which you cannot take at free_huge_page() time.
It might be necessary to hang your own tiny structure off the superblock, with one refcount for the superblock, and one for each hugepage attached, you freeing that structure when the count goes down to zero from either direction.
Whatever you do needs testing with lockdep and atomic sleep checks.
I do dislike tying these separate levels together in such an unusual way, but it is a difficult problem and I don't know of an easy answer. Maybe we'll need to find a better answer for other reasons, it does come up from time to time e.g. recent race between evicting inode and nrpages going down to 0.
You might care to take a look at how tmpfs (mm/shmem.c) deals with the equivalent issue there (sbinfo->used_blocks). But I expect you to conclude that hugetlbfs cannot afford the kind of approximations that tmpfs can afford.
Although I think tmpfs is more correct, to be associating the count with pagecache (so the count goes down as soon as a page is truncated or evicted from pagecache), your fewer and huger pages, and reservation conventions, may well demand that the count stays up until the page is actually freed back to hugepool. And let's not pretend that what tmpfs does is wonderful: the strange shmem_recalc_inode() tries its best to notice when memory pressure has freed clean pages, but it never looks beyond the inode being accessed at the times it's called. Not at all satisfactory, but not actually an issue in practice, since we stopped allocating pages for simple reads from sparse file. I did want to convert tmpfs to use ->freepage(), but couldn't manage it without stable mapping - same problem as you have.
Hugh
I've been doing something similar to this last proposal. I put a hugetlbfs_sb_info pointer into page_private, and dropped a reference counter and an active/inactive bit into the hugetlbfs_sb_info struct. At Umount time, the sbinfo is freed, only if the reference count is zero. Otherwise, the last put_quota frees the sbinfo structure. This fixed the race we were seeing between umount and a put_quota from an rdma transaction. I just gave it a cursory test on a 3.0 kernel; it has seen quite a lot more testing on a 2.6.32-derived kernel, with no more hits of the umount race.
Does this address the problems you were thinking about? -Andrew Barry
fs/hugetlbfs/inode.c +26 -14
@@ -615,8 +615,12 @@ static void hugetlbfs_put_super(struct sstruct hugetlbfs_sb_info *sbi =HUGETLBFS_SB (sb);if (sbi) { + sbi->active =HPAGE_INACTIVE ; sb->s_fs_info =NULL ; -kfree (sbi); + +/*Free only if used quota is zero. */ +if (sbi->used_blocks ==0 ) +kfree (sbi); } }
@@ -851,6 +855,8 @@ hugetlbfs_fill_super(struct super_block sbinfo->free_blocks = config.nr_blocks; sbinfo->max_inodes = config.nr_inodes; sbinfo->free_inodes = config.nr_inodes; + sbinfo->used_blocks =0 ; + sbinfo->active =HPAGE_ACTIVE ; sb->s_maxbytes =MAX_LFS_FILESIZE ; sb->s_blocksize =huge_page_size (config.hstate); sb->s_blocksize_bits =huge_page_shift (config.hstate);
@@ -874,30 +880,36 @@ out_free:return -ENOMEM ; } -int hugetlb_get_quota (struct address_space *mapping,long delta) +int hugetlb_get_quota (struct hugetlbfs_sb_info *sbinfo,long delta) {int ret =0 ; -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); -if (sbinfo->free_blocks >-1 ) { -spin_lock (&sbinfo->stat_lock); -if (sbinfo->free_blocks - delta >=0 ) +spin_lock (&sbinfo->stat_lock); +if ((sbinfo->free_blocks ==-1 ) || (sbinfo->free_blocks - delta >=0 )) { +if (sbinfo->free_blocks !=-1 ) sbinfo->free_blocks -= delta; -else - ret = -ENOMEM ; -spin_unlock (&sbinfo->stat_lock); + sbinfo->used_blocks += delta; + sbinfo->active =HPAGE_ACTIVE ; + }else { + ret = -ENOMEM ; } +spin_unlock (&sbinfo->stat_lock);return ret; } -void hugetlb_put_quota (struct address_space *mapping,long delta) +void hugetlb_put_quota (struct hugetlbfs_sb_info *sbinfo,long delta) { -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); - -if (sbinfo->free_blocks >-1 ) { -spin_lock (&sbinfo->stat_lock); +spin_lock (&sbinfo->stat_lock); +if (sbinfo->free_blocks >-1 ) sbinfo->free_blocks += delta; + sbinfo->used_blocks -= delta; +/* If hugetlbfs_put_super couldn't free sbinfo due to +* an outstanding quota reference, free it now. */ +if ((sbinfo->used_blocks ==0 ) && (sbinfo->active ==HPAGE_INACTIVE )) { +spin_unlock (&sbinfo->stat_lock); +kfree (sbinfo); + }else {spin_unlock (&sbinfo->stat_lock); } }
include/linux/hugetlb.h +7 -2
@@ -142,11 +142,16 @@ struct hugetlbfs_config {struct hstate *hstate; }; +#define HPAGE_INACTIVE 0 +#define HPAGE_ACTIVE 1 +struct hugetlbfs_sb_info {long max_blocks;/* blocks allowed */ long free_blocks;/* blocks free */ long max_inodes;/* inodes allowed */ long free_inodes;/* inodes free */ +long used_blocks;/* blocks used */ +long active;/* active bit */ spinlock_t stat_lock;struct hstate *hstate; };
@@ -171,8 +176,8 @@ extern const struct file_operations hugeextern const struct vm_operations_struct hugetlb_vm_ops;struct file *hugetlb_file_setup (const char *name,size_t size,vm_flags_t acct,struct user_struct **user,int creat_flags); -int hugetlb_get_quota (struct address_space *mapping,long delta); -void hugetlb_put_quota (struct address_space *mapping,long delta); +int hugetlb_get_quota (struct hugetlbfs_sb_info *sbinfo,long delta); +void hugetlb_put_quota (struct hugetlbfs_sb_info *sbinfo,long delta);static inline int is_file_hugepages (struct file *file) {
mm/hugetlb.c +7 -7
@@ -533,9 +533,9 @@ static void free_huge_page(struct page **/ struct hstate *h =page_hstate (page);int nid =page_to_nid (page); -struct address_space *mapping; +struct hugetlbfs_sb_info *sbinfo; - mapping = (struct address_space *)page_private (page); + sbinfo = (struct hugetlbfs_sb_info *)page_private (page);set_page_private (page,0 ); page->mapping =NULL ;BUG_ON (page_count (page));
@@ -551,8 +551,8 @@ static void free_huge_page(struct page *enqueue_huge_page (h, page); }spin_unlock (&hugetlb_lock); -if (mapping) -hugetlb_put_quota (mapping,1 ); +if (sbinfo) +hugetlb_put_quota (sbinfo,1 ); }static void prep_new_huge_page (struct hstate *h,struct page *page,int nid)
@@ -1035,7 +1035,7 @@ static struct page *alloc_huge_page(struif (chg <0 )return ERR_PTR (-VM_FAULT_OOM );if (chg) -if (hugetlb_get_quota (inode->i_mapping, chg)) +if (hugetlb_get_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg))return ERR_PTR (-VM_FAULT_SIGBUS );spin_lock (&hugetlb_lock);
@@ -1045,12 +1045,12 @@ static struct page *alloc_huge_page(struif (!page) { page =alloc_buddy_huge_page (h,NUMA_NO_NODE );if (!page) { -hugetlb_put_quota (inode->i_mapping, chg); +hugetlb_put_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg);return ERR_PTR (-VM_FAULT_SIGBUS ); } } -set_page_private (page, (unsigned long ) mapping); + set_page_private(page, (unsigned long)
HUGETLBFS_SB(inode->i_mapping->host->i_sb));
vma_commit_reservation(h, vma, addr);
@@ -2086,7 +2086,7 @@ static void hugetlb_vm_op_close(struct v
if (reserve) {
hugetlb_acct_memory(h, -reserve);
- hugetlb_put_quota(vma->vm_file->f_mapping, reserve);
+ hugetlb_put_quota(HUGETLBFS_SB(vma->vm_file->f_mapping->host->i_sb), reserve);
}
}
}
@@ -2884,7 +2884,7 @@ int hugetlb_reserve_pages(struct inode *
return chg;
/* There must be enough filesystem quota for the mapping */
- if (hugetlb_get_quota(inode->i_mapping, chg))
+ if (hugetlb_get_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg))
return -ENOSPC;
/*
@@ -2893,7 +2893,7 @@ int hugetlb_reserve_pages(struct inode *
*/
ret = hugetlb_acct_memory(h, chg);
if (ret < 0) {
- hugetlb_put_quota(inode->i_mapping, chg);
+ hugetlb_put_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg);
return ret;
}
@@ -2922,7 +2922,7 @@ void hugetlb_unreserve_pages(struct inod
inode->i_blocks -= (blocks_per_huge_page(h) * freed); spin_unlock(&inode->i_lock);
- hugetlb_put_quota(inode->i_mapping, (chg - freed));
+ hugetlb_put_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), (chg - freed));
hugetlb_acct_memory(h, -(chg - freed));
}
Quoted 70 lines · Hugh Dickins
Quoted 19 lines · David Gibson
Quoted 5 lines · Hugh Dickins
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Quoted 8 lines
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
That does sound closer to a reaonable approach, but my guess is that it will suck you into a world of superblock mutexes and semaphores, which you cannot take at
free_huge_page()time.It might be necessary to hang your own tiny structure off the superblock, with one refcount for the superblock, and one for each hugepage attached, you freeing that structure when the count goes down to zero from either direction.
Whatever you do needs testing with lockdep and atomic sleep checks.
I do dislike tying these separate levels together in such an unusual way, but it is a difficult problem and I don't know of an easy answer. Maybe we'll need to find a better answer for other reasons, it does come up from time to time e.g. recent race between evicting inode and nrpages going down to 0.
You might care to take a look at how tmpfs (
mm/shmem.c) deals with the equivalent issue there (sbinfo->used_blocks). But I expect you to conclude that hugetlbfs cannot afford the kind of approximations that tmpfs can afford.Although I think tmpfs is more correct, to be associating the count with pagecache (so the count goes down as soon as a page is truncated or evicted from pagecache), your fewer and huger pages, and reservation conventions, may well demand that the count stays up until the page is actually freed back to hugepool. And let's not pretend that what tmpfs does is wonderful: the strange
shmem_recalc_inode()tries its best to notice when memory pressure has freed clean pages, but it never looks beyond the inode being accessed at the times it's called. Not at all satisfactory, but not actually an issue in practice, since we stopped allocating pages for simple reads from sparse file. I did want to convert tmpfs to use ->freepage(), but couldn't manage it without stable mapping - same problem as you have.Hugh
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Quoted 10 lines · Andrew Barry
I've been doing something similar to this last proposal. I put a
hugetlbfs_sb_infopointer intopage_private, and dropped a reference counter and an active/inactive bit into thehugetlbfs_sb_infostruct. At Umount time, the sbinfo is freed, only if the reference count is zero. Otherwise, the lastput_quotafrees the sbinfo structure. This fixed the race we were seeing between umount and aput_quotafrom an rdma transaction. I just gave it a cursory test on a 3.0 kernel; it has seen quite a lot more testing on a 2.6.32-derived kernel, with no more hits of the umount race.Does this address the problems you were thinking about?
Ah, this looks much better than my patch. And the fact that you've seen your race demonstrates clearly that this isn't just a kvm problem. I hope we can push this upstream very soon - what can I do to help?
Quoted 268 lines
(quoted diff) +0
-1-Andrew Barryfs/hugetlbfs/inode.c +26
-14@@ -615,8 +615,12 @@ static void hugetlbfs_put_super(struct sstruct hugetlbfs_sb_info *sbi =HUGETLBFS_SB (sb);if (sbi) { + sbi->active =HPAGE_INACTIVE ; sb->s_fs_info =NULL ; -kfree (sbi); + +/*Free only if used quota is zero. */ +if (sbi->used_blocks ==0 ) +kfree (sbi); } }@@ -851,6 +855,8 @@ hugetlbfs_fill_super(struct super_block sbinfo->free_blocks = config.nr_blocks; sbinfo->max_inodes = config.nr_inodes; sbinfo->free_inodes = config.nr_inodes; + sbinfo->used_blocks =0 ; + sbinfo->active =HPAGE_ACTIVE ; sb->s_maxbytes =MAX_LFS_FILESIZE ; sb->s_blocksize =huge_page_size (config.hstate); sb->s_blocksize_bits =huge_page_shift (config.hstate);@@ -874,30 +880,36 @@ out_free:return -ENOMEM ; } -int hugetlb_get_quota (struct address_space *mapping,long delta) +int hugetlb_get_quota (struct hugetlbfs_sb_info *sbinfo,long delta) {int ret =0 ; -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); -if (sbinfo->free_blocks >-1 ) { -spin_lock (&sbinfo->stat_lock); -if (sbinfo->free_blocks - delta >=0 ) +spin_lock (&sbinfo->stat_lock); +if ((sbinfo->free_blocks ==-1 ) || (sbinfo->free_blocks - delta >=0 )) { +if (sbinfo->free_blocks !=-1 ) sbinfo->free_blocks -= delta; -else - ret = -ENOMEM ; -spin_unlock (&sbinfo->stat_lock); + sbinfo->used_blocks += delta; + sbinfo->active =HPAGE_ACTIVE ; + }else { + ret = -ENOMEM ; } +spin_unlock (&sbinfo->stat_lock);return ret; } -void hugetlb_put_quota (struct address_space *mapping,long delta) +void hugetlb_put_quota (struct hugetlbfs_sb_info *sbinfo,long delta) { -struct hugetlbfs_sb_info *sbinfo =HUGETLBFS_SB (mapping->host->i_sb); - -if (sbinfo->free_blocks >-1 ) { -spin_lock (&sbinfo->stat_lock); +spin_lock (&sbinfo->stat_lock); +if (sbinfo->free_blocks >-1 ) sbinfo->free_blocks += delta; + sbinfo->used_blocks -= delta; +/* If hugetlbfs_put_super couldn't free sbinfo due to +* an outstanding quota reference, free it now. */ +if ((sbinfo->used_blocks ==0 ) && (sbinfo->active ==HPAGE_INACTIVE )) { +spin_unlock (&sbinfo->stat_lock); +kfree (sbinfo); + }else {spin_unlock (&sbinfo->stat_lock); } }include/linux/hugetlb.h +7
-2@@ -142,11 +142,16 @@ struct hugetlbfs_config {struct hstate *hstate; }; +#define HPAGE_INACTIVE 0 +#define HPAGE_ACTIVE 1 +struct hugetlbfs_sb_info {long max_blocks;/* blocks allowed */ long free_blocks;/* blocks free */ long max_inodes;/* inodes allowed */ long free_inodes;/* inodes free */ +long used_blocks;/* blocks used */ +long active;/* active bit */ spinlock_t stat_lock;struct hstate *hstate; };@@ -171,8 +176,8 @@ extern const struct file_operations hugeextern const struct vm_operations_struct hugetlb_vm_ops;struct file *hugetlb_file_setup (const char *name,size_t size,vm_flags_t acct,struct user_struct **user,int creat_flags); -int hugetlb_get_quota (struct address_space *mapping,long delta); -void hugetlb_put_quota (struct address_space *mapping,long delta); +int hugetlb_get_quota (struct hugetlbfs_sb_info *sbinfo,long delta); +void hugetlb_put_quota (struct hugetlbfs_sb_info *sbinfo,long delta);static inline int is_file_hugepages (struct file *file) {mm/hugetlb.c +11
-11@@ -533,9 +533,9 @@ static void free_huge_page(struct page **/ struct hstate *h =page_hstate (page);int nid =page_to_nid (page); -struct address_space *mapping; +struct hugetlbfs_sb_info *sbinfo; - mapping = (struct address_space *)page_private (page); + sbinfo = (struct hugetlbfs_sb_info *)page_private (page);set_page_private (page,0 ); page->mapping =NULL ;BUG_ON (page_count (page));@@ -551,8 +551,8 @@ static void free_huge_page(struct page *enqueue_huge_page (h, page); }spin_unlock (&hugetlb_lock); -if (mapping) -hugetlb_put_quota (mapping,1 ); +if (sbinfo) +hugetlb_put_quota (sbinfo,1 ); }static void prep_new_huge_page (struct hstate *h,struct page *page,int nid)@@ -1035,7 +1035,7 @@ static struct page *alloc_huge_page(struif (chg <0 )return ERR_PTR (-VM_FAULT_OOM );if (chg) -if (hugetlb_get_quota (inode->i_mapping, chg)) +if (hugetlb_get_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg))return ERR_PTR (-VM_FAULT_SIGBUS );spin_lock (&hugetlb_lock);@@ -1045,12 +1045,12 @@ static struct page *alloc_huge_page(struif (!page) { page =alloc_buddy_huge_page (h,NUMA_NO_NODE );if (!page) { -hugetlb_put_quota (inode->i_mapping, chg); +hugetlb_put_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg);return ERR_PTR (-VM_FAULT_SIGBUS ); } } -set_page_private (page, (unsigned long ) mapping); +set_page_private (page, (unsigned long )HUGETLBFS_SB (inode->i_mapping->host->i_sb));vma_commit_reservation (h, vma, addr);@@ -2086,7 +2086,7 @@ static void hugetlb_vm_op_close(struct vif (reserve) {hugetlb_acct_memory (h, -reserve); -hugetlb_put_quota (vma->vm_file->f_mapping, reserve); +hugetlb_put_quota (HUGETLBFS_SB (vma->vm_file->f_mapping->host->i_sb), reserve); } } }@@ -2884,7 +2884,7 @@ int hugetlb_reserve_pages(struct inode *return chg;/* There must be enough filesystem quota for the mapping */ -if (hugetlb_get_quota (inode->i_mapping, chg)) +if (hugetlb_get_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg))return -ENOSPC ;/* @@ -2893,7 +2893,7 @@ int hugetlb_reserve_pages(struct inode **/ ret =hugetlb_acct_memory (h, chg);if (ret <0 ) { -hugetlb_put_quota (inode->i_mapping, chg); +hugetlb_put_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), chg);return ret; }@@ -2922,7 +2922,7 @@ void hugetlb_unreserve_pages(struct inod inode->i_blocks -= (blocks_per_huge_page (h) * freed);spin_unlock (&inode->i_lock); -hugetlb_put_quota (inode->i_mapping, (chg - freed)); +hugetlb_put_quota (HUGETLBFS_SB (inode->i_mapping->host->i_sb), (chg - freed));hugetlb_acct_memory (h, -(chg - freed)); }Quoted 65 lines · Hugh Dickins
Quoted 19 lines · David Gibson
Quoted 5 lines · Hugh Dickins
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Quoted 8 lines
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
That does sound closer to a reaonable approach, but my guess is that it will suck you into a world of superblock mutexes and semaphores, which you cannot take at
free_huge_page()time.It might be necessary to hang your own tiny structure off the superblock, with one refcount for the superblock, and one for each hugepage attached, you freeing that structure when the count goes down to zero from either direction.
Whatever you do needs testing with lockdep and atomic sleep checks.
I do dislike tying these separate levels together in such an unusual way, but it is a difficult problem and I don't know of an easy answer. Maybe we'll need to find a better answer for other reasons, it does come up from time to time e.g. recent race between evicting inode and nrpages going down to 0.
You might care to take a look at how tmpfs (
mm/shmem.c) deals with the equivalent issue there (sbinfo->used_blocks). But I expect you to conclude that hugetlbfs cannot afford the kind of approximations that tmpfs can afford.Although I think tmpfs is more correct, to be associating the count with pagecache (so the count goes down as soon as a page is truncated or evicted from pagecache), your fewer and huger pages, and reservation conventions, may well demand that the count stays up until the page is actually freed back to hugepool. And let's not pretend that what tmpfs does is wonderful: the strange
shmem_recalc_inode()tries its best to notice when memory pressure has freed clean pages, but it never looks beyond the inode being accessed at the times it's called. Not at all satisfactory, but not actually an issue in practice, since we stopped allocating pages for simple reads from sparse file. I did want to convert tmpfs to use ->freepage(), but couldn't manage it without stable mapping - same problem as you have.Hugh
Signature · 4 lines
David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
RFC PATCH 1/1 mm/hugepages: Fix race between hugetlbfs umount and quota update.
Discussion was titled: Fix refcounting in hugetlbfs quota handling
This patch fixes a race between the umount of a hugetlbfs filesystem, and quota updates in that filesystem, which can result in the update of the filesystem quota record, after the record structure has been freed.
Rather than an address-space struct pointer, it puts a hugetlbfs_sb_info struct pointer into page_private of the page struct. A reference count and an active bit are added to the hugetlbfs_sb_info struct; the reference count is increased by hugetlb_get_quota and decreased by hugetlb_put_quota. When hugetlbfs is unmounted, it frees the hugetlbfs_sb_info struct, but only if the reference count is zero, otherwise it clears the active bit. The last hugetlb_put_quota then frees the hugetlbfs_sb_info struct.
---
fs/hugetlbfs/inode.c | 40 ++++++++++++++++++++++++++-------------- include/linux/hugetlb.h | 9 +++++++-- mm/hugetlb.c | 22 +++++++++++----------- 3 files changed, 44 insertions(+), 27 deletions(-)
fs/hugetlbfs/inode.c +7 -1
@@ -615,8 +615,12 @@ static void hugetlbfs_put_super(struct sstruct hugetlbfs_sb_info *sbi =HUGETLBFS_SB (sb);if (sbi) { + sbi->active =HPAGE_INACTIVE ; sb->s_fs_info =NULL ; -kfree (sbi); + +/*Free only if used quota is zero. */ +if (sbi->used_blocks ==0 ) +kfree (sbi); } }
@@ -851,6 +855,8 @@ hugetlbfs_fill_super(struct super_block sbinfo->free_blocks = config.nr_blocks; sbinfo->max_inodes = config.nr_inodes; sbinfo->free_inodes = config.nr_inodes; + sbinfo->used_blocks =0 ; + sbinfo->active =HPAGE_ACTIVE ; sb->s_maxbytes =MAX_LFS_FILESIZE ; sb->s_blocksize =huge_page_size (config.hstate); sb->s_blocksize_bits =huge_page_shift (config.hstate);
@@ -874,30 +880,36 @@ out_free:
return -ENOMEM;
}
-int hugetlb_get_quota(struct address_space *mapping, long delta) +int hugetlb_get_quota(struct hugetlbfs_sb_info *sbinfo, long delta)
{
int ret = 0;
- struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(mapping->host->i_sb);
- if (sbinfo->free_blocks > -1) {
- spin_lock(&sbinfo->stat_lock);
- if (sbinfo->free_blocks - delta >= 0)
+ spin_lock(&sbinfo->stat_lock);
+ if ((sbinfo->free_blocks == -1) || (sbinfo->free_blocks - delta >= 0)) {
+ if (sbinfo->free_blocks != -1)
sbinfo->free_blocks -= delta;
- else
- ret = -ENOMEM;
- spin_unlock(&sbinfo->stat_lock);
+ sbinfo->used_blocks += delta;
+ sbinfo->active = HPAGE_ACTIVE;
+ } else {
+ ret = -ENOMEM;
}
+ spin_unlock(&sbinfo->stat_lock);
return ret;
}
-void hugetlb_put_quota(struct address_space *mapping, long delta) +void hugetlb_put_quota(struct hugetlbfs_sb_info *sbinfo, long delta)
{
- struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(mapping->host->i_sb);
-
- if (sbinfo->free_blocks > -1) {
- spin_lock(&sbinfo->stat_lock);
+ spin_lock(&sbinfo->stat_lock);
+ if (sbinfo->free_blocks > -1)
sbinfo->free_blocks += delta;
+ sbinfo->used_blocks -= delta;
+ /* If hugetlbfs_put_super couldn't free sbinfo due to
+ * an outstanding quota reference, free it now. */
+ if ((sbinfo->used_blocks == 0) && (sbinfo->active == HPAGE_INACTIVE)) {
+ spin_unlock(&sbinfo->stat_lock);
+ kfree(sbinfo);
+ } else {
spin_unlock(&sbinfo->stat_lock);
}
}
include/linux/hugetlb.h +5 -0
@@ -142,11 +142,16 @@ struct hugetlbfs_config {struct hstate *hstate; }; +#define HPAGE_INACTIVE 0 +#define HPAGE_ACTIVE 1 +struct hugetlbfs_sb_info {long max_blocks;/* blocks allowed */ long free_blocks;/* blocks free */ long max_inodes;/* inodes allowed */ long free_inodes;/* inodes free */ +long used_blocks;/* blocks used */ +long active;/* active bit */ spinlock_t stat_lock;struct hstate *hstate; };
@@ -171,8 +176,8 @@ extern const struct file_operations huge
extern const struct vm_operations_struct hugetlb_vm_ops;
struct file *hugetlb_file_setup(const char *name, size_t size, vm_flags_t acct,
struct user_struct **user, int creat_flags);
-int hugetlb_get_quota(struct address_space *mapping, long delta); -void hugetlb_put_quota(struct address_space *mapping, long delta); +int hugetlb_get_quota(struct hugetlbfs_sb_info *sbinfo, long delta); +void hugetlb_put_quota(struct hugetlbfs_sb_info *sbinfo, long delta);
static inline int is_file_hugepages(struct file *file) {
mm/hugetlb.c +2 -2
@@ -533,9 +533,9 @@ static void free_huge_page(struct page **/ struct hstate *h =page_hstate (page);int nid =page_to_nid (page); -struct address_space *mapping; +struct hugetlbfs_sb_info *sbinfo; - mapping = (struct address_space *)page_private (page); + sbinfo = (struct hugetlbfs_sb_info *)page_private (page);set_page_private (page,0 ); page->mapping =NULL ;BUG_ON (page_count (page));
@@ -551,8 +551,8 @@ static void free_huge_page(struct page *
enqueue_huge_page(h, page);
}
spin_unlock(&hugetlb_lock);
- if (mapping)
- hugetlb_put_quota(mapping, 1);
+ if (sbinfo)
+ hugetlb_put_quota(sbinfo, 1);
}
static void prep_new_huge_page(struct hstate *h, struct page *page, int nid)
@@ -1035,7 +1035,7 @@ static struct page *alloc_huge_page(stru
if (chg < 0)
return ERR_PTR(-VM_FAULT_OOM);
if (chg)
- if (hugetlb_get_quota(inode->i_mapping, chg))
+ if (hugetlb_get_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg))
return ERR_PTR(-VM_FAULT_SIGBUS);
spin_lock(&hugetlb_lock);
@@ -1045,12 +1045,12 @@ static struct page *alloc_huge_page(stru
if (!page) {
page = alloc_buddy_huge_page(h, NUMA_NO_NODE); if (!page) {
- hugetlb_put_quota(inode->i_mapping, chg);
+ hugetlb_put_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg);
return ERR_PTR(-VM_FAULT_SIGBUS);
}
}
- set_page_private(page, (unsigned long) mapping);
+ set_page_private(page, (unsigned long)HUGETLBFS_SB(inode->i_mapping->host->i_sb));
vma_commit_reservation(h, vma, addr);
@@ -2086,7 +2086,7 @@ static void hugetlb_vm_op_close(struct v
if (reserve) {
hugetlb_acct_memory(h, -reserve);
- hugetlb_put_quota(vma->vm_file->f_mapping, reserve);
+ hugetlb_put_quota(HUGETLBFS_SB(vma->vm_file->f_mapping->host->i_sb), reserve);
}
}
}
@@ -2884,7 +2884,7 @@ int hugetlb_reserve_pages(struct inode *
return chg;
/* There must be enough filesystem quota for the mapping */
- if (hugetlb_get_quota(inode->i_mapping, chg))
+ if (hugetlb_get_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg))
return -ENOSPC;
/*
@@ -2893,7 +2893,7 @@ int hugetlb_reserve_pages(struct inode *
*/
ret = hugetlb_acct_memory(h, chg);
if (ret < 0) {
- hugetlb_put_quota(inode->i_mapping, chg);
+ hugetlb_put_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), chg);
return ret;
}
@@ -2922,7 +2922,7 @@ void hugetlb_unreserve_pages(struct inod
inode->i_blocks -= (blocks_per_huge_page(h) * freed); spin_unlock(&inode->i_lock);
- hugetlb_put_quota(inode->i_mapping, (chg - freed));
+ hugetlb_put_quota(HUGETLBFS_SB(inode->i_mapping->host->i_sb), (chg - freed));
hugetlb_acct_memory(h, -(chg - freed));
}
Quoted 19 lines · David Gibson
Quoted 10 lines · Andrew Barry
I've been doing something similar to this last proposal. I put a
hugetlbfs_sb_infopointer intopage_private, and dropped a reference counter and an active/inactive bit into thehugetlbfs_sb_infostruct. At Umount time, the sbinfo is freed, only if the reference count is zero. Otherwise, the lastput_quotafrees the sbinfo structure. This fixed the race we were seeing between umount and aput_quotafrom an rdma transaction. I just gave it a cursory test on a 3.0 kernel; it has seen quite a lot more testing on a 2.6.32-derived kernel, with no more hits of the umount race.Does this address the problems you were thinking about?
Ah, this looks much better than my patch. And the fact that you've seen your race demonstrates clearly that this isn't just a kvm problem. I hope we can push this upstream very soon - what can I do to help?
Quoted 2 lines
-Andrew Barry
It is not a unique KVM problem. We saw the race while doing large async rDMA in our network driver, but I can imagine it happening with a slow NFS server, or other DMA that could complete after umount.
What I need, in order to push this upstream, is:
1. For you to light a fire under my feet to get this going. (done)
2. For you all to review the patch and let me know if I've missed anything. Patch RFC coming ASAP.
-Andrew Barry
Quoted 94 lines · David Gibson
Quoted 10 lines · Andrew Barry
I've been doing something similar to this last proposal. I put a
hugetlbfs_sb_infopointer intopage_private, and dropped a reference counter and an active/inactive bit into thehugetlbfs_sb_infostruct. At Umount time, the sbinfo is freed, only if the reference count is zero. Otherwise, the lastput_quotafrees the sbinfo structure. This fixed the race we were seeing between umount and aput_quotafrom an rdma transaction. I just gave it a cursory test on a 3.0 kernel; it has seen quite a lot more testing on a 2.6.32-derived kernel, with no more hits of the umount race.Does this address the problems you were thinking about?
Ah, this looks much better than my patch. And the fact that you've seen your race demonstrates clearly that this isn't just a kvm problem. I hope we can push this upstream very soon - what can I do to help?
Quoted 4 lines
-Andrew Barry
Quoted 67 lines
Quoted 65 lines · Hugh Dickins
Quoted 19 lines · David Gibson
Quoted 5 lines · Hugh Dickins
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Quoted 8 lines
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
That does sound closer to a reaonable approach, but my guess is that it will suck you into a world of superblock mutexes and semaphores, which you cannot take at
free_huge_page()time.It might be necessary to hang your own tiny structure off the superblock, with one refcount for the superblock, and one for each hugepage attached, you freeing that structure when the count goes down to zero from either direction.
Whatever you do needs testing with lockdep and atomic sleep checks.
I do dislike tying these separate levels together in such an unusual way, but it is a difficult problem and I don't know of an easy answer. Maybe we'll need to find a better answer for other reasons, it does come up from time to time e.g. recent race between evicting inode and nrpages going down to 0.
You might care to take a look at how tmpfs (
mm/shmem.c) deals with the equivalent issue there (sbinfo->used_blocks). But I expect you to conclude that hugetlbfs cannot afford the kind of approximations that tmpfs can afford.Although I think tmpfs is more correct, to be associating the count with pagecache (so the count goes down as soon as a page is truncated or evicted from pagecache), your fewer and huger pages, and reservation conventions, may well demand that the count stays up until the page is actually freed back to hugepool. And let's not pretend that what tmpfs does is wonderful: the strange
shmem_recalc_inode()tries its best to notice when memory pressure has freed clean pages, but it never looks beyond the inode being accessed at the times it's called. Not at all satisfactory, but not actually an issue in practice, since we stopped allocating pages for simple reads from sparse file. I did want to convert tmpfs to use ->freepage(), but couldn't manage it without stable mapping - same problem as you have.Hugh
--
David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_!
It is not a unique KVM problem. We saw the race while doing large async rDMA in our network driver, but I can imagine it happening with a slow NFS server, or other DMA that could complete after umount.
What I need, in order to push this upstream, is:
1. For you to light a fire under my feet to get this going. (done)
2. For you all to review the patch and let me know if I've missed anything. Patch RFC coming ASAP.
-Andrew Barry
Quoted 94 lines · David Gibson
Quoted 10 lines · Andrew Barry
I've been doing something similar to this last proposal. I put a
hugetlbfs_sb_infopointer intopage_private, and dropped a reference counter and an active/inactive bit into thehugetlbfs_sb_infostruct. At Umount time, the sbinfo is freed, only if the reference count is zero. Otherwise, the lastput_quotafrees the sbinfo structure. This fixed the race we were seeing between umount and aput_quotafrom an rdma transaction. I just gave it a cursory test on a 3.0 kernel; it has seen quite a lot more testing on a 2.6.32-derived kernel, with no more hits of the umount race.Does this address the problems you were thinking about?
Ah, this looks much better than my patch. And the fact that you've seen your race demonstrates clearly that this isn't just a kvm problem. I hope we can push this upstream very soon - what can I do to help?
Quoted 4 lines
-Andrew Barry
Quoted 67 lines
Quoted 65 lines · Hugh Dickins
Quoted 19 lines · David Gibson
Quoted 5 lines · Hugh Dickins
Setting that aside, I think this thing of grabbing a reference to inode for each page just does not work as you wish: when we unlink an inode, all its pages should be freed; but because they are themselves holding references to the inode, it and its pages stick around forever.
Ugh, yes. You're absolutely right. That circular reference will mess everything up. Thinking it through and testing fail.
Quoted 8 lines
A quick experiment with your patch versus without confirmed that:
meminfoHugePages_Freestayed down with your patch, but went back toHugePages_Totalwithout it. Please check, perhaps I'm just mistaken.Sorry, I've not looked into what a constructive alternative might be; and it's not the first time we've had this difficulty - it came up last year when the ->freepage function was added, that the inode may be gone by the time ->freepage(page) is called.
Quoted 8 lines
Ok, so. In fact the quota functions we call at free time only need the super block, not the inode per se. If we put a superblock pointer instead of an inode pointer in page private, and refcounted that, I think that should remove the circular ref. The only reason I didn't do it before is that the superblock refcounting functions didn't seem to be globally visible in an obvious way.
Does that sound like a reasonable approach?
That does sound closer to a reaonable approach, but my guess is that it will suck you into a world of superblock mutexes and semaphores, which you cannot take at
free_huge_page()time.It might be necessary to hang your own tiny structure off the superblock, with one refcount for the superblock, and one for each hugepage attached, you freeing that structure when the count goes down to zero from either direction.
Whatever you do needs testing with lockdep and atomic sleep checks.
I do dislike tying these separate levels together in such an unusual way, but it is a difficult problem and I don't know of an easy answer. Maybe we'll need to find a better answer for other reasons, it does come up from time to time e.g. recent race between evicting inode and nrpages going down to 0.
You might care to take a look at how tmpfs (
mm/shmem.c) deals with the equivalent issue there (sbinfo->used_blocks). But I expect you to conclude that hugetlbfs cannot afford the kind of approximations that tmpfs can afford.Although I think tmpfs is more correct, to be associating the count with pagecache (so the count goes down as soon as a page is truncated or evicted from pagecache), your fewer and huger pages, and reservation conventions, may well demand that the count stays up until the page is actually freed back to hugepool. And let's not pretend that what tmpfs does is wonderful: the strange
shmem_recalc_inode()tries its best to notice when memory pressure has freed clean pages, but it never looks beyond the inode being accessed at the times it's called. Not at all satisfactory, but not actually an issue in practice, since we stopped allocating pages for simple reads from sparse file. I did want to convert tmpfs to use ->freepage(), but couldn't manage it without stable mapping - same problem as you have.Hugh
--
David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_!
Quoted 6 lines · David Gibson
Linus, please apply
hugetlbfs tracks the current usage of hugepages per hugetlbfs mountpoint. To correctly track this when hugepages are released, it must find the right hugetlbfs
super_blockfrom the struct page available infree_huge_page().
a superblock is not a mountpoint, it's a filesystem instance. You can happily have a single filesystem mounted at multiple mount points.
Quoted 7 lines
However, this usage is buggy, because nothing ensures that the
address_spaceis not freed before all the hugepages that belonged to it are. In practice that will usually be the case, but if extra page references have been taken by e.g. drivers or kvm doingget_user_pages()then the file, inode and address space may be destroyed before all the pages.
Quoted 6 lines
In addition, the quota functions use the mapping only to get the inode then the
super_block. However, most of the callers already have the inode anyway and have to get the mapping from there.This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages.
What's sthe point? The lifetime of inode->i_mapping is exactly the same as that of the inode, except for those few filesystem that use one from a different inode (and then for the whole lifetime of the inode), so I can't see how your patch will make a difference.
Quoted 5 lines
More
importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.
That seems like the real fix. And even if you'd still do the other bits it should be a separate patch/
Quoted 6 lines · David Gibson
Linus, please apply
hugetlbfs tracks the current usage of hugepages per hugetlbfs mountpoint. To correctly track this when hugepages are released, it must find the right hugetlbfs
super_blockfrom the struct page available infree_huge_page().
a superblock is not a mountpoint, it's a filesystem instance. You can happily have a single filesystem mounted at multiple mount points.
Quoted 7 lines
However, this usage is buggy, because nothing ensures that the
address_spaceis not freed before all the hugepages that belonged to it are. In practice that will usually be the case, but if extra page references have been taken by e.g. drivers or kvm doingget_user_pages()then the file, inode and address space may be destroyed before all the pages.
Quoted 6 lines
In addition, the quota functions use the mapping only to get the inode then the
super_block. However, most of the callers already have the inode anyway and have to get the mapping from there.This patch, therefore, stores a pointer to the inode instead of the
address_spacein the page private data for hugepages.
What's sthe point? The lifetime of inode->i_mapping is exactly the same as that of the inode, except for those few filesystem that use one from a different inode (and then for the whole lifetime of the inode), so I can't see how your patch will make a difference.
Quoted 5 lines
More
importantly it correctly adjusts the reference count on the inodes when they're added to the page private data. This ensures that the inode (and therefore the super block) will not be freed before we use it fromfree_huge_page.
That seems like the real fix. And even if you'd still do the other bits it should be a separate patch/