Re: [PATCH 2/3] zram: support page-based parallel write

From: Sergey Senozhatsky
Date: Fri Oct 21 2016 - 02:04:25 EST


On (09/22/16 15:42), Minchan Kim wrote:
> +static ssize_t use_aio_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t len)
> +{
> + int ret;
> + u16 do_async;
> + struct zram *zram = dev_to_zram(dev);
> +
> + ret = kstrtou16(buf, 10, &do_async);
> + if (ret)
> + return ret;
> +
> + down_write(&zram->init_lock);
> + if (init_done(zram)) {
> + up_write(&zram->init_lock);
> + pr_info("Can't change for initialized device\n");
> + return -EBUSY;
> + }
> +
> + if (do_async)
> + zram->use_aio = true;
> + else
> + zram->use_aio = false;

a stupid nitpick:
zram->use_aio = do_async?

> + up_write(&zram->init_lock);
> +
> + return len;
> +}
> +#endif
> +
[..]
> +static void worker_wake_up(void)
> +{
> + /*
> + * Unless it's enough workes to handle accumulated requests,
> + * wake up new workers.
> + */
> + if (workers.nr_running * NR_BATCH_PAGES < workers.nr_req) {
> + int nr_wakeup = (workers.nr_req + NR_BATCH_PAGES) /
> + NR_BATCH_PAGES - workers.nr_running;
> +
> + wake_up_nr(&workers.req_wait, nr_wakeup);
> + }
> +}
> +
> +static void zram_unplug(struct blk_plug_cb *cb, bool from_schedule)
> +{
> + spin_lock(&workers.req_lock);
> + if (workers.nr_req)
> + worker_wake_up();
> + spin_unlock(&workers.req_lock);
> + kfree(cb);
> +}
> +
> +static int zram_check_plugged(void)
> +{
> + return !!blk_check_plugged(zram_unplug, NULL,
> + sizeof(struct blk_plug_cb));
> +}

I'm having some troubles understanding the purpose of zram_check_plugged().
it's a global symbol, can you just use it directly? otherwise we are
doing additional kmalloc/kfree, spin_lock/unlock and so on.

what am I missing? current->plug? can it affect us? how?


> +int queue_page_request(struct zram *zram, struct bio_vec *bvec, u32 index,
> + int offset, bool write)

static int?

