Introduction
Kernel programming represents the pinnacle of systems programming, where C code runs with unrestricted access to hardware and system resources. Linux kernel development requires deep understanding of operating system concepts, concurrency, memory management, and hardware interactions. This comprehensive guide explores the fundamentals of kernel programming, from module basics to advanced techniques.
1. Kernel Programming Fundamentals
1.1 Understanding Kernel Space vs User Space
// User space program - runs with protections
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("Hello from user space\n");
return 0;
}
// Kernel module - runs with full privileges
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello from kernel space\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye from kernel space\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Developer");
MODULE_DESCRIPTION("A simple kernel module");
1.2 Building a Kernel Module
# Makefile for kernel module obj-m += hello.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
Module Operations:
# Build module make # Insert module sudo insmod hello.ko # List modules lsmod | grep hello # Check kernel messages dmesg | tail # Remove module sudo rmmod hello
2. Kernel Module Structure
2.1 Complete Module Template
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#define DEVICE_NAME "mychardev"
#define CLASS_NAME "mychar"
#define BUFFER_SIZE 1024
static int major_number;
static struct class* char_class = NULL;
static struct device* char_device = NULL;
static char device_buffer[BUFFER_SIZE];
static int buffer_pointer = 0;
static DEFINE_MUTEX(buffer_mutex);
// Function prototypes
static int dev_open(struct inode*, struct file*);
static int dev_release(struct inode*, struct file*);
static ssize_t dev_read(struct file*, char __user*, size_t, loff_t*);
static ssize_t dev_write(struct file*, const char __user*, size_t, loff_t*);
// File operations structure
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = dev_open,
.release = dev_release,
.read = dev_read,
.write = dev_write,
};
// Called when module is loaded
static int __init char_driver_init(void) {
printk(KERN_INFO "Initializing character driver\n");
// Allocate major number
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
printk(KERN_ALERT "Failed to register device\n");
return major_number;
}
printk(KERN_INFO "Registered with major number %d\n", major_number);
// Create device class
char_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(char_class)) {
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to create class\n");
return PTR_ERR(char_class);
}
// Create device
char_device = device_create(char_class, NULL,
MKDEV(major_number, 0),
NULL, DEVICE_NAME);
if (IS_ERR(char_device)) {
class_destroy(char_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to create device\n");
return PTR_ERR(char_device);
}
// Initialize buffer
mutex_init(&buffer_mutex);
memset(device_buffer, 0, BUFFER_SIZE);
buffer_pointer = 0;
printk(KERN_INFO "Character driver ready\n");
return 0;
}
// Called when module is unloaded
static void __exit char_driver_exit(void) {
device_destroy(char_class, MKDEV(major_number, 0));
class_destroy(char_class);
unregister_chrdev(major_number, DEVICE_NAME);
mutex_destroy(&buffer_mutex);
printk(KERN_INFO "Character driver unloaded\n");
}
// Called when device is opened
static int dev_open(struct inode* inodep, struct file* filep) {
printk(KERN_INFO "Device opened\n");
return 0;
}
// Called when device is closed
static int dev_release(struct inode* inodep, struct file* filep) {
printk(KERN_INFO "Device closed\n");
return 0;
}
// Called when data is read from device
static ssize_t dev_read(struct file* filep, char __user* buffer,
size_t len, loff_t* offset) {
int bytes_to_read;
int bytes_read = 0;
mutex_lock(&buffer_mutex);
if (*offset >= buffer_pointer) {
mutex_unlock(&buffer_mutex);
return 0; // EOF
}
bytes_to_read = min(len, (size_t)(buffer_pointer - *offset));
// Copy data to user space
if (copy_to_user(buffer, device_buffer + *offset, bytes_to_read)) {
mutex_unlock(&buffer_mutex);
return -EFAULT;
}
*offset += bytes_to_read;
bytes_read = bytes_to_read;
mutex_unlock(&buffer_mutex);
printk(KERN_INFO "Sent %d bytes to user\n", bytes_read);
return bytes_read;
}
// Called when data is written to device
static ssize_t dev_write(struct file* filep, const char __user* buffer,
size_t len, loff_t* offset) {
int bytes_to_write;
int bytes_written = 0;
mutex_lock(&buffer_mutex);
bytes_to_write = min(len, (size_t)(BUFFER_SIZE - buffer_pointer));
// Copy data from user space
if (copy_from_user(device_buffer + buffer_pointer, buffer, bytes_to_write)) {
mutex_unlock(&buffer_mutex);
return -EFAULT;
}
buffer_pointer += bytes_to_write;
bytes_written = bytes_to_write;
mutex_unlock(&buffer_mutex);
printk(KERN_INFO "Received %d bytes from user\n", bytes_written);
return bytes_written;
}
module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Developer");
MODULE_DESCRIPTION("A character device driver");
3. Memory Management in Kernel
3.1 Kernel Memory Allocation
#include <linux/slab.h>
#include <linux/vmalloc.h>
void kernel_memory_examples(void) {
// kmalloc - for small, physically contiguous memory
void *k_ptr = kmalloc(1024, GFP_KERNEL);
if (k_ptr) {
memset(k_ptr, 0, 1024);
// Use memory...
kfree(k_ptr);
}
// vmalloc - for large, virtually contiguous memory
void *v_ptr = vmalloc(1024 * 1024); // 1 MB
if (v_ptr) {
memset(v_ptr, 0, 1024 * 1024);
// Use memory...
vfree(v_ptr);
}
// GFP flags
// GFP_KERNEL - normal allocation (can sleep)
// GFP_ATOMIC - atomic allocation (cannot sleep)
// GFP_DMA - DMA-able memory
// GFP_USER - user space memory
// Allocate zeroed memory
void *z_ptr = kzalloc(1024, GFP_KERNEL);
if (z_ptr) {
kfree(z_ptr);
}
// Allocate array
int *array = kmalloc_array(100, sizeof(int), GFP_KERNEL);
if (array) {
kfree(array);
}
}
3.2 Memory Pools
#include <linux/mempool.h>
struct my_data {
int id;
char name[32];
struct list_head list;
};
static mempool_t *data_pool;
static struct kmem_cache *data_cache;
int init_memory_pool(void) {
// Create slab cache
data_cache = kmem_cache_create("my_data_cache",
sizeof(struct my_data),
0,
SLAB_HWCACHE_ALIGN,
NULL);
if (!data_cache) {
return -ENOMEM;
}
// Create memory pool
data_pool = mempool_create(10, // Minimum number of elements
mempool_alloc_slab,
mempool_free_slab,
data_cache);
if (!data_pool) {
kmem_cache_destroy(data_cache);
return -ENOMEM;
}
return 0;
}
struct my_data* allocate_data(void) {
return mempool_alloc(data_pool, GFP_KERNEL);
}
void free_data(struct my_data *data) {
mempool_free(data, data_pool);
}
void cleanup_memory_pool(void) {
mempool_destroy(data_pool);
kmem_cache_destroy(data_cache);
}
4. Concurrency and Synchronization
4.1 Mutexes and Spinlocks
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
// Mutex - for sleeping contexts
struct mutex my_mutex;
void mutex_example(void) {
mutex_init(&my_mutex);
if (mutex_lock_interruptible(&my_mutex)) {
// Signal interrupted
return;
}
// Critical section
// ...
mutex_unlock(&my_mutex);
}
// Spinlock - for atomic contexts (interrupt handlers, etc.)
DEFINE_SPINLOCK(my_spinlock);
void spinlock_example(void) {
unsigned long flags;
spin_lock_irqsave(&my_spinlock, flags);
// Critical section (cannot sleep!)
// ...
spin_unlock_irqrestore(&my_spinlock, flags);
}
// Read-Write lock
rwlock_t my_rwlock;
void rwlock_example_read(void) {
read_lock(&my_rwlock);
// Read-only critical section
read_unlock(&my_rwlock);
}
void rwlock_example_write(void) {
write_lock(&my_rwlock);
// Write critical section
write_unlock(&my_rwlock);
}
// Semaphore
struct semaphore my_sem;
void semaphore_example(void) {
sema_init(&my_sem, 1); // Initial count 1 (binary semaphore)
if (down_interruptible(&my_sem)) {
return;
}
// Critical section
// ...
up(&my_sem);
}
4.2 Completion Variables
#include <linux/completion.h>
struct completion my_comp;
void producer_thread(void) {
init_completion(&my_comp);
// Do work...
complete(&my_comp); // Signal completion
}
void consumer_thread(void) {
wait_for_completion(&my_comp); // Wait for signal
// Continue after completion
}
4.3 Atomic Operations
#include <linux/atomic.h>
atomic_t counter = ATOMIC_INIT(0);
void atomic_example(void) {
atomic_inc(&counter);
atomic_add(5, &counter);
int val = atomic_read(&counter);
atomic_dec(&counter);
// Compare and exchange
int old = atomic_cmpxchg(&counter, 10, 20);
}
5. Interrupt Handling
5.1 Registering Interrupt Handlers
#include <linux/interrupt.h>
#include <linux/irq.h>
static int irq_number = 1; // Example IRQ number
static const char *irq_name = "my_device";
// Interrupt handler
static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
// This runs in interrupt context - cannot sleep!
// Acknowledge interrupt
// Read device registers
// Process data quickly
// Schedule bottom half if needed
// ...
return IRQ_HANDLED; // or IRQ_NONE if not our interrupt
}
// Request IRQ
int setup_interrupt(void) {
int ret = request_irq(irq_number,
my_interrupt_handler,
IRQF_SHARED, // Shared interrupt
irq_name,
NULL); // Device ID
if (ret) {
printk(KERN_ERR "Failed to request IRQ %d\n", irq_number);
return ret;
}
return 0;
}
void cleanup_interrupt(void) {
free_irq(irq_number, NULL);
}
5.2 Tasklets (Bottom Half)
#include <linux/interrupt.h>
static struct tasklet_struct my_tasklet;
void tasklet_handler(unsigned long data) {
// This runs in softirq context
// Can sleep? No, but less restrictive than interrupt context
printk(KERN_INFO "Tasklet executing\n");
}
void setup_tasklet(void) {
tasklet_init(&my_tasklet, tasklet_handler, 0);
}
// In interrupt handler:
irqreturn_t interrupt_handler(int irq, void *dev_id) {
// Quick processing...
// Schedule tasklet for remaining work
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
5.3 Workqueues (Bottom Half)
#include <linux/workqueue.h>
static struct work_struct my_work;
void work_handler(struct work_struct *work) {
// This runs in process context (can sleep)
printk(KERN_INFO "Workqueue executing\n");
// Can do heavy processing, allocate memory, etc.
}
void setup_workqueue(void) {
INIT_WORK(&my_work, work_handler);
}
// In interrupt handler:
irqreturn_t interrupt_handler(int irq, void *dev_id) {
// Schedule work
schedule_work(&my_work);
return IRQ_HANDLED;
}
6. Kernel Timers
6.1 Standard Timers
#include <linux/timer.h>
static struct timer_list my_timer;
void timer_callback(struct timer_list *t) {
printk(KERN_INFO "Timer expired\n");
// Reschedule if needed
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}
void setup_timer(void) {
timer_setup(&my_timer, timer_callback, 0);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}
void cleanup_timer(void) {
del_timer(&my_timer);
}
6.2 High-Resolution Timers (hrtimers)
#include <linux/hrtimer.h>
#include <linux/ktime.h>
static struct hrtimer my_hrtimer;
enum hrtimer_restart hrtimer_callback(struct hrtimer *timer) {
ktime_t now = ktime_get();
printk(KERN_INFO "HRTimer expired at %lld\n", ktime_to_ns(now));
// Reschedule
ktime_t interval = ktime_set(0, 100000000); // 100 ms
hrtimer_forward(timer, now, interval);
return HRTIMER_RESTART;
}
void setup_hrtimer(void) {
hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_hrtimer.function = hrtimer_callback;
ktime_t start = ktime_set(1, 0); // 1 second
hrtimer_start(&my_hrtimer, start, HRTIMER_MODE_REL);
}
7. Kernel Threads
7.1 Creating Kernel Threads
#include <linux/kthread.h>
#include <linux/delay.h>
static struct task_struct *my_thread;
int thread_function(void *data) {
int i = 0;
while (!kthread_should_stop()) {
printk(KERN_INFO "Thread iteration %d\n", i++);
msleep_interruptible(1000); // Sleep 1 second
}
printk(KERN_INFO "Thread stopping\n");
return 0;
}
void create_kthread(void) {
my_thread = kthread_run(thread_function, NULL, "my_kthread");
if (IS_ERR(my_thread)) {
printk(KERN_ERR "Failed to create thread\n");
}
}
void stop_kthread(void) {
if (my_thread) {
kthread_stop(my_thread);
my_thread = NULL;
}
}
7.2 Workqueues with Threads
#include <linux/workqueue.h>
static struct workqueue_struct *my_wq;
void work_handler(struct work_struct *work) {
// This runs in a dedicated thread
printk(KERN_INFO "Workqueue work executing\n");
msleep(500); // Can sleep
}
void setup_workqueue(void) {
// Create single-threaded workqueue
my_wq = create_singlethread_workqueue("my_wq");
// Schedule work
struct work_struct *work = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work, work_handler);
queue_work(my_wq, work);
}
void cleanup_workqueue(void) {
flush_workqueue(my_wq);
destroy_workqueue(my_wq);
}
8. Debugging Techniques
8.1 Kernel Logging
// Log levels
printk(KERN_EMERG "System is unusable\n");
printk(KERN_ALERT "Action required\n");
printk(KERN_CRIT "Critical condition\n");
printk(KERN_ERR "Error condition\n");
printk(KERN_WARNING "Warning\n");
printk(KERN_NOTICE "Normal but significant\n");
printk(KERN_INFO "Informational\n");
printk(KERN_DEBUG "Debug-level messages\n");
// Dynamic debugging
#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) \
printk(KERN_DEBUG "my_module: " fmt, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif
// Function tracing
static int my_function(int arg) {
printk(KERN_INFO "Entering %s\n", __func__);
// ...
printk(KERN_INFO "Exiting %s\n", __func__);
return 0;
}
8.2 Proc Filesystem Interface
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static struct proc_dir_entry *proc_entry;
int proc_show(struct seq_file *m, void *v) {
seq_printf(m, "Module status: running\n");
seq_printf(m, "Counter: %d\n", atomic_read(&counter));
return 0;
}
int proc_open(struct inode *inode, struct file *file) {
return single_open(file, proc_show, NULL);
}
static const struct proc_ops proc_fops = {
.proc_open = proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
void create_proc_entry(void) {
proc_entry = proc_create("my_module", 0444, NULL, &proc_fops);
}
void remove_proc_entry(void) {
proc_remove(proc_entry);
}
8.3 Debugfs Interface
#include <linux/debugfs.h>
static struct dentry *debugfs_dir;
static struct dentry *debugfs_file;
int debugfs_value;
int debugfs_show(struct seq_file *m, void *v) {
seq_printf(m, "%d\n", debugfs_value);
return 0;
}
int debugfs_open(struct inode *inode, struct file *file) {
return single_open(file, debugfs_show, NULL);
}
static const struct file_operations debugfs_fops = {
.open = debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void setup_debugfs(void) {
debugfs_dir = debugfs_create_dir("my_module", NULL);
debugfs_file = debugfs_create_file("value", 0644, debugfs_dir,
NULL, &debugfs_fops);
}
void cleanup_debugfs(void) {
debugfs_remove_recursive(debugfs_dir);
}
9. Complete Example: Character Device Driver
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#define DEVICE_NAME "scull"
#define CLASS_NAME "scull_class"
#define QUANTUM 4096
#define QSET 100
/* Device structure */
struct scull_dev {
void **data;
int quantum;
int qset;
unsigned long size;
struct mutex lock;
struct cdev cdev;
};
static int scull_major = 0;
static int scull_minor = 0;
static struct class *scull_class = NULL;
/* Forward declarations */
static int scull_open(struct inode *inode, struct file *filp);
static int scull_release(struct inode *inode, struct file *filp);
static ssize_t scull_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos);
static ssize_t scull_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos);
static loff_t scull_llseek(struct file *filp, loff_t offset, int whence);
/* File operations structure */
static struct file_operations scull_fops = {
.owner = THIS_MODULE,
.open = scull_open,
.release = scull_release,
.read = scull_read,
.write = scull_write,
.llseek = scull_llseek,
};
/* Memory management functions */
static void scull_trim(struct scull_dev *dev) {
int i;
if (!dev->data) return;
for (i = 0; i < dev->qset; i++) {
if (dev->data[i]) {
kfree(dev->data[i]);
dev->data[i] = NULL;
}
}
kfree(dev->data);
dev->data = NULL;
dev->size = 0;
}
static int scull_quantum_set(struct scull_dev *dev, int quantum) {
scull_trim(dev);
dev->quantum = quantum;
return 0;
}
static int scull_qset_set(struct scull_dev *dev, int qset) {
scull_trim(dev);
dev->qset = qset;
return 0;
}
/* Follow the linked list */
static void *scull_follow(struct scull_dev *dev, int items, int *pos) {
void **ptr;
int qset = dev->qset;
if (!dev->data) {
dev->data = kmalloc_array(qset, sizeof(void *), GFP_KERNEL);
if (!dev->data) return NULL;
memset(dev->data, 0, qset * sizeof(void *));
}
ptr = dev->data;
*pos = 0;
while (items-- && ptr) {
if (!*ptr) {
*ptr = kmalloc_array(qset, sizeof(void *), GFP_KERNEL);
if (!*ptr) return NULL;
memset(*ptr, 0, qset * sizeof(void *));
}
ptr = *ptr;
(*pos)++;
}
return ptr;
}
/* Device open */
static int scull_open(struct inode *inode, struct file *filp) {
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev;
if (mutex_lock_interruptible(&dev->lock)) {
return -ERESTARTSYS;
}
if (!dev->data) {
dev->quantum = QUANTUM;
dev->qset = QSET;
dev->size = 0;
dev->data = kmalloc_array(dev->qset, sizeof(void *), GFP_KERNEL);
if (!dev->data) {
mutex_unlock(&dev->lock);
return -ENOMEM;
}
memset(dev->data, 0, dev->qset * sizeof(void *));
}
mutex_unlock(&dev->lock);
return 0;
}
/* Device release */
static int scull_release(struct inode *inode, struct file *filp) {
return 0;
}
/* Device read */
static ssize_t scull_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos) {
struct scull_dev *dev = filp->private_data;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
void **ptr;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* Find list item, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
ptr = scull_follow(dev, item, &item);
if (!ptr || !ptr[s_pos])
goto out;
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, ptr[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
mutex_unlock(&dev->lock);
return retval;
}
/* Device write */
static ssize_t scull_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos) {
struct scull_dev *dev = filp->private_data;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM;
void **ptr;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* Find list item, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
ptr = scull_follow(dev, item, &item);
if (!ptr)
goto out;
if (!ptr[s_pos]) {
ptr[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!ptr[s_pos])
goto out;
memset(ptr[s_pos], 0, quantum);
}
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(ptr[s_pos] + q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* Update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
mutex_unlock(&dev->lock);
return retval;
}
/* Seek implementation */
static loff_t scull_llseek(struct file *filp, loff_t offset, int whence) {
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch (whence) {
case SEEK_SET:
newpos = offset;
break;
case SEEK_CUR:
newpos = filp->f_pos + offset;
break;
case SEEK_END:
newpos = dev->size + offset;
break;
default:
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/* Module initialization */
static int __init scull_init(void) {
dev_t dev_num;
struct scull_dev *dev;
int result;
/* Allocate device numbers */
result = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (result < 0) {
printk(KERN_WARNING "scull: can't get major number\n");
return result;
}
scull_major = MAJOR(dev_num);
scull_minor = MINOR(dev_num);
/* Create device class */
scull_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(scull_class)) {
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(scull_class);
}
/* Create device */
device_create(scull_class, NULL, dev_num, NULL, DEVICE_NAME);
/* Allocate device structure */
dev = kmalloc(sizeof(struct scull_dev), GFP_KERNEL);
if (!dev) {
device_destroy(scull_class, dev_num);
class_destroy(scull_class);
unregister_chrdev_region(dev_num, 1);
return -ENOMEM;
}
/* Initialize device */
memset(dev, 0, sizeof(struct scull_dev));
mutex_init(&dev->lock);
scull_quantum_set(dev, QUANTUM);
scull_qset_set(dev, QSET);
/* Register cdev */
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
result = cdev_add(&dev->cdev, dev_num, 1);
if (result) {
kfree(dev);
device_destroy(scull_class, dev_num);
class_destroy(scull_class);
unregister_chrdev_region(dev_num, 1);
return result;
}
printk(KERN_INFO "scull: loaded with major %d\n", scull_major);
return 0;
}
/* Module cleanup */
static void __exit scull_exit(void) {
dev_t dev_num = MKDEV(scull_major, scull_minor);
struct scull_dev *dev;
struct device *device;
/* Get device structure */
device = device_find_child(scull_class, NULL, NULL);
if (device) {
dev = (struct scull_dev *)dev_get_drvdata(device);
if (dev) {
scull_trim(dev);
cdev_del(&dev->cdev);
kfree(dev);
}
put_device(device);
}
device_destroy(scull_class, dev_num);
class_destroy(scull_class);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "scull: unloaded\n");
}
module_init(scull_init);
module_exit(scull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Developer");
MODULE_DESCRIPTION("Simple character device driver");
10. Best Practices and Common Pitfalls
10.1 Error Handling
// Always check return values
int result = some_kernel_function();
if (result < 0) {
printk(KERN_ERR "Function failed: %d\n", result);
goto cleanup;
}
// Use gotos for cleanup
static int __init my_init(void) {
int ret;
ret = allocate_resource1();
if (ret < 0)
goto err1;
ret = allocate_resource2();
if (ret < 0)
goto err2;
return 0;
err2:
free_resource1();
err1:
return ret;
}
10.2 Avoiding Common Mistakes
// DON'T: Use floating point in kernel
// float f = 3.14; // Not allowed!
// DON'T: Block in interrupt context
// msleep(100); // Not allowed in interrupt handlers!
// DON'T: Access user memory directly
// *user_ptr = value; // Must use copy_to_user
// DON'T: Ignore memory barriers
// Use smp_mb() when needed for ordering
// DO: Use appropriate GFP flags
// kmalloc(size, GFP_ATOMIC); // In interrupt context
// kmalloc(size, GFP_KERNEL); // In process context
// DO: Check for signals in long operations
if (signal_pending(current)) {
return -ERESTARTSYS;
}
Conclusion
Kernel programming in C requires deep understanding of operating system concepts, careful memory management, and rigorous attention to concurrency. The Linux kernel provides a rich set of APIs for device drivers, file systems, network protocols, and more.
Key principles to remember:
- Always sleep when possible, spin only when necessary
- Use appropriate synchronization primitives
- Handle errors gracefully
- Test on multiple kernel versions
- Follow kernel coding style
- Document your code
- Be mindful of performance
The examples in this guide provide a foundation for kernel development. Start with simple modules, understand the concepts thoroughly, and gradually tackle more complex subsystems. The Linux kernel source code itself is the best reference—explore it, learn from it, and contribute back.