[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20241205221928.1155984-2-david@ixit.cz>
Date: Thu, 5 Dec 2024 17:18:39 -0500
From: David Heidelberg <david@...t.cz>
To: Andrew Morton <akpm@...ux-foundation.org>,
David Heidelberg <david@...t.cz>,
Dmitry Rokosov <ddrokosov@...utedevices.com>
Cc: linux-kernel@...r.kernel.org
Subject: [PATCH] samples: add a sample list module
Sample list module that creates a proc entry /proc/sample_list
and registers read/write file operations.
Each write to the proc will copy the buffer
and store it in a data structure.
Each read from the proc will populate the read buffer with
list entries, up to the read length (buffer size).
Signed-off-by: David Heidelberg <david@...t.cz>
---
I would love to receive feedback on this list implementation approach.
If there are any implementation mistakes, I am open to revising
this sample to make it more accurate.
samples/Kconfig | 13 +++++
samples/Makefile | 1 +
samples/list/Makefile | 2 +
samples/list/list.c | 125 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 141 insertions(+)
create mode 100644 samples/list/Makefile
create mode 100644 samples/list/list.c
diff --git ./samples/Kconfig ./samples/Kconfig
index b288d9991d27..7bc9c2c85599 100644
--- ./samples/Kconfig
+++ ./samples/Kconfig
@@ -128,6 +128,19 @@ config SAMPLE_RPMSG_CLIENT
to communicate with an AMP-configured remote processor over
the rpmsg bus.
+config SAMPLE_LIST
+ tristate "Build list implementation sample"
+ depends on PROC_FS
+ help
+ Build a sample list module that creates a proc entry
+ /proc/sample_list and registers read/write file operations.
+
+ Each write to the proc will copy the buffer
+ and store it in a data structure.
+
+ Each read from the proc will populate the read buffer with
+ list entries, up to the read length (buffer size).
+
config SAMPLE_LIVEPATCH
tristate "Build live patching samples -- loadable modules only"
depends on LIVEPATCH && m
diff --git ./samples/Makefile ./samples/Makefile
index b85fa64390c5..68158af60c96 100644
--- ./samples/Makefile
+++ ./samples/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/
obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/
obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/
subdir-$(CONFIG_SAMPLE_LANDLOCK) += landlock
+obj-$(CONFIG_SAMPLE_LIST) += list/
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/
subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/
diff --git ./samples/list/Makefile ./samples/list/Makefile
new file mode 100644
index 000000000000..8e77d97aaa9c
--- /dev/null
+++ ./samples/list/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SAMPLE_LIST) += list.o
diff --git ./samples/list/list.c ./samples/list/list.c
new file mode 100644
index 000000000000..1db693ee65b3
--- /dev/null
+++ ./samples/list/list.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * list.c - Kernel list sample code
+ *
+ * Copyright 2024 David Heidelberg <david@...t.cz>
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+
+#define PROC_NAME "list_sample"
+#define BUFFER_SIZE 128
+
+LIST_HEAD(data_list);
+
+struct data_node {
+ struct list_head list;
+ char data[BUFFER_SIZE];
+};
+
+static struct proc_dir_entry *proc_entry;
+
+static ssize_t proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos)
+{
+ struct data_node *new_node;
+
+ if (count >= BUFFER_SIZE)
+ count = BUFFER_SIZE - 1;
+
+ new_node = kmalloc(sizeof(*new_node), GFP_KERNEL);
+ if (!new_node)
+ return -ENOMEM;
+
+ if (strncpy_from_user(new_node->data, buffer, count) < 0) {
+ kfree(new_node);
+ return -EFAULT;
+ }
+
+ list_add_tail(&new_node->list, &data_list);
+
+ return count;
+}
+
+/*
+ * Several copy_to_user() is a bit less efficient
+ * comparing to list_for_each_entry_safe, but makes code simpler.
+ */
+static ssize_t proc_read(struct file *file, char __user *buffer, size_t count, loff_t *pos)
+{
+ static struct list_head *start, *cur;
+ struct data_node *node;
+ size_t data_len, batch_len = 0;
+ char temp_buf[2 * BUFFER_SIZE] = {0};
+
+ if (*pos == 0) {
+ start = &data_list;
+ cur = &data_list;
+ }
+
+ if (*pos != 0 && cur->next == start)
+ return 0;
+
+ if (list_empty(cur))
+ return 0;
+
+ while (batch_len < BUFFER_SIZE) {
+ node = list_entry(cur->next, struct data_node, list);
+ data_len = snprintf(temp_buf + strlen(temp_buf),
+ BUFFER_SIZE - strlen(temp_buf),
+ "%s",
+ node->data);
+ if (data_len + batch_len > BUFFER_SIZE)
+ break;
+
+ batch_len += data_len;
+
+ cur = cur->next;
+ if (cur->next == start)
+ break;
+ }
+
+ if (copy_to_user(buffer, temp_buf, batch_len))
+ return -EFAULT;
+
+ *pos += batch_len;
+
+ return batch_len;
+}
+
+static const struct proc_ops proc_fops = {
+ .proc_read = proc_read,
+ .proc_write = proc_write,
+};
+
+static int __init list_sample_init(void)
+{
+ proc_entry = proc_create(PROC_NAME, 0666, NULL, &proc_fops);
+ if (!proc_entry) {
+ pr_err("Failed to create proc entry\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void __exit list_sample_exit(void)
+{
+ struct data_node *node, *tmp;
+
+ proc_remove(proc_entry);
+
+ list_for_each_entry_safe(node, tmp, &data_list, list) {
+ list_del(&node->list);
+ kfree(node);
+ }
+}
+
+module_init(list_sample_init);
+module_exit(list_sample_exit);
+
+MODULE_AUTHOR("David Heidelberg");
+MODULE_DESCRIPTION("List sample module.");
+MODULE_LICENSE("GPL v2");
--
2.45.2
Powered by blists - more mailing lists