Skip to contentSkip to conversation

Replace kmalloc with vmalloc in seq_files

PATCH In review Colin Cross 6 participants 13 messages Sep 22, 2011 – Sep 23, 2011

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover Replace kmalloc with vmalloc in seq_files

When a seq_file is implemented with single_open, the show function will be called with a kmalloc'd PAGE_SIZE buffer. If the show function produces more data than can fit in the buffer, the buffer will be thrown away, and the show function will be called again with a buffer twice as large. This process repeats until the show function does not overflow the buffer, or kmalloc fails.

seq_files are often used for debugging data. When the system is under memory pressure, and dumping debugging data starts trying to allocate large physically contiguous buffers, it often makes the problem worse.

Since there is no need for a physically contiguous buffer, this patch set converts the kmalloc'd buffers into vmallocs. There are two seq_file users that kmalloc buffer and place it directly into the seq_file structure, later to be freed by seq_release. Convert those to call a new seq_reserve function that will do the correct allocation for them.

#1 lore raw

PATCH 1/3 fs: seq_file: add seq_reserve

Adds a seq_reserve function to allow users of the seq_file interface to increase the initial size of the buffer. Avoids repeated allocations and calls to the show function in cases where large buffers of known size are needed.

---
 fs/seq_file.c            |   30 ++++++++++++++++++++++++++++++
 include/linux/seq_file.h |    1 +
 2 files changed, 31 insertions(+), 0 deletions(-)
fs/seq_file.c +30 -0
index 05d6b0e..8d9778c 100644
@@ -62,6 +62,36 @@ int seq_open(struct file *file, const struct seq_operations *op)
 }
 EXPORT_SYMBOL(seq_open);
 
