Re: [PATCH] Add a page cache-backed balloon device driver.

From: Michael S. Tsirkin
Date: Tue Jun 26 2012 - 17:41:03 EST


On Tue, Jun 26, 2012 at 01:32:58PM -0700, Frank Swiderski wrote:
> This implementation of a virtio balloon driver uses the page cache to
> "store" pages that have been released to the host. The communication
> (outside of target counts) is one way--the guest notifies the host when
> it adds a page to the page cache, allowing the host to madvise(2) with
> MADV_DONTNEED. Reclaim in the guest is therefore automatic and implicit
> (via the regular page reclaim). This means that inflating the balloon
> is similar to the existing balloon mechanism, but the deflate is
> different--it re-uses existing Linux kernel functionality to
> automatically reclaim.
>
> Signed-off-by: Frank Swiderski <fes@xxxxxxxxxx>

I'm pondering this:

Should it really be a separate driver/device ID?
If it behaves the same from host POV, maybe it
should be up to the guest how to inflate/deflate
the balloon internally?

> ---
> drivers/virtio/Kconfig | 13 +
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio_fileballoon.c | 636 +++++++++++++++++++++++++++++++++++
> include/linux/virtio_balloon.h | 9 +
> include/linux/virtio_ids.h | 1 +
> 5 files changed, 660 insertions(+), 0 deletions(-)
> create mode 100644 drivers/virtio/virtio_fileballoon.c
>
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index f38b17a..cffa2a7 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -35,6 +35,19 @@ config VIRTIO_BALLOON
>
> If unsure, say M.
>
> +config VIRTIO_FILEBALLOON
> + tristate "Virtio page cache-backed balloon driver"
> + select VIRTIO
> + select VIRTIO_RING
> + ---help---
> + This driver supports decreasing and automatically reclaiming the
> + memory within a guest VM. Unlike VIRTIO_BALLOON, this driver instead
> + tries to maintain a specific target balloon size using the page cache.
> + This allows the guest to implicitly deflate the balloon by flushing
> + pages from the cache and touching the page.
> +
> + If unsure, say N.
> +
> config VIRTIO_MMIO
> tristate "Platform bus driver for memory mapped virtio devices (EXPERIMENTAL)"
> depends on HAS_IOMEM && EXPERIMENTAL
> diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
> index 5a4c63c..7ca0a3f 100644
> --- a/drivers/virtio/Makefile
> +++ b/drivers/virtio/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_VIRTIO_RING) += virtio_ring.o
> obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o
> obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
> obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
> +obj-$(CONFIG_VIRTIO_FILEBALLOON) += virtio_fileballoon.o
> diff --git a/drivers/virtio/virtio_fileballoon.c b/drivers/virtio/virtio_fileballoon.c
> new file mode 100644
> index 0000000..ff252ec
> --- /dev/null
> +++ b/drivers/virtio/virtio_fileballoon.c
> @@ -0,0 +1,636 @@
> +/* Virtio file (page cache-backed) balloon implementation, inspired by
> + * Dor Loar and Marcelo Tosatti's implementations, and based on Rusty Russel's
> + * implementation.
> + *
> + * This implementation of the virtio balloon driver re-uses the page cache to
> + * allow memory consumed by inflating the balloon to be reclaimed by linux. It
> + * creates and mounts a bare-bones filesystem containing a single inode. When
> + * the host requests the balloon to inflate, it does so by "reading" pages at
> + * offsets into the inode mapping's page_tree. The host is notified when the
> + * pages are added to the page_tree, allowing it (the host) to madvise(2) the
> + * corresponding host memory, reducing the RSS of the virtual machine. In this
> + * implementation, the host is only notified when a page is added to the
> + * balloon. Reclaim happens under the existing TTFP logic, which flushes unused
> + * pages in the page cache. If the host used MADV_DONTNEED, then when the guest
> + * uses the page, the zero page will be mapped in, allowing automatic (and fast,
> + * compared to requiring a host notification via a virtio queue to get memory
> + * back) reclaim.
> + *
> + * Copyright 2008 Rusty Russell IBM Corporation
> + * Copyright 2011 Frank Swiderski Google Inc
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +#include <linux/backing-dev.h>
> +#include <linux/delay.h>
> +#include <linux/file.h>
> +#include <linux/freezer.h>
> +#include <linux/fs.h>
> +#include <linux/jiffies.h>
> +#include <linux/kthread.h>
> +#include <linux/module.h>
> +#include <linux/mount.h>
> +#include <linux/pagemap.h>
> +#include <linux/slab.h>
> +#include <linux/swap.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_balloon.h>
> +#include <linux/writeback.h>
> +
> +#define VIRTBALLOON_PFN_ARRAY_SIZE 256
> +
> +struct virtio_balloon {
> + struct virtio_device *vdev;
> + struct virtqueue *inflate_vq;
> +
> + /* Where the ballooning thread waits for config to change. */
> + wait_queue_head_t config_change;
> +
> + /* The thread servicing the balloon. */
> + struct task_struct *thread;
> +
> + /* Waiting for host to ack the pages we released. */
> + struct completion acked;
> +
> + /* The array of pfns we tell the Host about. */
> + unsigned int num_pfns;
> + u32 pfns[VIRTBALLOON_PFN_ARRAY_SIZE];
> +
> + struct virtio_balloon_stat stats[VIRTIO_BALLOON_S_NR];
> +
> + /* The last page offset read into the mapping's page_tree */
> + unsigned long last_scan_page_array;
> +
> + /* The last time a page was reclaimed */
> + unsigned long last_reclaim;
> +};
> +
> +/* Magic number used for the skeleton filesystem in the call to mount_pseudo */
> +#define BALLOONFS_MAGIC 0x42414c4c
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_FILE_BALLOON, VIRTIO_DEV_ANY_ID },
> + { 0 },
> +};
> +
> +/*
> + * The skeleton filesystem contains a single inode, held by the structure below.
> + * Using the containing structure below allows easy access to the struct
> + * virtio_balloon.
> + */
> +static struct balloon_inode {
> + struct inode inode;
> + struct virtio_balloon *vb;
> +} the_inode;
> +
> +/*
> + * balloon_alloc_inode is called when the single inode for the skeleton
> + * filesystem is created in init() with the call to new_inode.
> + */
> +static struct inode *balloon_alloc_inode(struct super_block *sb)
> +{
> + static bool already_inited;
> + /* We should only ever be called once! */
> + BUG_ON(already_inited);
> + already_inited = true;
> + inode_init_once(&the_inode.inode);
> + return &the_inode.inode;
> +}
> +
> +/* Noop implementation of destroy_inode. */
> +static void balloon_destroy_inode(struct inode *inode)
> +{
> +}
> +
> +static int balloon_sync_fs(struct super_block *sb, int wait)
> +{
> + return filemap_write_and_wait(the_inode.inode.i_mapping);
> +}
> +
> +static const struct super_operations balloonfs_ops = {
> + .alloc_inode = balloon_alloc_inode,
> + .destroy_inode = balloon_destroy_inode,
> + .sync_fs = balloon_sync_fs,
> +};
> +
> +static const struct dentry_operations balloonfs_dentry_operations = {
> +};
> +
> +/*
> + * balloonfs_writepage is called when linux needs to reclaim memory held using
> + * the balloonfs' page cache.
> + */
> +static int balloonfs_writepage(struct page *page, struct writeback_control *wbc)
> +{
> + the_inode.vb->last_reclaim = jiffies;
> + SetPageUptodate(page);
> + ClearPageDirty(page);
> + /*
> + * If the page isn't being flushed from the page allocator, go ahead and
> + * drop it from the page cache anyway.
> + */
> + if (!wbc->for_reclaim)
> + delete_from_page_cache(page);
> + unlock_page(page);
> + return 0;
> +}
> +
> +/* Nearly no-op implementation of readpage */
> +static int balloonfs_readpage(struct file *file, struct page *page)
> +{
> + SetPageUptodate(page);
> + unlock_page(page);
> + return 0;
> +}
> +
> +static const struct address_space_operations balloonfs_aops = {
> + .writepage = balloonfs_writepage,
> + .readpage = balloonfs_readpage
> +};
> +
> +static struct backing_dev_info balloonfs_backing_dev_info = {
> + .name = "balloonfs",
> + .ra_pages = 0,
> + .capabilities = BDI_CAP_NO_ACCT_AND_WRITEBACK
> +};
> +
> +static struct dentry *balloonfs_mount(struct file_system_type *fs_type,
> + int flags, const char *dev_name, void *data)
> +{
> + struct dentry *root;
> + struct inode *inode;
> + root = mount_pseudo(fs_type, "balloon:", &balloonfs_ops,
> + &balloonfs_dentry_operations, BALLOONFS_MAGIC);
> + inode = root->d_inode;
> + inode->i_mapping->a_ops = &balloonfs_aops;
> + mapping_set_gfp_mask(inode->i_mapping,
> + (GFP_HIGHUSER | __GFP_NOMEMALLOC));
> + inode->i_mapping->backing_dev_info = &balloonfs_backing_dev_info;
> + return root;
> +}
> +
> +/* The single mounted skeleton filesystem */
> +static struct vfsmount *balloon_mnt __read_mostly;
> +
> +static struct file_system_type balloon_fs_type = {
> + .name = "balloonfs",
> + .mount = balloonfs_mount,
> + .kill_sb = kill_anon_super,
> +};
> +
> +/* Acknowledges a message from the specified virtqueue. */
> +static void balloon_ack(struct virtqueue *vq)
> +{
> + struct virtio_balloon *vb;
> + unsigned int len;
> +
> + vb = virtqueue_get_buf(vq, &len);
> + if (vb)
> + complete(&vb->acked);
> +}
> +
> +/*
> + * Scans the page_tree for the inode's mapping, looking for an offset that is
> + * currently empty, returning that index (or 0 if it could not fill the
> + * request).
> + */
> +static unsigned long find_available_inode_page(struct virtio_balloon *vb)
> +{
> + unsigned long radix_index, index, max_scan;
> + struct address_space *mapping = the_inode.inode.i_mapping;
> +
> + /*
> + * This function is a serialized call (only happens on the free-to-host
> + * thread), so no locking is necessary here.
> + */
> + index = vb->last_scan_page_array;
> + max_scan = totalram_pages - vb->last_scan_page_array;
> +
> + /*
> + * Scan starting at the last scanned offset, then wrap around if
> + * necessary.
> + */
> + if (index == 0)
> + index = 1;
> + rcu_read_lock();
> + radix_index = radix_tree_next_hole(&mapping->page_tree,
> + index, max_scan);
> + rcu_read_unlock();
> + /*
> + * If we hit the end of the tree, wrap and search up to the original
> + * index.
> + */
> + if (radix_index - index >= max_scan) {
> + if (index != 1) {
> + rcu_read_lock();
> + radix_index = radix_tree_next_hole(&mapping->page_tree,
> + 1, index);
> + rcu_read_unlock();
> + if (radix_index - 1 >= index)
> + radix_index = 0;
> + } else {
> + radix_index = 0;
> + }
> + }
> + vb->last_scan_page_array = radix_index;
> +
> + return radix_index;
> +}
> +
> +/* Notifies the host of pages in the specified virtqueue. */
> +static int tell_host(struct virtio_balloon *vb, struct virtqueue *vq)
> +{
> + int err;
> + struct scatterlist sg;
> +
> + sg_init_one(&sg, vb->pfns, sizeof(vb->pfns[0]) * vb->num_pfns);
> +
> + init_completion(&vb->acked);
> +
> + /* We should always be able to add one buffer to an empty queue. */
> + err = virtqueue_add_buf(vq, &sg, 1, 0, vb, GFP_KERNEL);
> + if (err < 0)
> + return err;
> + virtqueue_kick(vq);
> +
> + /* When host has read buffer, this completes via balloon_ack */
> + wait_for_completion(&vb->acked);
> + return err;
> +}
> +
> +static void fill_balloon(struct virtio_balloon *vb, size_t num)
> +{
> + int err;
> +
> + /* We can only do one array worth at a time. */
> + num = min(num, ARRAY_SIZE(vb->pfns));
> +
> + for (vb->num_pfns = 0; vb->num_pfns < num; vb->num_pfns++) {
> + struct page *page;
> + unsigned long inode_pfn = find_available_inode_page(vb);
> + /* Should always be able to find a page. */
> + BUG_ON(!inode_pfn);
> + page = read_mapping_page(the_inode.inode.i_mapping, inode_pfn,
> + NULL);
> + if (IS_ERR(page)) {
> + if (printk_ratelimit())
> + dev_printk(KERN_INFO, &vb->vdev->dev,
> + "Out of puff! Can't get %zu pages\n",
> + num);
> + break;
> + }
> +
> + /* Set the page to be dirty */
> + set_page_dirty(page);
> +
> + vb->pfns[vb->num_pfns] = page_to_pfn(page);
> + }
> +
> + /* Didn't get any? Oh well. */
> + if (vb->num_pfns == 0)
> + return;
> +
> + /* Notify the host of the pages we just added to the page_tree. */
> + err = tell_host(vb, vb->inflate_vq);
> +
> + for (; vb->num_pfns != 0; vb->num_pfns--) {
> + struct page *page = pfn_to_page(vb->pfns[vb->num_pfns - 1]);
> + /*
> + * Release our refcount on the page so that it can be reclaimed
> + * when necessary.
> + */
> + page_cache_release(page);
> + }
> + __mark_inode_dirty(&the_inode.inode, I_DIRTY_PAGES);
> +}
> +
> +static inline void update_stat(struct virtio_balloon *vb, int idx,
> + u64 val)
> +{
> + BUG_ON(idx >= VIRTIO_BALLOON_S_NR);
> + vb->stats[idx].tag = idx;
> + vb->stats[idx].val = val;
> +}
> +
> +#define pages_to_bytes(x) ((u64)(x) << PAGE_SHIFT)
> +
> +static inline u32 config_pages(struct virtio_balloon *vb);
> +static void update_balloon_stats(struct virtio_balloon *vb)
> +{
> + unsigned long events[NR_VM_EVENT_ITEMS];
> + struct sysinfo i;
> +
> + all_vm_events(events);
> + si_meminfo(&i);
> +
> + update_stat(vb, VIRTIO_BALLOON_S_SWAP_IN,
> + pages_to_bytes(events[PSWPIN]));
> + update_stat(vb, VIRTIO_BALLOON_S_SWAP_OUT,
> + pages_to_bytes(events[PSWPOUT]));
> + update_stat(vb, VIRTIO_BALLOON_S_MAJFLT, events[PGMAJFAULT]);
> + update_stat(vb, VIRTIO_BALLOON_S_MINFLT, events[PGFAULT]);
> +
> + /* Total and Free Mem */
> + update_stat(vb, VIRTIO_BALLOON_S_MEMFREE, pages_to_bytes(i.freeram));
> + update_stat(vb, VIRTIO_BALLOON_S_MEMTOT, pages_to_bytes(i.totalram));
> +}
> +
> +static void virtballoon_changed(struct virtio_device *vdev)
> +{
> + struct virtio_balloon *vb = vdev->priv;
> +
> + wake_up(&vb->config_change);
> +}
> +
> +static inline bool config_need_stats(struct virtio_balloon *vb)
> +{
> + u32 v = 0;
> +
> + vb->vdev->config->get(vb->vdev,
> + offsetof(struct virtio_balloon_config,
> + need_stats),
> + &v, sizeof(v));
> + return (v != 0);
> +}
> +
> +static inline u32 config_pages(struct virtio_balloon *vb)
> +{
> + u32 v = 0;
> +
> + vb->vdev->config->get(vb->vdev,
> + offsetof(struct virtio_balloon_config, num_pages),
> + &v, sizeof(v));
> + return v;
> +}
> +
> +static inline s64 towards_target(struct virtio_balloon *vb)
> +{
> + struct address_space *mapping = the_inode.inode.i_mapping;
> + u32 v = config_pages(vb);
> +
> + return (s64)v - (mapping ? mapping->nrpages : 0);
> +}
> +
> +static void update_balloon_size(struct virtio_balloon *vb)
> +{
> + struct address_space *mapping = the_inode.inode.i_mapping;
> + __le32 actual = cpu_to_le32((mapping ? mapping->nrpages : 0));
> +
> + vb->vdev->config->set(vb->vdev,
> + offsetof(struct virtio_balloon_config, actual),
> + &actual, sizeof(actual));
> +}
> +
> +static void update_free_and_total(struct virtio_balloon *vb)
> +{
> + struct sysinfo i;
> + u32 value;
> +
> + si_meminfo(&i);
> +
> + update_balloon_stats(vb);
> + value = i.totalram;
> + vb->vdev->config->set(vb->vdev,
> + offsetof(struct virtio_balloon_config,
> + pages_total),
> + &value, sizeof(value));
> + value = i.freeram;
> + vb->vdev->config->set(vb->vdev,
> + offsetof(struct virtio_balloon_config,
> + pages_free),
> + &value, sizeof(value));
> + value = 0;
> + vb->vdev->config->set(vb->vdev,
> + offsetof(struct virtio_balloon_config,
> + need_stats),
> + &value, sizeof(value));
> +}
> +
> +static int balloon(void *_vballoon)
> +{
> + struct virtio_balloon *vb = _vballoon;
> +
> + set_freezable();
> + while (!kthread_should_stop()) {
> + s64 diff;
> + try_to_freeze();
> + wait_event_interruptible(vb->config_change,
> + (diff = towards_target(vb)) > 0
> + || config_need_stats(vb)
> + || kthread_should_stop()
> + || freezing(current));
> + if (config_need_stats(vb))
> + update_free_and_total(vb);
> + if (diff > 0) {
> + unsigned long reclaim_time = vb->last_reclaim + 2 * HZ;
> + /*
> + * Don't fill the balloon if a page reclaim happened in
> + * the past 2 seconds.
> + */
> + if (time_after_eq(reclaim_time, jiffies)) {
> + /* Inflating too fast--sleep and skip. */
> + msleep(500);
> + } else {
> + fill_balloon(vb, diff);
> + }
> + } else if (diff < 0 && config_pages(vb) == 0) {
> + /*
> + * Here we are specifically looking to detect the case
> + * where there are pages in the page cache, but the
> + * device wants us to go to 0. This is used in save/
> + * restore since the host device doesn't keep track of
> + * PFNs, and must flush the page cache on restore
> + * (which loses the context of the original device
> + * instance). However, we still suggest syncing the
> + * diff so that we can get within the target range.
> + */
> + s64 nr_to_write =
> + (!config_pages(vb) ? LONG_MAX : -diff);
> + struct writeback_control wbc = {
> + .sync_mode = WB_SYNC_ALL,
> + .nr_to_write = nr_to_write,
> + .range_start = 0,
> + .range_end = LLONG_MAX,
> + };
> + sync_inode(&the_inode.inode, &wbc);
> + }
> + update_balloon_size(vb);
> + }
> + return 0;
> +}
> +
> +static ssize_t virtballoon_attr_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf);
> +
> +static DEVICE_ATTR(total_memory, 0644,
> + virtballoon_attr_show, NULL);
> +
> +static DEVICE_ATTR(free_memory, 0644,
> + virtballoon_attr_show, NULL);
> +
> +static DEVICE_ATTR(target_pages, 0644,
> + virtballoon_attr_show, NULL);
> +
> +static DEVICE_ATTR(actual_pages, 0644,
> + virtballoon_attr_show, NULL);
> +
> +static struct attribute *virtballoon_attrs[] = {
> + &dev_attr_total_memory.attr,
> + &dev_attr_free_memory.attr,
> + &dev_attr_target_pages.attr,
> + &dev_attr_actual_pages.attr,
> + NULL
> +};
> +static struct attribute_group virtballoon_attr_group = {
> + .name = "virtballoon",
> + .attrs = virtballoon_attrs,
> +};
> +
> +static ssize_t virtballoon_attr_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct address_space *mapping = the_inode.inode.i_mapping;
> + struct virtio_device *vdev = container_of(dev, struct virtio_device,
> + dev);
> + struct virtio_balloon *vb = vdev->priv;
> + unsigned long long value = 0;
> + if (attr == &dev_attr_total_memory)
> + value = vb->stats[VIRTIO_BALLOON_S_MEMTOT].val;
> + else if (attr == &dev_attr_free_memory)
> + value = vb->stats[VIRTIO_BALLOON_S_MEMFREE].val;
> + else if (attr == &dev_attr_target_pages)
> + value = config_pages(vb);
> + else if (attr == &dev_attr_actual_pages)
> + value = cpu_to_le32((mapping ? mapping->nrpages : 0));
> + return sprintf(buf, "%llu\n", value);
> +}
> +
> +static int virtballoon_probe(struct virtio_device *vdev)
> +{
> + struct virtio_balloon *vb;
> + struct virtqueue *vq[1];
> + vq_callback_t *callback = balloon_ack;
> + const char *name = "inflate";
> + int err;
> +
> + vdev->priv = vb = kmalloc(sizeof(*vb), GFP_KERNEL);
> + if (!vb) {
> + err = -ENOMEM;
> + goto out;
> + }
> +
> + init_waitqueue_head(&vb->config_change);
> + vb->vdev = vdev;
> +
> + /* We use one virtqueue: inflate */
> + err = vdev->config->find_vqs(vdev, 1, vq, &callback, &name);
> + if (err)
> + goto out_free_vb;
> +
> + vb->inflate_vq = vq[0];
> +
> + err = sysfs_create_group(&vdev->dev.kobj, &virtballoon_attr_group);
> + if (err) {
> + pr_err("Failed to create virtballoon sysfs node\n");
> + goto out_free_vb;
> + }
> +
> + vb->last_scan_page_array = 0;
> + vb->last_reclaim = 0;
> + the_inode.vb = vb;
> +
> + vb->thread = kthread_run(balloon, vb, "vballoon");
> + if (IS_ERR(vb->thread)) {
> + err = PTR_ERR(vb->thread);
> + goto out_del_vqs;
> + }
> +
> + return 0;
> +
> +out_del_vqs:
> + vdev->config->del_vqs(vdev);
> +out_free_vb:
> + kfree(vb);
> +out:
> + return err;
> +}
> +
> +static void __devexit virtballoon_remove(struct virtio_device *vdev)
> +{
> + struct virtio_balloon *vb = vdev->priv;
> +
> + kthread_stop(vb->thread);
> +
> + sysfs_remove_group(&vdev->dev.kobj, &virtballoon_attr_group);
> +
> + /* Now we reset the device so we can clean up the queues. */
> + vdev->config->reset(vdev);
> +
> + vdev->config->del_vqs(vdev);
> + kfree(vb);
> +}
> +
> +static struct virtio_driver virtio_balloon_driver = {
> + .feature_table = NULL,
> + .feature_table_size = 0,
> + .driver.name = KBUILD_MODNAME,
> + .driver.owner = THIS_MODULE,
> + .id_table = id_table,
> + .probe = virtballoon_probe,
> + .remove = __devexit_p(virtballoon_remove),
> + .config_changed = virtballoon_changed,
> +};
> +
> +static int __init init(void)
> +{
> + int err = register_filesystem(&balloon_fs_type);
> + if (err)
> + goto out;
> +
> + balloon_mnt = kern_mount(&balloon_fs_type);
> + if (IS_ERR(balloon_mnt)) {
> + err = PTR_ERR(balloon_mnt);
> + goto out_filesystem;
> + }
> +
> + err = register_virtio_driver(&virtio_balloon_driver);
> + if (err)
> + goto out_filesystem;
> +
> + goto out;
> +
> +out_filesystem:
> + unregister_filesystem(&balloon_fs_type);
> +
> +out:
> + return err;
> +}
> +
> +static void __exit fini(void)
> +{
> + if (balloon_mnt) {
> + unregister_filesystem(&balloon_fs_type);
> + balloon_mnt = NULL;
> + }
> + unregister_virtio_driver(&virtio_balloon_driver);
> +}
> +module_init(init);
> +module_exit(fini);
> +
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +MODULE_DESCRIPTION("Virtio file (page cache-backed) balloon driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/virtio_balloon.h b/include/linux/virtio_balloon.h
> index 652dc8b..2be9a02 100644
> --- a/include/linux/virtio_balloon.h
> +++ b/include/linux/virtio_balloon.h
> @@ -41,6 +41,15 @@ struct virtio_balloon_config
> __le32 num_pages;
> /* Number of pages we've actually got in balloon. */
> __le32 actual;
> +#if defined(CONFIG_VIRTIO_FILEBALLOON) ||\
> + defined(CONFIG_VIRTIO_FILEBALLOON_MODULE)
> + /* Total pages on this system. */
> + __le32 pages_total;
> + /* Free pages on this system. */
> + __le32 pages_free;
> + /* If the device needs pages_total/pages_free updated. */
> + __le32 need_stats;
> +#endif
> };
>
> #define VIRTIO_BALLOON_S_SWAP_IN 0 /* Amount of memory swapped in */
> diff --git a/include/linux/virtio_ids.h b/include/linux/virtio_ids.h
> index 7529b85..2f081d7 100644
> --- a/include/linux/virtio_ids.h
> +++ b/include/linux/virtio_ids.h
> @@ -37,5 +37,6 @@
> #define VIRTIO_ID_RPMSG 7 /* virtio remote processor messaging */
> #define VIRTIO_ID_SCSI 8 /* virtio scsi */
> #define VIRTIO_ID_9P 9 /* 9p virtio console */
> +#define VIRTIO_ID_FILE_BALLOON 10 /* virtio file-backed balloon */
>
> #endif /* _LINUX_VIRTIO_IDS_H */
> --
> 1.7.7.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/