From: Andrew Morton <akpm@linux-foundation.org>
To: Nick Piggin <npiggin@suse.de>
Cc: Linux Kernel <linux-kernel@vger.kernel.org>,
Linux Filesystems <linux-fsdevel@vger.kernel.org>,
Linux Memory Management <linux-mm@kvack.org>
Subject: Re: [patch 9/9] mm: fix pagecache write deadlocks
Date: Fri, 2 Feb 2007 15:53:11 -0800 [thread overview]
Message-ID: <20070202155311.e4f57985.akpm@linux-foundation.org> (raw)
In-Reply-To: <20070129082030.23584.72376.sendpatchset@linux.site>
On Mon, 29 Jan 2007 11:33:03 +0100 (CET)
Nick Piggin <npiggin@suse.de> wrote:
> Modify the core write() code so that it won't take a pagefault while holding a
> lock on the pagecache page. There are a number of different deadlocks possible
> if we try to do such a thing:
>
> 1. generic_buffered_write
> 2. lock_page
> 3. prepare_write
> 4. unlock_page+vmtruncate
> 5. copy_from_user
> 6. mmap_sem(r)
> 7. handle_mm_fault
> 8. lock_page (filemap_nopage)
> 9. commit_write
> 10. unlock_page
>
> a. sys_munmap / sys_mlock / others
> b. mmap_sem(w)
> c. make_pages_present
> d. get_user_pages
> e. handle_mm_fault
> f. lock_page (filemap_nopage)
>
> 2,8 - recursive deadlock if page is same
> 2,8;2,8 - ABBA deadlock is page is different
> 2,6;b,f - ABBA deadlock if page is same
>
> The solution is as follows:
> 1. If we find the destination page is uptodate, continue as normal, but use
> atomic usercopies which do not take pagefaults and do not zero the uncopied
> tail of the destination. The destination is already uptodate, so we can
> commit_write the full length even if there was a partial copy: it does not
> matter that the tail was not modified, because if it is dirtied and written
> back to disk it will not cause any problems (uptodate *means* that the
> destination page is as new or newer than the copy on disk).
>
> 1a. The above requires that fault_in_pages_readable correctly returns access
> information, because atomic usercopies cannot distinguish between
> non-present pages in a readable mapping, from lack of a readable mapping.
>
> 2. If we find the destination page is non uptodate, unlock it (this could be
> made slightly more optimal), then find and pin the source page with
> get_user_pages. Relock the destination page and continue with the copy.
> However, instead of a usercopy (which might take a fault), copy the data
> via the kernel address space.
>
Oh what a mess we're making :(
Unfortunately, write() into a non-uptodate page is very much the common
case. We've always tried to avoid doing a pte-walk in the write() path to
fix this bug. Careful performance testing is needed here so we can assess
the impact. For threaded applications, simply the taking of mmap_sem might
be the biggest problem.
And I can't think of any tricks we can play to avoid doing the pte-walk in
most cases. For example, we don't yet have a page to run page_mapped()
against.
> break;
> }
>
> + /*
> + * non-uptodate pages cannot cope with short copies, and we
> + * cannot take a pagefault with the destination page locked.
> + * So pin the source page to copy it.
> + */
> + if (!PageUptodate(page)) {
> + unlock_page(page);
> +
> + bytes = min(bytes, PAGE_CACHE_SIZE -
> + ((unsigned long)buf & ~PAGE_CACHE_MASK));
> +
> + /*
> + * Cannot get_user_pages with a page locked for the
> + * same reason as we can't take a page fault with a
> + * page locked (as explained below).
> + */
> + down_read(¤t->mm->mmap_sem);
> + status = get_user_pages(current, current->mm,
> + (unsigned long)buf & PAGE_CACHE_MASK, 1,
> + 0, 0, &src_page, NULL);
> + up_read(¤t->mm->mmap_sem);
> + if (status != 1) {
> + page_cache_release(page);
> + break;
> + }
> +
> + lock_page(page);
> + if (!page->mapping) {
Hopefully this can't happen? If it can, who went and took our page off the
mapping? Reclaim? The elevated page_count will prevent that?
> + unlock_page(page);
> + page_cache_release(page);
> + page_cache_release(src_page);
> + continue;
> + }
> +
> + }
> +
> status = a_ops->prepare_write(file, page, offset, offset+bytes);
> if (unlikely(status))
> goto fs_write_aop_error;
>
> - copied = filemap_copy_from_user(page, offset,
> + if (!src_page) {
> + /*
> + * Must not enter the pagefault handler here, because
> + * we hold the page lock, so we might recursively
> + * deadlock on the same lock, or get an ABBA deadlock
> + * against a different lock, or against the mmap_sem
> + * (which nests outside the page lock). So increment
> + * preempt count, and use _atomic usercopies.
> + *
> + * The page is uptodate so we are OK to encounter a
> + * short copy: if unmodified parts of the page are
> + * marked dirty and written out to disk, it doesn't
> + * really matter.
> + */
> + pagefault_disable();
> + copied = filemap_copy_from_user_atomic(page, offset,
> cur_iov, nr_segs, iov_offset, bytes);
> + pagefault_enable();
> + } else {
> + char *src, *dst;
> + src = kmap(src_page);
> + dst = kmap(page);
> + memcpy(dst + offset,
> + src + ((unsigned long)buf & ~PAGE_CACHE_MASK),
> + bytes);
> + kunmap(page);
> + kunmap(src_page);
> + copied = bytes;
> + }
As you say, we don't want to do this: taking two kmap()s at the same time
leaves us vulnerable to kmap() deadlocks: we deadlock when there are 512
tasks (LAST_PKMAP) each holding one kmap, and all waiting for someone else
to give one back.
WARNING: multiple messages have this Message-ID (diff)
From: Andrew Morton <akpm@linux-foundation.org>
To: Nick Piggin <npiggin@suse.de>
Cc: Linux Kernel <linux-kernel@vger.kernel.org>,
Linux Filesystems <linux-fsdevel@vger.kernel.org>,
Linux Memory Management <linux-mm@kvack.org>
Subject: Re: [patch 9/9] mm: fix pagecache write deadlocks
Date: Fri, 2 Feb 2007 15:53:11 -0800 [thread overview]
Message-ID: <20070202155311.e4f57985.akpm@linux-foundation.org> (raw)
In-Reply-To: <20070129082030.23584.72376.sendpatchset@linux.site>
On Mon, 29 Jan 2007 11:33:03 +0100 (CET)
Nick Piggin <npiggin@suse.de> wrote:
> Modify the core write() code so that it won't take a pagefault while holding a
> lock on the pagecache page. There are a number of different deadlocks possible
> if we try to do such a thing:
>
> 1. generic_buffered_write
> 2. lock_page
> 3. prepare_write
> 4. unlock_page+vmtruncate
> 5. copy_from_user
> 6. mmap_sem(r)
> 7. handle_mm_fault
> 8. lock_page (filemap_nopage)
> 9. commit_write
> 10. unlock_page
>
> a. sys_munmap / sys_mlock / others
> b. mmap_sem(w)
> c. make_pages_present
> d. get_user_pages
> e. handle_mm_fault
> f. lock_page (filemap_nopage)
>
> 2,8 - recursive deadlock if page is same
> 2,8;2,8 - ABBA deadlock is page is different
> 2,6;b,f - ABBA deadlock if page is same
>
> The solution is as follows:
> 1. If we find the destination page is uptodate, continue as normal, but use
> atomic usercopies which do not take pagefaults and do not zero the uncopied
> tail of the destination. The destination is already uptodate, so we can
> commit_write the full length even if there was a partial copy: it does not
> matter that the tail was not modified, because if it is dirtied and written
> back to disk it will not cause any problems (uptodate *means* that the
> destination page is as new or newer than the copy on disk).
>
> 1a. The above requires that fault_in_pages_readable correctly returns access
> information, because atomic usercopies cannot distinguish between
> non-present pages in a readable mapping, from lack of a readable mapping.
>
> 2. If we find the destination page is non uptodate, unlock it (this could be
> made slightly more optimal), then find and pin the source page with
> get_user_pages. Relock the destination page and continue with the copy.
> However, instead of a usercopy (which might take a fault), copy the data
> via the kernel address space.
>
Oh what a mess we're making :(
Unfortunately, write() into a non-uptodate page is very much the common
case. We've always tried to avoid doing a pte-walk in the write() path to
fix this bug. Careful performance testing is needed here so we can assess
the impact. For threaded applications, simply the taking of mmap_sem might
be the biggest problem.
And I can't think of any tricks we can play to avoid doing the pte-walk in
most cases. For example, we don't yet have a page to run page_mapped()
against.
> break;
> }
>
> + /*
> + * non-uptodate pages cannot cope with short copies, and we
> + * cannot take a pagefault with the destination page locked.
> + * So pin the source page to copy it.
> + */
> + if (!PageUptodate(page)) {
> + unlock_page(page);
> +
> + bytes = min(bytes, PAGE_CACHE_SIZE -
> + ((unsigned long)buf & ~PAGE_CACHE_MASK));
> +
> + /*
> + * Cannot get_user_pages with a page locked for the
> + * same reason as we can't take a page fault with a
> + * page locked (as explained below).
> + */
> + down_read(¤t->mm->mmap_sem);
> + status = get_user_pages(current, current->mm,
> + (unsigned long)buf & PAGE_CACHE_MASK, 1,
> + 0, 0, &src_page, NULL);
> + up_read(¤t->mm->mmap_sem);
> + if (status != 1) {
> + page_cache_release(page);
> + break;
> + }
> +
> + lock_page(page);
> + if (!page->mapping) {
Hopefully this can't happen? If it can, who went and took our page off the
mapping? Reclaim? The elevated page_count will prevent that?
> + unlock_page(page);
> + page_cache_release(page);
> + page_cache_release(src_page);
> + continue;
> + }
> +
> + }
> +
> status = a_ops->prepare_write(file, page, offset, offset+bytes);
> if (unlikely(status))
> goto fs_write_aop_error;
>
> - copied = filemap_copy_from_user(page, offset,
> + if (!src_page) {
> + /*
> + * Must not enter the pagefault handler here, because
> + * we hold the page lock, so we might recursively
> + * deadlock on the same lock, or get an ABBA deadlock
> + * against a different lock, or against the mmap_sem
> + * (which nests outside the page lock). So increment
> + * preempt count, and use _atomic usercopies.
> + *
> + * The page is uptodate so we are OK to encounter a
> + * short copy: if unmodified parts of the page are
> + * marked dirty and written out to disk, it doesn't
> + * really matter.
> + */
> + pagefault_disable();
> + copied = filemap_copy_from_user_atomic(page, offset,
> cur_iov, nr_segs, iov_offset, bytes);
> + pagefault_enable();
> + } else {
> + char *src, *dst;
> + src = kmap(src_page);
> + dst = kmap(page);
> + memcpy(dst + offset,
> + src + ((unsigned long)buf & ~PAGE_CACHE_MASK),
> + bytes);
> + kunmap(page);
> + kunmap(src_page);
> + copied = bytes;
> + }
As you say, we don't want to do this: taking two kmap()s at the same time
leaves us vulnerable to kmap() deadlocks: we deadlock when there are 512
tasks (LAST_PKMAP) each holding one kmap, and all waiting for someone else
to give one back.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
next prev parent reply other threads:[~2007-02-02 23:53 UTC|newest]
Thread overview: 105+ messages / expand[flat|nested] mbox.gz Atom feed top
2007-01-29 10:31 [patch 0/9] buffered write deadlock fix Nick Piggin
2007-01-29 10:31 ` Nick Piggin
2007-01-29 10:31 ` [patch 1/9] fs: libfs buffered write leak fix Nick Piggin
2007-01-29 10:31 ` Nick Piggin
2007-02-02 23:52 ` Andrew Morton
2007-02-02 23:52 ` Andrew Morton
2007-02-03 1:33 ` Nick Piggin
2007-02-03 1:33 ` Nick Piggin
2007-02-03 1:58 ` Andrew Morton
2007-02-03 1:58 ` Andrew Morton
2007-02-03 2:09 ` Nick Piggin
2007-02-03 2:09 ` Nick Piggin
2007-02-03 2:19 ` Andrew Morton
2007-02-03 2:19 ` Andrew Morton
2007-02-03 2:28 ` Nick Piggin
2007-02-03 2:28 ` Nick Piggin
2007-02-03 17:49 ` Jörn Engel
2007-02-03 17:49 ` Jörn Engel
2007-02-04 3:55 ` Nick Piggin
2007-02-04 3:55 ` Nick Piggin
2007-02-04 3:55 ` Nick Piggin
2007-01-29 10:31 ` [patch 2/9] mm: revert "generic_file_buffered_write(): handle zero length iovec segments" Nick Piggin
2007-01-29 10:31 ` Nick Piggin, Andrew Morton
2007-01-29 10:32 ` [patch 3/9] mm: revert "generic_file_buffered_write(): deadlock on vectored write" Nick Piggin
2007-01-29 10:32 ` Nick Piggin, Andrew Morton
2007-01-29 10:32 ` [patch 4/9] mm: generic_file_buffered_write cleanup Nick Piggin
2007-01-29 10:32 ` Nick Piggin, Andrew Morton
2007-01-29 10:32 ` [patch 5/9] mm: debug write deadlocks Nick Piggin
2007-01-29 10:32 ` Nick Piggin
2007-01-29 10:32 ` [patch 6/9] mm: be sure to trim blocks Nick Piggin
2007-01-29 10:32 ` Nick Piggin
2007-01-29 10:32 ` [patch 7/9] mm: cleanup pagecache insertion operations Nick Piggin
2007-01-29 10:32 ` Nick Piggin
2007-01-29 10:32 ` [patch 8/9] mm: generic_file_buffered_write iovec cleanup Nick Piggin
2007-01-29 10:32 ` Nick Piggin
2007-01-29 10:33 ` [patch 9/9] mm: fix pagecache write deadlocks Nick Piggin
2007-01-29 10:33 ` Nick Piggin
2007-01-29 11:11 ` Nick Piggin
2007-01-29 11:11 ` Nick Piggin
2007-02-02 23:53 ` Andrew Morton [this message]
2007-02-02 23:53 ` Andrew Morton
2007-02-03 1:38 ` Nick Piggin
2007-02-03 1:38 ` Nick Piggin
2007-01-30 20:55 ` [patch 0/9] buffered write deadlock fix Andrew Morton
2007-01-30 20:55 ` Andrew Morton
2007-01-30 23:21 ` Andrew Morton
2007-01-30 23:21 ` Andrew Morton
2007-01-31 1:31 ` Nick Piggin
2007-01-31 1:31 ` Nick Piggin
2007-01-31 0:32 ` Nick Piggin
2007-01-31 0:32 ` Nick Piggin
2007-02-02 23:52 ` Andrew Morton
2007-02-02 23:52 ` Andrew Morton
2007-02-03 1:22 ` Nick Piggin
2007-02-03 1:22 ` Nick Piggin
2007-02-03 6:43 ` Suparna Bhattacharya
2007-02-03 6:43 ` Suparna Bhattacharya
2007-02-03 15:31 ` Fengguang Wu
2007-02-03 15:31 ` Fengguang Wu
2007-02-03 15:31 ` Fengguang Wu
-- strict thread matches above, loose matches on Subject: below --
2007-02-04 8:49 Nick Piggin
2007-02-04 8:51 ` [patch 9/9] mm: fix pagecache write deadlocks Nick Piggin
2007-02-04 8:51 ` Nick Piggin
2007-02-04 9:44 ` Andrew Morton
2007-02-04 9:44 ` Andrew Morton
2007-02-04 10:15 ` Nick Piggin
2007-02-04 10:15 ` Nick Piggin
2007-02-04 10:26 ` Christoph Hellwig
2007-02-04 10:26 ` Christoph Hellwig
2007-02-04 10:30 ` Andrew Morton
2007-02-04 10:30 ` Andrew Morton
2007-02-04 10:46 ` Nick Piggin
2007-02-04 10:46 ` Nick Piggin
2007-02-04 10:50 ` Nick Piggin
2007-02-04 10:50 ` Nick Piggin
2007-02-04 10:56 ` Andrew Morton
2007-02-04 10:56 ` Andrew Morton
2007-02-04 11:03 ` Nick Piggin
2007-02-04 11:03 ` Nick Piggin
2007-02-04 11:15 ` Andrew Morton
2007-02-04 11:15 ` Andrew Morton
2007-02-04 15:10 ` Nick Piggin
2007-02-04 15:10 ` Nick Piggin
2007-02-04 18:36 ` Andrew Morton
2007-02-04 18:36 ` Andrew Morton
2007-02-06 2:25 ` Nick Piggin
2007-02-06 2:25 ` Nick Piggin
2007-02-06 4:41 ` Nick Piggin
2007-02-06 4:41 ` Nick Piggin
2007-02-06 5:30 ` Andrew Morton
2007-02-06 5:30 ` Andrew Morton
2007-02-06 5:49 ` Nick Piggin
2007-02-06 5:49 ` Nick Piggin
2007-02-06 5:53 ` Nick Piggin
2007-02-06 5:53 ` Nick Piggin
2007-02-04 10:59 ` Anton Altaparmakov
2007-02-04 10:59 ` Anton Altaparmakov
2007-02-04 11:10 ` Andrew Morton
2007-02-04 11:10 ` Andrew Morton
2007-02-04 11:22 ` Nick Piggin
2007-02-04 11:22 ` Nick Piggin
2007-02-04 17:40 ` Anton Altaparmakov
2007-02-04 17:40 ` Anton Altaparmakov
2007-02-06 2:09 ` Nick Piggin
2007-02-06 2:09 ` Nick Piggin
2007-02-06 13:13 ` Anton Altaparmakov
2007-02-06 13:13 ` Anton Altaparmakov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20070202155311.e4f57985.akpm@linux-foundation.org \
--to=akpm@linux-foundation.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=npiggin@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.