7.2 Examples
1. Read Something in Kernel Space
Following example is a a Loadable Kernel Module (LKM) that creates a file in /proc
so you can read something from the kernel space using user-space commands like cat
.
LKM That Adds a File to /proc
This module creates a file /proc/helloproc
that you can read with cat
.
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/uaccess.h> // for copy_to_user()
#define PROC_NAME "helloproc"#define MESSAGE "Hello from the kernel!\n"
static ssize_t proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return simple_read_from_buffer(buf, count, ppos, MESSAGE, strlen(MESSAGE));}
static struct proc_ops proc_file_ops = { .proc_read = proc_read,};
static int __init hello_init(void) { proc_create(PROC_NAME, 0, NULL, &proc_file_ops); printk(KERN_INFO "/proc/%s created\n", PROC_NAME); return 0;}
static void __exit hello_exit(void) { remove_proc_entry(PROC_NAME, NULL); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);}
module_init(hello_init);module_exit(hello_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("You");MODULE_DESCRIPTION("Kernel module with /proc file");
π Makefile (same as before)
obj-m += helloproc.o
all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
π Usage
makesudo insmod helloproc.kocat /proc/helloproc # Output: Hello from the kernel!sudo rmmod helloproc
You can also run dmesg | tail
to see kernel logs.
1.1 Extending the Example
Now your LKM will read and write from a /proc
file. Youβll be able to do:
echo "Hi kernel!" > /proc/helloproccat /proc/helloproc
and see your message echoed back.
π Read/Write LKM Using /proc
This module creates /proc/helloproc
, and stores a message that you can update with echo
.
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/uaccess.h>
#define PROC_NAME "helloproc"#define MAX_LEN 100
static char message[MAX_LEN] = "Hello from kernel!\n";static ssize_t message_len = 0;
static ssize_t proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return simple_read_from_buffer(buf, count, ppos, message, message_len);}
static ssize_t proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { if (count > MAX_LEN) count = MAX_LEN; if (copy_from_user(message, buf, count)) return -EFAULT; message_len = count; return count;}
static struct proc_ops proc_file_ops = { .proc_read = proc_read, .proc_write = proc_write,};
static int __init hello_init(void) { proc_create(PROC_NAME, 0666, NULL, &proc_file_ops); printk(KERN_INFO "/proc/%s created (RW)\n", PROC_NAME); return 0;}
static void __exit hello_exit(void) { remove_proc_entry(PROC_NAME, NULL); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);}
module_init(hello_init);module_exit(hello_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("You");MODULE_DESCRIPTION("Proc RW Kernel Module");
π Makefile
obj-m += helloproc_rw.o
all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
π§ͺ Try it out:
makesudo insmod helloproc_rw.ko
# Read default messagecat /proc/helloproc
# Write a new messageecho "Hi kernel!" | sudo tee /proc/helloproc
# Read the updated messagecat /proc/helloproc
# Clean upsudo rmmod helloproc_rw
1.2 π§© Kernel Module: Responds to "uptime"
Now weβll make a user-space C program that talks to our kernel module via /proc/helloproc
. Weβll also change the kernel module a bit to show the current uptime in seconds whenever a user writes "uptime"
to it.
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/uaccess.h>#include <linux/jiffies.h>#include <linux/ktime.h>
#define PROC_NAME "helloproc"#define MAX_LEN 100
static char message[MAX_LEN];static ssize_t message_len;
static ssize_t proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return simple_read_from_buffer(buf, count, ppos, message, message_len);}
static ssize_t proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char input[MAX_LEN];
if (count > MAX_LEN - 1) count = MAX_LEN - 1; if (copy_from_user(input, buf, count)) return -EFAULT; input[count] = '\0';
if (strncmp(input, "uptime", 6) == 0) { unsigned long uptime = jiffies / HZ; message_len = snprintf(message, MAX_LEN, "Uptime: %lu seconds\n", uptime); } else { message_len = snprintf(message, MAX_LEN, "You said: %s", input); }
return count;}
static struct proc_ops proc_file_ops = { .proc_read = proc_read, .proc_write = proc_write,};
static int __init hello_init(void) { proc_create(PROC_NAME, 0666, NULL, &proc_file_ops); printk(KERN_INFO "/proc/%s created (uptime enabled)\n", PROC_NAME); return 0;}
static void __exit hello_exit(void) { remove_proc_entry(PROC_NAME, NULL); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);}
module_init(hello_init);module_exit(hello_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("You");MODULE_DESCRIPTION("Proc RW Kernel Module with uptime");
π§βπ» User-Space C Program
#include <stdio.h>#include <stdlib.h>#include <string.h>
int main() { const char *path = "/proc/helloproc"; FILE *fp;
// Write "uptime" to kernel fp = fopen(path, "w"); if (!fp) { perror("fopen write"); return 1; } fprintf(fp, "uptime\n"); fclose(fp);
// Read response fp = fopen(path, "r"); if (!fp) { perror("fopen read"); return 1; }
char buffer[128]; while (fgets(buffer, sizeof(buffer), fp)) { printf("Kernel says: %s", buffer); }
fclose(fp); return 0;}
Compile it:
gcc userprog.c -o userprog
π§ͺ Try It Out
-
Compile and load your module:
Terminal window makesudo insmod helloproc_uptime.ko -
Run the user-space program:
Terminal window ./userprogβ Output:
Kernel says: Uptime: 12345 seconds -
Cleanup:
Terminal window sudo rmmod helloproc_uptime
1.3 Creating a Device File
Youβre going full kernel hacker mode now π. Letβs level up from /proc
to creating a real device file in /dev
, using the misc
device API, which is much closer to how real drivers work.
π¦ Whatβs a misc
device?
Itβs a simpler way to register a character device. You get:
- A real file under
/dev/mydevice
- Full control over read, write, ioctl, etc.
- Clean and minimal boilerplate
π§ͺ Goal
/dev/mydevice
that responds to input
Behavior:
- Write
"uptime"
β responds with system uptime - Write anything else β echoes it back
πΈ Kernel Module: mydevice.c
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/miscdevice.h>#include <linux/uaccess.h>#include <linux/jiffies.h>
#define BUFFER_SIZE 256
static char message[BUFFER_SIZE];static ssize_t message_len;
static ssize_t device_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return simple_read_from_buffer(buf, count, ppos, message, message_len);}
static ssize_t device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char input[BUFFER_SIZE];
if (count >= BUFFER_SIZE) count = BUFFER_SIZE - 1;
if (copy_from_user(input, buf, count)) return -EFAULT;
input[count] = '\0';
if (strncmp(input, "uptime", 6) == 0) { unsigned long uptime = jiffies / HZ; message_len = snprintf(message, BUFFER_SIZE, "Uptime: %lu seconds\n", uptime); } else { message_len = snprintf(message, BUFFER_SIZE, "You said: %s", input); }
return count;}
static struct file_operations fops = { .owner = THIS_MODULE, .read = device_read, .write = device_write,};
static struct miscdevice my_misc_device = { .minor = MISC_DYNAMIC_MINOR, .name = "mydevice", .fops = &fops, .mode = 0666};
static int __init mydevice_init(void) { int ret = misc_register(&my_misc_device); if (ret) printk(KERN_ERR "Failed to register misc device\n"); else printk(KERN_INFO "/dev/mydevice registered\n"); return ret;}
static void __exit mydevice_exit(void) { misc_deregister(&my_misc_device); printk(KERN_INFO "/dev/mydevice unregistered\n");}
module_init(mydevice_init);module_exit(mydevice_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("You");MODULE_DESCRIPTION("Simple misc device example");
π Makefile
obj-m += mydevice.o
all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
π§ͺ Run It!
makesudo insmod mydevice.ko
# Check that the device was createdls -l /dev/mydevice
# Interact with itecho "uptime" | sudo tee /dev/mydevicecat /dev/mydevice
echo "hello from user" | sudo tee /dev/mydevicecat /dev/mydevice
sudo rmmod mydevice
π§βπ» Want to write a user-space C client for /dev/mydevice
too?
Or maybe want bidirectional communication with a user-space daemon (e.g., using ioctl
) or notifications from the kernel (poll
, select
, or fasync
)?
2. Extending netfilter
#include <linux/kernel.h>#include <linux/module.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4.h>
static struct nf_hook_ops netfilter_ops_in;static struct nf_hook_ops netfilter_ops_out;
unsigned int main_hook(void* priv, struct sk_buff* skb, const struct nf_hook_state* state){ printk(KERN_INFO "packet received\n"); return NF_ACCEPT; // NF_ACCEPT allows packets, NF_DROP rejects them}
static int __init hello_init(void){ netfilter_ops_in.hook = main_hook; // What function to call? netfilter_ops_in.pf = PF_INET; // Protocol Family netfilter_ops_in.hooknum = NF_INET_PRE_ROUTING; // Incoming traffic netfilter_ops_in.priority = NF_IP_PRI_FIRST; // What priority is this hook?
netfilter_ops_out.hook = main_hook; netfilter_ops_out.pf = PF_INET; netfilter_ops_out.hooknum = NF_INET_POST_ROUTING; // Outgoing traffic netfilter_ops_out.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &netfilter_ops_in); // Register the incoming traffic hook nf_register_net_hook(&init_net, &netfilter_ops_out); // Register the outgoing traffic hook return 0;}
static void __exit hello_exit(void){ nf_unregister_net_hook(&init_net, &netfilter_ops_in); nf_unregister_net_hook(&init_net, &netfilter_ops_out);}
module_init(hello_init);module_exit(hello_exit);