> +{
> + struct page_request *page_req = kmalloc(sizeof(*page_req), GFP_NOIO);
> +
> + if (!page_req)
> + return -ENOMEM;


...


> +int queue_page_request_list(struct zram *zram, struct bio_request *bio_req,

static int?

> + struct bio_vec *bvec, u32 index, int offset,
> + bool write, struct list_head *page_list)
> +{
> + struct page_request *page_req = kmalloc(sizeof(*page_req), GFP_NOIO);
> +
> + if (!page_req) {
> + while (!list_empty(page_list)) {
> + page_req = list_first_entry(page_list,
> + struct page_request, list);
> + list_del(&page_req->list);
> + kfree(page_req);
> + }
> +
> + return -ENOMEM;
> + }
> +
> + page_req->bio_req = bio_req;
> + page_req->zram = zram;
> + page_req->bvec = *bvec;
> + page_req->index = index;
> + page_req->offset = offset;
> + page_req->write = write;
> + atomic_inc(&bio_req->nr_pages);
> +
> + list_add_tail(&page_req->list, page_list);
> +
> + return 0;
> +}
> +
> +/*
> + * @page_list: pages isolated from request queue
> + */
> +static void get_page_requests(struct list_head *page_list)
> +{
> + struct page_request *page_req;
> + int nr_pages;
> +
> + for (nr_pages = 0; nr_pages < NR_BATCH_PAGES; nr_pages++) {
> + if (list_empty(&workers.req_list))
> + break;
> +
> + page_req = list_first_entry(&workers.req_list,
> + struct page_request, list);
> + list_move(&page_req->list, page_list);
> + }
> +
> + workers.nr_req -= nr_pages;
> +}
> +
> +/*
> + * move @page_list to request queue and wake up workers if it is necessary.
> + */
> +static void run_worker(struct bio *bio, struct list_head *page_list,
> + unsigned int nr_pages)
> +{
> + WARN_ON(list_empty(page_list));
> +
> + spin_lock(&workers.req_lock);
> + list_splice_tail(page_list, &workers.req_list);
> + workers.nr_req += nr_pages;
> + if (bio->bi_opf & REQ_SYNC || !zram_check_plugged())
> + worker_wake_up();
> + spin_unlock(&workers.req_lock);
> +}
> +
> +static bool __zram_make_async_request(struct zram *zram, struct bio *bio)
> +{
> + int offset;
> + u32 index;
> + struct bio_vec bvec;
> + struct bvec_iter iter;
> + LIST_HEAD(page_list);
> + struct bio_request *bio_req;
> + unsigned int nr_pages = 0;
> + bool write = op_is_write(bio_op(bio));
> +
> + if (!zram->use_aio || !op_is_write(bio_op(bio)))
> + return false;
> +
> + bio_req = kmalloc(sizeof(*bio_req), GFP_NOIO);
> + if (!bio_req)
> + return false;
> +
> + index = bio->bi_iter.bi_sector >> SECTORS_PER_PAGE_SHIFT;
> + offset = (bio->bi_iter.bi_sector &
> + (SECTORS_PER_PAGE - 1)) << SECTOR_SHIFT;
> +
> + bio_req->bio = bio;
> + atomic_set(&bio_req->nr_pages, 0);
> +
> + bio_for_each_segment(bvec, bio, iter) {
> + int max_transfer_size = PAGE_SIZE - offset;
> +
> + if (bvec.bv_len > max_transfer_size) {
> + struct bio_vec bv;
> +
> + bv.bv_page = bvec.bv_page;
> + bv.bv_len = max_transfer_size;
> + bv.bv_offset = bvec.bv_offset;
> +
> + if (queue_page_request_list(zram, bio_req, &bv,
> + index, offset, write, &page_list))
> + goto fail;
> +
> + bv.bv_len = bvec.bv_len - max_transfer_size;
> + bv.bv_offset += max_transfer_size;
> + if (queue_page_request_list(zram, bio_req, &bv,
> + index + 1, 0, write, &page_list))
> + goto fail;
> + } else {
> + if (queue_page_request_list(zram, bio_req, &bvec,
> + index, offset, write, &page_list))
> + goto fail;
> + }
> +
> + nr_pages++;
> + update_position(&index, &offset, &bvec);
> + }
> +
> + run_worker(bio, &page_list, nr_pages);
> +
> + return true;
> +fail:
> + kfree(bio_req);
> +
> + WARN_ON(!list_empty(&page_list));
> +
> + return false;
> +}
> +
> +void page_requests_rw(struct list_head *page_list)
> +{
> + struct page_request *page_req;
> + bool write;
> + struct page *page;
> + struct zram *zram;
> + int err;
> + bool free_bio;
> + struct bio_request *bio_req;
> +
> + while (!list_empty(page_list)) {
> + free_bio = false;
> + page_req = list_last_entry(page_list, struct page_request,
> + list);
> + write = page_req->write;
> + page = page_req->bvec.bv_page;
> + zram = page_req->zram;
> + bio_req = page_req->bio_req;
> + if (bio_req && atomic_dec_and_test(&bio_req->nr_pages))
> + free_bio = true;
> + list_del(&page_req->list);
> +
> + err = zram_bvec_rw(zram, &page_req->bvec, page_req->index,
> + page_req->offset, page_req->write);
> +
> + kfree(page_req);
> +
> + /* page-based request */
> + if (!bio_req) {
> + page_endio(page, write, err);
> + zram_meta_put(zram);

who is calling zram_meta_get()? I mean in this loop...
what if the loop continues after that `if'?

> + /* bio-based request */
> + } else if (free_bio) {
> + if (likely(!err))
> + bio_endio(bio_req->bio);
> + else
> + bio_io_error(bio_req->bio);
> + kfree(bio_req);
> + zram_meta_put(zram);

ditto, who is calling zram_meta_get()?
what if the loop continue after that `if'?


we do zram_meta_get() only once in zram_make_request(). but then
put meta for every request in page_list, while a bio passed to
__zram_make_async_request() can span across several requests in
page_list. right?

> + }
> + }
> +}
> +
> +static int zram_thread(void *data)
> +{
> + DEFINE_WAIT(wait);
> + LIST_HEAD(page_list);
> +
> + spin_lock(&workers.req_lock);
> + workers.nr_running++;
> +
> + while (!kthread_should_stop()) {
> + if (list_empty(&workers.req_list)) {
> + prepare_to_wait_exclusive(&workers.req_wait, &wait,
> + TASK_INTERRUPTIBLE);
> + workers.nr_running--;
> + spin_unlock(&workers.req_lock);
> + schedule();
> + spin_lock(&workers.req_lock);
> + finish_wait(&workers.req_wait, &wait);
> + workers.nr_running++;
> + continue;
> + }
> +
> + get_page_requests(&page_list);
> + if (list_empty(&page_list))
> + continue;
> +
> + spin_unlock(&workers.req_lock);
> + page_requests_rw(&page_list);
> + cond_resched();
> + spin_lock(&workers.req_lock);
> + }
> +
> + workers.nr_running--;
> + spin_unlock(&workers.req_lock);
> +
> + return 0;
> +}
> +
> +static void destroy_workers(void)
> +{
> + struct zram_worker *worker;
> +
> + while (!list_empty(&workers.worker_list)) {
> + worker = list_first_entry(&workers.worker_list,
> + struct zram_worker,
> + list);
> + kthread_stop(worker->task);
> + list_del(&worker->list);
> + kfree(worker);
> + }
> +
> + WARN_ON(workers.nr_running);
> +}
> +
> +static int create_workers(void)
> +{
> + int i;
> + int nr_cpu = num_online_cpus();
> + struct zram_worker *worker;
> +
> + INIT_LIST_HEAD(&workers.worker_list);
> + INIT_LIST_HEAD(&workers.req_list);
> + spin_lock_init(&workers.req_lock);
> + init_waitqueue_head(&workers.req_wait);
> +
> + for (i = 0; i < nr_cpu; i++) {
> + worker = kmalloc(sizeof(*worker), GFP_KERNEL);
> + if (!worker)
> + goto error;
> +
> + worker->task = kthread_run(zram_thread, NULL, "zramd-%d", i);
> + if (IS_ERR(worker->task)) {
> + kfree(worker);
> + goto error;
> + }
> +
> + list_add(&worker->list, &workers.worker_list);
> + }
> +
> + return 0;
> +
> +error:
> + destroy_workers();
> + return 1;
> +}
> +
> +static int zram_rw_async_page(struct zram *zram,
> + struct bio_vec *bv, u32 index, int offset,
> + bool is_write)
> +{
> + if (!zram->use_aio || !is_write)
> + return 1;
> +
> + return queue_page_request(zram, bv, index, offset, is_write);
> +}
> +#else
> +static bool __zram_make_async_request(struct zram *zram, struct bio *bio)
> +{
> + return false;
> +}
> +
> +static int zram_rw_async_page(struct zram *zram,
> + struct bio_vec *bv, u32 index, int offset,
> + bool is_write)
> +{
> + return 1;
> +}
> +
> +static int create_workers(void)
> +{
> + return 0;
> +}
> +
> +static void destroy_workers(void)
> +{
> +}
> +#endif
> +
> /*
> * Handler function for all zram I/O requests.
> */
> @@ -954,6 +1380,9 @@ static blk_qc_t zram_make_request(struct request_queue *queue, struct bio *bio)
> goto out;
> }
>
> + if (__zram_make_async_request(zram, bio))
> + goto out;
> +
> __zram_make_sync_request(zram, bio);
> out:
> return BLK_QC_T_NONE;
> @@ -1028,8 +1457,12 @@ static int zram_rw_page(struct block_device *bdev, sector_t sector,
> bv.bv_len = PAGE_SIZE;
> bv.bv_offset = 0;
>
> - err = zram_rw_sync_page(bdev, zram, &bv, index, offset, is_write);
> + err = zram_rw_async_page(zram, &bv, index, offset, is_write);
> + if (!err)
> + goto out;
>
> + err = zram_rw_sync_page(bdev, zram, &bv, index, offset, is_write);
> +out:
> return err;
> }
>
> @@ -1066,7 +1499,6 @@ static void zram_reset_device(struct zram *zram)
> /* Reset stats */
> memset(&zram->stats, 0, sizeof(zram->stats));
> zram->disksize = 0;
> -
> set_capacity(zram->disk, 0);
> part_stat_set_all(&zram->disk->part0, 0);
>
> @@ -1211,6 +1643,9 @@ static DEVICE_ATTR_RW(mem_limit);
> static DEVICE_ATTR_RW(mem_used_max);
> static DEVICE_ATTR_RW(max_comp_streams);
> static DEVICE_ATTR_RW(comp_algorithm);
> +#ifdef CONFIG_ZRAM_ASYNC_IO
> +static DEVICE_ATTR_RW(use_aio);
> +#endif