+/**
+ *	seq_reserve -	increase initial buffer for sequential file
+ *	@m:	target buffer
+ *	@size: size in bytes to reserve
+ *
+ *	seq_reserve() increases the initial size of the seq_file buffer.  Can
+ *	be used to avoid future allocations and repeated calls to the show
+ *	function for large buffers of known size.
+ *	Returns 0 on success, or -ENOMEM if the buffer cannot be allocated.
+ */
+int seq_reserve(struct seq_file *m, size_t size)
+{
+	void *buf;
+
+	/* don't ask for more than the kmalloc() max size */
+	if (size > KMALLOC_MAX_SIZE)
+		size = KMALLOC_MAX_SIZE;
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	kfree(m->buf);
+	m->buf = buf;
+	m->size = size;
+
+	return 0;
+}
+EXPORT_SYMBOL(seq_reserve);
+
 static int traverse(struct seq_file *m, loff_t offset)
 {
 	loff_t pos = 0, index;
include/linux/seq_file.h +1 -0
index 03c0232..ad93319 100644
@@ -75,6 +75,7 @@ static inline void seq_commit(struct seq_file *m, int num)
 
 char *mangle_path(char *s, char *p, char *esc);
 int seq_open(struct file *, const struct seq_operations *);
+int seq_reserve(struct seq_file *m, size_t size);
 ssize_t seq_read(struct file *, char __user *, size_t, loff_t *);
 loff_t seq_lseek(struct file *, loff_t, int);
 int seq_release(struct inode *, struct file *);
Signature · 1 line
1.7.4.1
#2 lore raw

Re: [PATCH 1/3] fs: seq_file: add seq_reserve

Quoted 4 lines · On Thu, 22 Sep 2011 13:57:07 -0700 Colin Cross <[email protected]> wrote:

Adds a seq_reserve function to allow users of the seq_file interface to increase the initial size of the buffer. Avoids repeated allocations and calls to the show function in cases where large buffers of known size are needed.

Would it not be better to have something akin to

	void seq_set_buffer(struct seq_file *m, void *ptr, size_t size)
	{
		m->buf = ptr;
		m->size = size;
		m->private = 1;
	}

and let the caller simply provide a buffer by any means it wishes (even statically) and free it again itself. That keeps the caller fully in control when it wishes to be.

Alan

#3 lore raw

Re: [PATCH 1/3] fs: seq_file: add seq_reserve

Quoted 18 lines · Alan Cox
Quoted 4 lines · On Thu, 22 Sep 2011 13:57:07 -0700 Colin Cross <[email protected]> wrote:

Adds a seq_reserve function to allow users of the seq_file interface to increase the initial size of the buffer.  Avoids repeated allocations and calls to the show function in cases where large buffers of known size are needed.

Would it not be better to have something akin to

       void seq_set_buffer(struct seq_file *m, void *ptr, size_t size) {
               m->buf = ptr;
               m->size = size;
               m->private = 1;
       }

Quoted 3 lines

and let the caller simply provide a buffer by any means it wishes (even statically) and free it again itself. That keeps the caller fully in control when it wishes to be.

What would happen when seq_read overflowed the buffer? It wouldn't be able to reallocate it.
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#4 lore raw

PATCH 2/3 sched_stats: use the new seq_reserve function

In reply to Colin Cross

Instead of writing directly to private members of the seq_file struct, use the new seq_reserve function to specify the predicted size of the seq buffer.

---
 fs/proc/stat.c       |   23 ++++++++---------------
 kernel/sched_stats.h |   18 +++++++++---------
 2 files changed, 17 insertions(+), 24 deletions(-)
fs/proc/stat.c +8 -15
index 9758b65..1fdf158 100644
@@ -6,7 +6,6 @@
 #include <linux/proc_fs.h>
 #include <linux/sched.h>
 #include <linux/seq_file.h>
-#include <linux/slab.h>
 #include <linux/time.h>
 #include <linux/irqnr.h>
 #include <asm/cputime.h>
@@ -134,24 +133,18 @@ static int show_stat(struct seq_file *p, void *v)
 static int stat_open(struct inode *inode, struct file *file)
 {
 	unsigned size = 4096 * (1 + num_possible_cpus() / 32);
-	char *buf;
 	struct seq_file *m;
 	int res;
 
-	/* don't ask for more than the kmalloc() max size */
-	if (size > KMALLOC_MAX_SIZE)
-		size = KMALLOC_MAX_SIZE;
-	buf = kmalloc(size, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
 	res = single_open(file, show_stat, NULL);
-	if (!res) {
-		m = file->private_data;
-		m->buf = buf;
-		m->size = size;
-	} else
-		kfree(buf);
+	if (res)
+		return res;
+
+	m = file->private_data;
+	res = seq_reserve(m, size);
+	if (res)
+		single_release(inode, file);
+
 	return res;
 }
 
kernel/sched_stats.h +9 -9
index 331e01b..ee05143 100644
@@ -74,19 +74,19 @@ static int show_schedstat(struct seq_file *seq, void *v)
 static int schedstat_open(struct inode *inode, struct file *file)
 {
 	unsigned int size = PAGE_SIZE * (1 + num_online_cpus() / 32);
-	char *buf = kmalloc(size, GFP_KERNEL);
 	struct seq_file *m;
 	int res;
 
-	if (!buf)
-		return -ENOMEM;
 	res = single_open(file, show_schedstat, NULL);
-	if (!res) {
-		m = file->private_data;
-		m->buf = buf;
-		m->size = size;
-	} else
-		kfree(buf);
+	if (res)
+		return res;
+
+
+	m = file->private_data;
+	res = seq_reserve(m, size);
+	if (res)
+		single_release(inode, file);
+
 	return res;
 }
 
Signature · 1 line
1.7.4.1
#5 lore raw

PATCH 3/3 seq_file: convert seq buffer to vmalloc

In reply to Colin Cross

seq_files are often used for debugging. When things are going wrong due to failed physically contiguous allocations, the exponentially growing physically contiguous allocations in seq_read can make things worse. There is no need for physically contiguous memory, so switch to virtually contiguous memory instead.

---
 fs/seq_file.c |   23 ++++++++++-------------
 1 files changed, 10 insertions(+), 13 deletions(-)
fs/seq_file.c +10 -13
index 8d9778c..7045656 100644
@@ -9,6 +9,7 @@
 #include <linux/module.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
+#include <linux/vmalloc.h>
 
 #include <asm/uaccess.h>
 #include <asm/page.h>
@@ -76,15 +77,11 @@ int seq_reserve(struct seq_file *m, size_t size)
 {
 	void *buf;
 
-	/* don't ask for more than the kmalloc() max size */
-	if (size > KMALLOC_MAX_SIZE)
-		size = KMALLOC_MAX_SIZE;
-
-	buf = kmalloc(size, GFP_KERNEL);
+	buf = vmalloc(size);
 	if (!buf)
 		return -ENOMEM;
 
-	kfree(m->buf);
+	vfree(m->buf);
 	m->buf = buf;
 	m->size = size;
 
@@ -106,7 +103,7 @@ static int traverse(struct seq_file *m, loff_t offset)
 		return 0;
 	}
 	if (!m->buf) {
-		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
+		m->buf = vmalloc(m->size = PAGE_SIZE);
 		if (!m->buf)
 			return -ENOMEM;
 	}
@@ -145,8 +142,8 @@ static int traverse(struct seq_file *m, loff_t offset)
 
 Eoverflow:
 	m->op->stop(m, p);
-	kfree(m->buf);
-	m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
+	vfree(m->buf);
+	m->buf = vmalloc(m->size <<= 1);
 	return !m->buf ? -ENOMEM : -EAGAIN;
 }
 
@@ -199,7 +196,7 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 	m->version = file->f_version;
 	/* grab buffer if we didn't have one */
 	if (!m->buf) {
-		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
+		m->buf = vmalloc(m->size = PAGE_SIZE);
 		if (!m->buf)
 			goto Enomem;
 	}
@@ -239,8 +236,8 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 		if (m->count < m->size)
 			goto Fill;
 		m->op->stop(m, p);
-		kfree(m->buf);
-		m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
+		vfree(m->buf);
+		m->buf = vmalloc(m->size <<= 1);
 		if (!m->buf)
 			goto Enomem;
 		m->count = 0;
@@ -355,7 +352,7 @@ EXPORT_SYMBOL(seq_lseek);
 int seq_release(struct inode *inode, struct file *file)
 {
 	struct seq_file *m = file->private_data;
-	kfree(m->buf);
+	vfree(m->buf);
 	kfree(m);
 	return 0;
 }
Signature · 1 line
1.7.4.1
#6 lore raw

Re: [PATCH 3/3] seq_file: convert seq buffer to vmalloc

Quoted 5 lines · Colin Cross

seq_files are often used for debugging. When things are going wrong due to failed physically contiguous allocations, the exponentially growing physically contiguous allocations in seq_read can make things worse. There is no need for physically contiguous memory, so switch to virtually contiguous memory instead.

vmalloc's are relatively expensive.
Perhaps use kmalloc when appropriate instead?

[]

fs/seq_file.c int seq_reserve(struct seq_file *m, size_t size) · line 82

Quoted 8 lines
fs/seq_file.c +1 -5
-	/* don't ask for more than the kmalloc() max size */
-	if (size > KMALLOC_MAX_SIZE)
-		size = KMALLOC_MAX_SIZE;
-
-	buf = kmalloc(size, GFP_KERNEL);
+	buf = vmalloc(size);
 	if (!buf)
 		return -ENOMEM;

if (size > KMALLOC_MAX_SIZE)
buf = vmalloc(size, GFP_KERNEL)
else
buf = kmalloc(size, GFP_KERNEL);

Review

Quoted 1 line
(quoted diff) +1 -0
+	vfree(m->buf);
	if (m->size > KMALLOC_MAX_SIZE)
		vfree(m->buf);
	else
		kfree(m->buf);

fs/seq_file.c static int traverse(struct seq_file *m, loff_t offset) · line 106

Quoted 9 lines
fs/seq_file.c +1 -1
 	m->buf = buf;
 	m->size = size;
@@ -106,7 +103,7 @@ static int traverse(struct seq_file *m, loff_t offset)
 		return 0;
 	}
 	if (!m->buf) {
-		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
+		m->buf = vmalloc(m->size = PAGE_SIZE);

embedding the set of m->size like this is ugly.

[do the same as above kmalloc/vmalloc based on size]

etc.

#7 lore raw

Re: [PATCH 3/3] seq_file: convert seq buffer to vmalloc

Quoted 9 lines · Joe Perches
Quoted 5 lines · Colin Cross

seq_files are often used for debugging.  When things are going wrong due to failed physically contiguous allocations, the exponentially growing physically contiguous allocations in seq_read can make things worse.  There is no need for physically contiguous memory, so switch to virtually contiguous memory instead.

vmalloc's are relatively expensive.
Perhaps use kmalloc when appropriate instead?

Talking about allocation efficiency in the context of seq_files is silly - for a KMALLOC_MAX_SIZE buffer (8MB), you are already going to allocate 11 times, with increasingly large buffers, and are likely to fail long before you ever get to KMALLOC_MAX_SIZE.

Quoted 10 lines

[]

Quoted 8 lines

-     /* don't ask for more than the kmalloc() max size */
-     if (size > KMALLOC_MAX_SIZE)
-             size = KMALLOC_MAX_SIZE;
-
-     buf = kmalloc(size, GFP_KERNEL);
+     buf = vmalloc(size);
      if (!buf)
              return -ENOMEM;

Quoted 4 lines

       if (size > KMALLOC_MAX_SIZE)
               buf = vmalloc(size, GFP_KERNEL)
       else
               buf = kmalloc(size, GFP_KERNEL);

KMALLOC_MAX_SIZE is far too big for this. KMALLOC_MAX_SIZE is the maximum allocation that is theoretically possible, but will fail if you don't have any completely empty pageblocks. If I were to put a size here, it would probably be order 3, but even that can easily fail on a system that has under memory pressure.

Quoted 10 lines
Quoted 1 line

+     vfree(m->buf);

       if (m->size > KMALLOC_MAX_SIZE)
               vfree(m->buf);
       else
               kfree(m->buf);

Quoted 3 lines

      m->buf = buf;
      m->size = size;

Quoted 8 lines

fs/seq_file.c static int traverse(struct seq_file *m, loff_t offset) · line 106

Quoted 6 lines
fs/seq_file.c +1 -1
@@ -106,7 +103,7 @@ static int traverse(struct seq_file *m, loff_t offset)
               return 0;
       }
       if (!m->buf) {
-             m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
+             m->buf = vmalloc(m->size = PAGE_SIZE);

embedding the set of m->size like this is ugly.

I agree, but it was there in the original file. I could clean it up, but that should be in a separate patch.

Quoted 5 lines

[do the same as above kmalloc/vmalloc based on size]

etc.

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw

Re: [PATCH 3/3] seq_file: convert seq buffer to vmalloc

2011/9/23 Colin Cross <[email protected]>:

Quoted 29 lines · Joe Perches
Quoted 9 lines · Colin Cross
Quoted 5 lines

seq_files are often used for debugging.  When things are going wrong due to failed physically contiguous allocations, the exponentially growing physically contiguous allocations in seq_read can make things worse.  There is no need for physically contiguous memory, so switch to virtually contiguous memory instead.

vmalloc's are relatively expensive.
Perhaps use kmalloc when appropriate instead?

Talking about allocation efficiency in the context of seq_files is silly - for a KMALLOC_MAX_SIZE buffer (8MB), you are already going to allocate 11 times, with increasingly large buffers, and are likely to fail long before you ever get to KMALLOC_MAX_SIZE.

Quoted 10 lines

[]

Quoted 8 lines

-     /* don't ask for more than the kmalloc() max size */
-     if (size > KMALLOC_MAX_SIZE)
-             size = KMALLOC_MAX_SIZE;
-
-     buf = kmalloc(size, GFP_KERNEL);
+     buf = vmalloc(size);
      if (!buf)
              return -ENOMEM;

Quoted 4 lines

       if (size > KMALLOC_MAX_SIZE)
               buf = vmalloc(size, GFP_KERNEL)
       else
               buf = kmalloc(size, GFP_KERNEL);

Quoted 5 lines

KMALLOC_MAX_SIZE is far too big for this.  KMALLOC_MAX_SIZE is the maximum allocation that is theoretically possible, but will fail if you don't have any completely empty pageblocks.  If I were to put a size here, it would probably be order 3, but even that can easily fail on a system that has under memory pressure.

-> I agree. I think that vmalloc is better than kmalloc. because you can face page allocation fail by high order page.

Quoted 33 lines
Quoted 11 lines
Quoted 2 lines

+     vfree(m->buf);

       if (m->size > KMALLOC_MAX_SIZE)
               vfree(m->buf);
       else
               kfree(m->buf);

Quoted 3 lines

      m->buf = buf;
      m->size = size;

Quoted 8 lines

fs/seq_file.c static int traverse(struct seq_file *m, loff_t offset) · line 106

Quoted 6 lines
fs/seq_file.c +1 -1
@@ -106,7 +103,7 @@ static int traverse(struct seq_file *m, loff_t offset)
               return 0;
       }
       if (!m->buf) {
-             m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
+             m->buf = vmalloc(m->size = PAGE_SIZE);

embedding the set of m->size like this is ugly.

I agree, but it was there in the original file.  I could clean it up, but that should be in a separate patch.

Quoted 5 lines

[do the same as above kmalloc/vmalloc based on size]

etc.

--
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/

#9 lore raw

Re: [PATCH 3/3] seq_file: convert seq buffer to vmalloc

In reply to Joe Perches
Quoted 8 lines · Joe Perches
Quoted 5 lines · Colin Cross

seq_files are often used for debugging. When things are going wrong due to failed physically contiguous allocations, the exponentially growing physically contiguous allocations in seq_read can make things worse. There is no need for physically contiguous memory, so switch to virtually contiguous memory instead.

vmalloc's are relatively expensive.

And very limited on certain archs..

Quoted 16 lines

Perhaps use kmalloc when appropriate instead?

[]

fs/seq_file.c int seq_reserve(struct seq_file *m, size_t size) · line 82

Quoted 8 lines
fs/seq_file.c +1 -5
-	/* don't ask for more than the kmalloc() max size */
-	if (size > KMALLOC_MAX_SIZE)
-		size = KMALLOC_MAX_SIZE;
-
-	buf = kmalloc(size, GFP_KERNEL);
+	buf = vmalloc(size);
 	if (!buf)
 		return -ENOMEM;

if (size > KMALLOC_MAX_SIZE)
buf = vmalloc(size, GFP_KERNEL)
else
buf = kmalloc(size, GFP_KERNEL)

There's wrappers for this stuff IIRC, that said all this is a horrible idea. Just avoid the situation instead of coping with it.

#10 lore raw
In reply to Colin Cross
Quoted 5 lines · Colin Cross

seq_files are often used for debugging data. When the system is under memory pressure, and dumping debugging data starts trying to allocate large physically contiguous buffers, it often makes the problem worse.

Shouldn't you be using ->next instead of dumping everything in a single seqop? That way it can return partial buffers and keep state etc.

#11 lore raw
In reply to Colin Cross
Quoted 3 lines · Colin Cross

seq_files are often used for debugging data. When the system is under memory pressure, and dumping debugging data starts trying to allocate large physically contiguous buffers, it often makes the problem worse.

Please fix the instances that you see issues with by using the full seq_file interface which was designed for his instead of the simplified "single" interface that is only designed for small amounts of data.

#12 lore raw
Quoted 8 lines · Christoph Hellwig
Quoted 3 lines · Colin Cross

seq_files are often used for debugging data.  When the system is under memory pressure, and dumping debugging data starts trying to allocate large physically contiguous buffers, it often makes the problem worse.

Please fix the instances that you see issues with by using the full seq_file interface which was designed for his instead of the simplified "single" interface that is only designed for small amounts of data.

You're probably right, but it's not always easy. For files that need to show an atomic snapshot of some data, the data needs to go somewhere. It would be possible to allocate a smaller data structure and atomically copy a snapshot into it, then use the iterator interface to push the data out to userspace, but that's a lot harder than a vmalloc and a few seq_printfs.

If seq_file used a list of buffers, it could allocate much smaller chunks (a page?), and add new buffers to the list instead of reallocating and calling the read function again.
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html