diff mbox

fs/seq_file: remove kmalloc(ops) for single_open seqfiles

Message ID 20180720102952.30935-1-vbabka@suse.cz (mailing list archive)
State New, archived
Headers show

Commit Message

Vlastimil Babka July 20, 2018, 10:29 a.m. UTC
single_open() currently allocates seq_operations with kmalloc(). This is
suboptimal, because that's four pointers, of which three are constant, and
only the 'show' op differs. We also have to be careful to use single_release()
to avoid leaking the ops structure.

Instead of this we can have a fixed single_show() function and constant ops
structure for these seq_files. We can store the pointer to the 'show' op as
a new field of struct seq_file. That's also not terribly elegant, because the
field is there also for non-single_open() seq files, but it's a single pointer
in an already existing (and already relatively large) structure instead of
an extra kmalloc of four pointers, so the tradeoff is OK.

Suggested-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
---
 Documentation/filesystems/seq_file.txt |  5 +++-
 fs/seq_file.c                          | 40 ++++++++++++--------------
 include/linux/seq_file.h               |  5 ++--
 3 files changed, 25 insertions(+), 25 deletions(-)

Comments

Al Viro July 20, 2018, 11:15 a.m. UTC | #1
On Fri, Jul 20, 2018 at 12:29:52PM +0200, Vlastimil Babka wrote:
> single_open() currently allocates seq_operations with kmalloc(). This is
> suboptimal, because that's four pointers, of which three are constant, and
> only the 'show' op differs. We also have to be careful to use single_release()
> to avoid leaking the ops structure.
> 
> Instead of this we can have a fixed single_show() function and constant ops
> structure for these seq_files. We can store the pointer to the 'show' op as
> a new field of struct seq_file. That's also not terribly elegant, because the
> field is there also for non-single_open() seq files, but it's a single pointer
> in an already existing (and already relatively large) structure instead of
> an extra kmalloc of four pointers, so the tradeoff is OK.

... except that piling indirect calls is costly and ->show() is called a lot more
than open() is.
diff mbox

Patch

diff --git a/Documentation/filesystems/seq_file.txt b/Documentation/filesystems/seq_file.txt
index 9de4303201e1..ed61495abee8 100644
--- a/Documentation/filesystems/seq_file.txt
+++ b/Documentation/filesystems/seq_file.txt
@@ -335,4 +335,7 @@  When output time comes, the show() function will be called once. The data
 value given to single_open() can be found in the private field of the
 seq_file structure. When using single_open(), the programmer should use
 single_release() instead of seq_release() in the file_operations structure
-to avoid a memory leak.
+to avoid a memory leak. Note that the implementation has changed and current
+kernels will not leak anymore, but it's better to keep using single_release()
+in case the implementation details change again.
+
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 4cc090b50cc5..3fd2ded04d93 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -563,22 +563,27 @@  static void single_stop(struct seq_file *p, void *v)
 {
 }
 
+static int single_show(struct seq_file *p, void *v)
+{
+	return p->single_show_op(p, v);
+}
+
+static const struct seq_operations single_seq_op = {
+	.start	= single_start,
+	.next	= single_next,
+	.stop	= single_stop,
+	.show	= single_show
+};
+
 int single_open(struct file *file, int (*show)(struct seq_file *, void *),
 		void *data)
 {
-	struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
-	int res = -ENOMEM;
-
-	if (op) {
-		op->start = single_start;
-		op->next = single_next;
-		op->stop = single_stop;
-		op->show = show;
-		res = seq_open(file, op);
-		if (!res)
-			((struct seq_file *)file->private_data)->private = data;
-		else
-			kfree(op);
+	int res;
+
+	res = seq_open(file, &single_seq_op);
+	if (!res) {
+		((struct seq_file *)file->private_data)->private = data;
+		((struct seq_file *)file->private_data)->single_show_op = show;
 	}
 	return res;
 }
@@ -602,15 +607,6 @@  int single_open_size(struct file *file, int (*show)(struct seq_file *, void *),
 }
 EXPORT_SYMBOL(single_open_size);
 
-int single_release(struct inode *inode, struct file *file)
-{
-	const struct seq_operations *op = ((struct seq_file *)file->private_data)->op;
-	int res = seq_release(inode, file);
-	kfree(op);
-	return res;
-}
-EXPORT_SYMBOL(single_release);
-
 int seq_release_private(struct inode *inode, struct file *file)
 {
 	struct seq_file *seq = file->private_data;
diff --git a/include/linux/seq_file.h b/include/linux/seq_file.h
index a121982af0f5..c9a70c584a7d 100644
--- a/include/linux/seq_file.h
+++ b/include/linux/seq_file.h
@@ -24,9 +24,10 @@  struct seq_file {
 	u64 version;
 	struct mutex lock;
 	const struct seq_operations *op;
-	int poll_event;
+	int (*single_show_op)(struct seq_file *, void *);
 	const struct file *file;
 	void *private;
+	int poll_event;
 };
 
 struct seq_operations {
@@ -140,7 +141,7 @@  int seq_path_root(struct seq_file *m, const struct path *path,
 
 int single_open(struct file *, int (*)(struct seq_file *, void *), void *);
 int single_open_size(struct file *, int (*)(struct seq_file *, void *), void *, size_t);
-int single_release(struct inode *, struct file *);
+#define single_release	seq_release
 void *__seq_open_private(struct file *, const struct seq_operations *, int);
 int seq_open_private(struct file *, const struct seq_operations *, int);
 int seq_release_private(struct inode *, struct file *);