hm... no real objection, but exporing this sysfs attr can be very hacky
and difficult for people...

-ss

> static struct attribute *zram_disk_attrs[] = {
> &dev_attr_disksize.attr,
> @@ -1231,6 +1666,9 @@ static struct attribute *zram_disk_attrs[] = {
> &dev_attr_mem_used_max.attr,
> &dev_attr_max_comp_streams.attr,
> &dev_attr_comp_algorithm.attr,
> +#ifdef CONFIG_ZRAM_ASYNC_IO
> + &dev_attr_use_aio.attr,
> +#endif
> &dev_attr_io_stat.attr,
> &dev_attr_mm_stat.attr,
> &dev_attr_debug_stat.attr,
> @@ -1261,7 +1699,9 @@ static int zram_add(void)
> device_id = ret;
>
> init_rwsem(&zram->init_lock);
> -
> +#ifdef CONFIG_ZRAM_ASYNC_IO
> + zram->use_aio = true;
> +#endif
> queue = blk_alloc_queue(GFP_KERNEL);
> if (!queue) {
> pr_err("Error allocating disk queue for device %d\n",
> @@ -1485,6 +1925,9 @@ static int __init zram_init(void)
> num_devices--;
> }
>
> + if (create_workers())
> + goto out_error;
> +
> return 0;
>
> out_error:
> @@ -1495,6 +1938,7 @@ static int __init zram_init(void)
> static void __exit zram_exit(void)
> {
> destroy_devices();
> + destroy_workers();
> }
>
> module_init(zram_init);
> diff --git a/drivers/block/zram/zram_drv.h b/drivers/block/zram/zram_drv.h
> index 74fcf10da374..3f62501f619f 100644
> --- a/drivers/block/zram/zram_drv.h
> +++ b/drivers/block/zram/zram_drv.h
> @@ -119,5 +119,8 @@ struct zram {
> * zram is claimed so open request will be failed
> */
> bool claim; /* Protected by bdev->bd_mutex */
> +#ifdef CONFIG_ZRAM_ASYNC_IO
> + bool use_aio;
> +#endif
> };
> #endif
> --
> 2.7.4
>