diff options
| author | Andrew Morton <akpm@osdl.org> | 2006-09-26 02:32:44 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-09-26 11:48:58 -0400 |
| commit | 546e0d271941dd1ff6961e2a1f7eac75f1fc277e (patch) | |
| tree | 60c74a9598f7cb4622c1b6acd25df5df67284353 | |
| parent | 8c002494b55119a3fd1dddee83b4fb75cfda47e5 (diff) | |
[PATCH] swsusp: read speedup
Implement async reads for swsusp resuming.
Crufty old PIII testbox:
15.7 MB/s -> 20.3 MB/s
Sony Vaio:
14.6 MB/s -> 33.3 MB/s
I didn't implement the post-resume bio_set_pages_dirty(). I don't really
understand why resume needs to run set_page_dirty() against these pages.
It might be a worry that this code modifies PG_Uptodate, PG_Error and
PG_Locked against the image pages. Can this possibly affect the resumed-into
kernel? Hopefully not, if we're atomically restoring its mem_map?
Cc: Pavel Machek <pavel@ucw.cz>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Cc: Jens Axboe <axboe@suse.de>
Cc: Laurent Riffard <laurent.riffard@free.fr>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
| -rw-r--r-- | include/linux/swap.h | 1 | ||||
| -rw-r--r-- | kernel/power/power.h | 1 | ||||
| -rw-r--r-- | kernel/power/snapshot.c | 11 | ||||
| -rw-r--r-- | kernel/power/swap.c | 138 | ||||
| -rw-r--r-- | mm/page_io.c | 2 |
5 files changed, 81 insertions, 72 deletions
diff --git a/include/linux/swap.h b/include/linux/swap.h index 3d434cbffe26..e7c36ba2a2db 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h | |||
| @@ -220,6 +220,7 @@ extern int swap_readpage(struct file *, struct page *); | |||
| 220 | extern int swap_writepage(struct page *page, struct writeback_control *wbc); | 220 | extern int swap_writepage(struct page *page, struct writeback_control *wbc); |
| 221 | extern int rw_swap_page_sync(int rw, swp_entry_t entry, struct page *page, | 221 | extern int rw_swap_page_sync(int rw, swp_entry_t entry, struct page *page, |
| 222 | struct bio **bio_chain); | 222 | struct bio **bio_chain); |
| 223 | extern int end_swap_bio_read(struct bio *bio, unsigned int bytes_done, int err); | ||
| 223 | 224 | ||
| 224 | /* linux/mm/swap_state.c */ | 225 | /* linux/mm/swap_state.c */ |
| 225 | extern struct address_space swapper_space; | 226 | extern struct address_space swapper_space; |
diff --git a/kernel/power/power.h b/kernel/power/power.h index 57a792982fb9..59ce712f082d 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h | |||
| @@ -58,6 +58,7 @@ struct snapshot_handle { | |||
| 58 | struct pbe *pbe, *last_pbe; | 58 | struct pbe *pbe, *last_pbe; |
| 59 | void *buffer; | 59 | void *buffer; |
| 60 | unsigned int buf_offset; | 60 | unsigned int buf_offset; |
| 61 | int sync_read; | ||
| 61 | }; | 62 | }; |
| 62 | 63 | ||
| 63 | #define data_of(handle) ((handle).buffer + (handle).buf_offset) | 64 | #define data_of(handle) ((handle).buffer + (handle).buf_offset) |
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 75d4886e648e..591301ae8b7d 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c | |||
| @@ -314,7 +314,7 @@ static unsigned int unsafe_pages; | |||
| 314 | * and we count them using unsafe_pages | 314 | * and we count them using unsafe_pages |
| 315 | */ | 315 | */ |
| 316 | 316 | ||
| 317 | static inline void *alloc_image_page(gfp_t gfp_mask, int safe_needed) | 317 | static void *alloc_image_page(gfp_t gfp_mask, int safe_needed) |
| 318 | { | 318 | { |
| 319 | void *res; | 319 | void *res; |
| 320 | 320 | ||
| @@ -828,13 +828,16 @@ int snapshot_write_next(struct snapshot_handle *handle, size_t count) | |||
| 828 | } | 828 | } |
| 829 | if (!handle->offset) | 829 | if (!handle->offset) |
| 830 | handle->buffer = buffer; | 830 | handle->buffer = buffer; |
| 831 | handle->sync_read = 1; | ||
| 831 | if (handle->prev < handle->page) { | 832 | if (handle->prev < handle->page) { |
| 832 | if (!handle->prev) { | 833 | if (!handle->prev) { |
| 833 | error = load_header(handle, (struct swsusp_info *)buffer); | 834 | error = load_header(handle, |
| 835 | (struct swsusp_info *)buffer); | ||
| 834 | if (error) | 836 | if (error) |
| 835 | return error; | 837 | return error; |
| 836 | } else if (handle->prev <= nr_meta_pages) { | 838 | } else if (handle->prev <= nr_meta_pages) { |
| 837 | handle->pbe = unpack_orig_addresses(buffer, handle->pbe); | 839 | handle->pbe = unpack_orig_addresses(buffer, |
| 840 | handle->pbe); | ||
| 838 | if (!handle->pbe) { | 841 | if (!handle->pbe) { |
| 839 | error = prepare_image(handle); | 842 | error = prepare_image(handle); |
| 840 | if (error) | 843 | if (error) |
| @@ -842,10 +845,12 @@ int snapshot_write_next(struct snapshot_handle *handle, size_t count) | |||
| 842 | handle->pbe = pagedir_nosave; | 845 | handle->pbe = pagedir_nosave; |
| 843 | handle->last_pbe = NULL; | 846 | handle->last_pbe = NULL; |
| 844 | handle->buffer = get_buffer(handle); | 847 | handle->buffer = get_buffer(handle); |
| 848 | handle->sync_read = 0; | ||
| 845 | } | 849 | } |
| 846 | } else { | 850 | } else { |
| 847 | handle->pbe = handle->pbe->next; | 851 | handle->pbe = handle->pbe->next; |
| 848 | handle->buffer = get_buffer(handle); | 852 | handle->buffer = get_buffer(handle); |
| 853 | handle->sync_read = 0; | ||
| 849 | } | 854 | } |
| 850 | handle->prev = handle->page; | 855 | handle->prev = handle->page; |
| 851 | } | 856 | } |
diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 9ab989572164..8309d20b2563 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c | |||
| @@ -22,6 +22,7 @@ | |||
| 22 | #include <linux/device.h> | 22 | #include <linux/device.h> |
| 23 | #include <linux/buffer_head.h> | 23 | #include <linux/buffer_head.h> |
| 24 | #include <linux/bio.h> | 24 | #include <linux/bio.h> |
| 25 | #include <linux/blkdev.h> | ||
| 25 | #include <linux/swap.h> | 26 | #include <linux/swap.h> |
| 26 | #include <linux/swapops.h> | 27 | #include <linux/swapops.h> |
| 27 | #include <linux/pm.h> | 28 | #include <linux/pm.h> |
| @@ -214,6 +215,8 @@ static int wait_on_bio_chain(struct bio **bio_chain) | |||
| 214 | return 0; | 215 | return 0; |
| 215 | 216 | ||
| 216 | bio = *bio_chain; | 217 | bio = *bio_chain; |
| 218 | if (bio == NULL) | ||
| 219 | return 0; | ||
| 217 | while (bio) { | 220 | while (bio) { |
| 218 | struct page *page; | 221 | struct page *page; |
| 219 | 222 | ||
| @@ -349,7 +352,8 @@ int swsusp_write(void) | |||
| 349 | int error; | 352 | int error; |
| 350 | 353 | ||
| 351 | if ((error = swsusp_swap_check())) { | 354 | if ((error = swsusp_swap_check())) { |
| 352 | printk(KERN_ERR "swsusp: Cannot find swap device, try swapon -a.\n"); | 355 | printk(KERN_ERR "swsusp: Cannot find swap device, try " |
| 356 | "swapon -a.\n"); | ||
| 353 | return error; | 357 | return error; |
| 354 | } | 358 | } |
| 355 | memset(&snapshot, 0, sizeof(struct snapshot_handle)); | 359 | memset(&snapshot, 0, sizeof(struct snapshot_handle)); |
| @@ -381,27 +385,6 @@ int swsusp_write(void) | |||
| 381 | return error; | 385 | return error; |
| 382 | } | 386 | } |
| 383 | 387 | ||
| 384 | /* | ||
| 385 | * Using bio to read from swap. | ||
| 386 | * This code requires a bit more work than just using buffer heads | ||
| 387 | * but, it is the recommended way for 2.5/2.6. | ||
| 388 | * The following are to signal the beginning and end of I/O. Bios | ||
| 389 | * finish asynchronously, while we want them to happen synchronously. | ||
| 390 | * A simple atomic_t, and a wait loop take care of this problem. | ||
| 391 | */ | ||
| 392 | |||
| 393 | static atomic_t io_done = ATOMIC_INIT(0); | ||
| 394 | |||
| 395 | static int end_io(struct bio *bio, unsigned int num, int err) | ||
| 396 | { | ||
| 397 | if (!test_bit(BIO_UPTODATE, &bio->bi_flags)) { | ||
| 398 | printk(KERN_ERR "I/O error reading swsusp image.\n"); | ||
| 399 | return -EIO; | ||
| 400 | } | ||
| 401 | atomic_set(&io_done, 0); | ||
| 402 | return 0; | ||
| 403 | } | ||
| 404 | |||
| 405 | static struct block_device *resume_bdev; | 388 | static struct block_device *resume_bdev; |
| 406 | 389 | ||
| 407 | /** | 390 | /** |
| @@ -409,15 +392,15 @@ static struct block_device *resume_bdev; | |||
| 409 | * @rw: READ or WRITE. | 392 | * @rw: READ or WRITE. |
| 410 | * @off physical offset of page. | 393 | * @off physical offset of page. |
| 411 | * @page: page we're reading or writing. | 394 | * @page: page we're reading or writing. |
| 395 | * @bio_chain: list of pending biod (for async reading) | ||
| 412 | * | 396 | * |
| 413 | * Straight from the textbook - allocate and initialize the bio. | 397 | * Straight from the textbook - allocate and initialize the bio. |
| 414 | * If we're writing, make sure the page is marked as dirty. | 398 | * If we're reading, make sure the page is marked as dirty. |
| 415 | * Then submit it and wait. | 399 | * Then submit it and, if @bio_chain == NULL, wait. |
| 416 | */ | 400 | */ |
| 417 | 401 | static int submit(int rw, pgoff_t page_off, struct page *page, | |
| 418 | static int submit(int rw, pgoff_t page_off, void *page) | 402 | struct bio **bio_chain) |
| 419 | { | 403 | { |
| 420 | int error = 0; | ||
| 421 | struct bio *bio; | 404 | struct bio *bio; |
| 422 | 405 | ||
| 423 | bio = bio_alloc(GFP_ATOMIC, 1); | 406 | bio = bio_alloc(GFP_ATOMIC, 1); |
| @@ -425,33 +408,40 @@ static int submit(int rw, pgoff_t page_off, void *page) | |||
| 425 | return -ENOMEM; | 408 | return -ENOMEM; |
| 426 | bio->bi_sector = page_off * (PAGE_SIZE >> 9); | 409 | bio->bi_sector = page_off * (PAGE_SIZE >> 9); |
| 427 | bio->bi_bdev = resume_bdev; | 410 | bio->bi_bdev = resume_bdev; |
| 428 | bio->bi_end_io = end_io; | 411 | bio->bi_end_io = end_swap_bio_read; |
| 429 | 412 | ||
| 430 | if (bio_add_page(bio, virt_to_page(page), PAGE_SIZE, 0) < PAGE_SIZE) { | 413 | if (bio_add_page(bio, page, PAGE_SIZE, 0) < PAGE_SIZE) { |
| 431 | printk("swsusp: ERROR: adding page to bio at %ld\n",page_off); | 414 | printk("swsusp: ERROR: adding page to bio at %ld\n", page_off); |
| 432 | error = -EFAULT; | 415 | bio_put(bio); |
| 433 | goto Done; | 416 | return -EFAULT; |
| 434 | } | 417 | } |
| 435 | 418 | ||
| 436 | atomic_set(&io_done, 1); | 419 | lock_page(page); |
| 437 | submit_bio(rw | (1 << BIO_RW_SYNC), bio); | 420 | bio_get(bio); |
| 438 | while (atomic_read(&io_done)) | 421 | |
| 439 | yield(); | 422 | if (bio_chain == NULL) { |
| 440 | if (rw == READ) | 423 | submit_bio(rw | (1 << BIO_RW_SYNC), bio); |
| 441 | bio_set_pages_dirty(bio); | 424 | wait_on_page_locked(page); |
| 442 | Done: | 425 | if (rw == READ) |
| 443 | bio_put(bio); | 426 | bio_set_pages_dirty(bio); |
| 444 | return error; | 427 | bio_put(bio); |
| 428 | } else { | ||
| 429 | get_page(page); | ||
| 430 | bio->bi_private = *bio_chain; | ||
| 431 | *bio_chain = bio; | ||
| 432 | submit_bio(rw | (1 << BIO_RW_SYNC), bio); | ||
| 433 | } | ||
| 434 | return 0; | ||
| 445 | } | 435 | } |
| 446 | 436 | ||
| 447 | static int bio_read_page(pgoff_t page_off, void *page) | 437 | static int bio_read_page(pgoff_t page_off, void *addr, struct bio **bio_chain) |
| 448 | { | 438 | { |
| 449 | return submit(READ, page_off, page); | 439 | return submit(READ, page_off, virt_to_page(addr), bio_chain); |
| 450 | } | 440 | } |
| 451 | 441 | ||
| 452 | static int bio_write_page(pgoff_t page_off, void *page) | 442 | static int bio_write_page(pgoff_t page_off, void *addr) |
| 453 | { | 443 | { |
| 454 | return submit(WRITE, page_off, page); | 444 | return submit(WRITE, page_off, virt_to_page(addr), NULL); |
| 455 | } | 445 | } |
| 456 | 446 | ||
| 457 | /** | 447 | /** |
| @@ -476,7 +466,7 @@ static int get_swap_reader(struct swap_map_handle *handle, | |||
| 476 | handle->cur = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC); | 466 | handle->cur = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC); |
| 477 | if (!handle->cur) | 467 | if (!handle->cur) |
| 478 | return -ENOMEM; | 468 | return -ENOMEM; |
| 479 | error = bio_read_page(swp_offset(start), handle->cur); | 469 | error = bio_read_page(swp_offset(start), handle->cur, NULL); |
| 480 | if (error) { | 470 | if (error) { |
| 481 | release_swap_reader(handle); | 471 | release_swap_reader(handle); |
| 482 | return error; | 472 | return error; |
| @@ -485,7 +475,8 @@ static int get_swap_reader(struct swap_map_handle *handle, | |||
| 485 | return 0; | 475 | return 0; |
| 486 | } | 476 | } |
| 487 | 477 | ||
| 488 | static int swap_read_page(struct swap_map_handle *handle, void *buf) | 478 | static int swap_read_page(struct swap_map_handle *handle, void *buf, |
| 479 | struct bio **bio_chain) | ||
| 489 | { | 480 | { |
| 490 | unsigned long offset; | 481 | unsigned long offset; |
| 491 | int error; | 482 | int error; |
| @@ -495,16 +486,17 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf) | |||
| 495 | offset = handle->cur->entries[handle->k]; | 486 | offset = handle->cur->entries[handle->k]; |
| 496 | if (!offset) | 487 | if (!offset) |
| 497 | return -EFAULT; | 488 | return -EFAULT; |
| 498 | error = bio_read_page(offset, buf); | 489 | error = bio_read_page(offset, buf, bio_chain); |
| 499 | if (error) | 490 | if (error) |
| 500 | return error; | 491 | return error; |
| 501 | if (++handle->k >= MAP_PAGE_ENTRIES) { | 492 | if (++handle->k >= MAP_PAGE_ENTRIES) { |
| 493 | error = wait_on_bio_chain(bio_chain); | ||
| 502 | handle->k = 0; | 494 | handle->k = 0; |
| 503 | offset = handle->cur->next_swap; | 495 | offset = handle->cur->next_swap; |
| 504 | if (!offset) | 496 | if (!offset) |
| 505 | release_swap_reader(handle); | 497 | release_swap_reader(handle); |
| 506 | else | 498 | else if (!error) |
| 507 | error = bio_read_page(offset, handle->cur); | 499 | error = bio_read_page(offset, handle->cur, NULL); |
| 508 | } | 500 | } |
| 509 | return error; | 501 | return error; |
| 510 | } | 502 | } |
| @@ -517,38 +509,48 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf) | |||
| 517 | 509 | ||
| 518 | static int load_image(struct swap_map_handle *handle, | 510 | static int load_image(struct swap_map_handle *handle, |
| 519 | struct snapshot_handle *snapshot, | 511 | struct snapshot_handle *snapshot, |
| 520 | unsigned int nr_pages) | 512 | unsigned int nr_to_read) |
| 521 | { | 513 | { |
| 522 | unsigned int m; | 514 | unsigned int m; |
| 523 | int ret; | ||
| 524 | int error = 0; | 515 | int error = 0; |
| 525 | struct timeval start; | 516 | struct timeval start; |
| 526 | struct timeval stop; | 517 | struct timeval stop; |
| 518 | struct bio *bio; | ||
| 519 | int err2; | ||
| 520 | unsigned nr_pages; | ||
| 527 | 521 | ||
| 528 | printk("Loading image data pages (%u pages) ... ", nr_pages); | 522 | printk("Loading image data pages (%u pages) ... ", nr_to_read); |
| 529 | m = nr_pages / 100; | 523 | m = nr_to_read / 100; |
| 530 | if (!m) | 524 | if (!m) |
| 531 | m = 1; | 525 | m = 1; |
| 532 | nr_pages = 0; | 526 | nr_pages = 0; |
| 527 | bio = NULL; | ||
| 533 | do_gettimeofday(&start); | 528 | do_gettimeofday(&start); |
| 534 | do { | 529 | for ( ; ; ) { |
| 535 | ret = snapshot_write_next(snapshot, PAGE_SIZE); | 530 | error = snapshot_write_next(snapshot, PAGE_SIZE); |
| 536 | if (ret > 0) { | 531 | if (error <= 0) |
| 537 | error = swap_read_page(handle, data_of(*snapshot)); | 532 | break; |
| 538 | if (error) | 533 | error = swap_read_page(handle, data_of(*snapshot), &bio); |
| 539 | break; | 534 | if (error) |
| 540 | if (!(nr_pages % m)) | 535 | break; |
| 541 | printk("\b\b\b\b%3d%%", nr_pages / m); | 536 | if (snapshot->sync_read) |
| 542 | nr_pages++; | 537 | error = wait_on_bio_chain(&bio); |
| 543 | } | 538 | if (error) |
| 544 | } while (ret > 0); | 539 | break; |
| 540 | if (!(nr_pages % m)) | ||
| 541 | printk("\b\b\b\b%3d%%", nr_pages / m); | ||
| 542 | nr_pages++; | ||
| 543 | } | ||
| 544 | err2 = wait_on_bio_chain(&bio); | ||
| 545 | do_gettimeofday(&stop); | 545 | do_gettimeofday(&stop); |
| 546 | if (!error) | ||
| 547 | error = err2; | ||
| 546 | if (!error) { | 548 | if (!error) { |
| 547 | printk("\b\b\b\bdone\n"); | 549 | printk("\b\b\b\bdone\n"); |
| 548 | if (!snapshot_image_loaded(snapshot)) | 550 | if (!snapshot_image_loaded(snapshot)) |
| 549 | error = -ENODATA; | 551 | error = -ENODATA; |
| 550 | } | 552 | } |
| 551 | show_speed(&start, &stop, nr_pages, "Read"); | 553 | show_speed(&start, &stop, nr_to_read, "Read"); |
| 552 | return error; | 554 | return error; |
| 553 | } | 555 | } |
| 554 | 556 | ||
| @@ -571,7 +573,7 @@ int swsusp_read(void) | |||
| 571 | header = (struct swsusp_info *)data_of(snapshot); | 573 | header = (struct swsusp_info *)data_of(snapshot); |
| 572 | error = get_swap_reader(&handle, swsusp_header.image); | 574 | error = get_swap_reader(&handle, swsusp_header.image); |
| 573 | if (!error) | 575 | if (!error) |
| 574 | error = swap_read_page(&handle, header); | 576 | error = swap_read_page(&handle, header, NULL); |
| 575 | if (!error) | 577 | if (!error) |
| 576 | error = load_image(&handle, &snapshot, header->pages - 1); | 578 | error = load_image(&handle, &snapshot, header->pages - 1); |
| 577 | release_swap_reader(&handle); | 579 | release_swap_reader(&handle); |
| @@ -597,7 +599,7 @@ int swsusp_check(void) | |||
| 597 | if (!IS_ERR(resume_bdev)) { | 599 | if (!IS_ERR(resume_bdev)) { |
| 598 | set_blocksize(resume_bdev, PAGE_SIZE); | 600 | set_blocksize(resume_bdev, PAGE_SIZE); |
| 599 | memset(&swsusp_header, 0, sizeof(swsusp_header)); | 601 | memset(&swsusp_header, 0, sizeof(swsusp_header)); |
| 600 | if ((error = bio_read_page(0, &swsusp_header))) | 602 | if ((error = bio_read_page(0, &swsusp_header, NULL))) |
| 601 | return error; | 603 | return error; |
| 602 | if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) { | 604 | if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) { |
| 603 | memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10); | 605 | memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10); |
diff --git a/mm/page_io.c b/mm/page_io.c index f46a9862b7ef..d4840ecbf8f9 100644 --- a/mm/page_io.c +++ b/mm/page_io.c | |||
| @@ -74,7 +74,7 @@ static int end_swap_bio_write(struct bio *bio, unsigned int bytes_done, int err) | |||
| 74 | return 0; | 74 | return 0; |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | static int end_swap_bio_read(struct bio *bio, unsigned int bytes_done, int err) | 77 | int end_swap_bio_read(struct bio *bio, unsigned int bytes_done, int err) |
| 78 | { | 78 | { |
| 79 | const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags); | 79 | const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags); |
| 80 | struct page *page = bio->bi_io_vec[0].bv_page; | 80 | struct page *page = bio->bi_io_vec[0].bv_page; |
