Design of Linux Kernel Character Device Driver SCULL

SCULL (Simple Character Utility for Loading Localities) is a classic example of a character device driver in the Linux kernel, often used as an introductory project for learning Linux character device drivers. The design goal of the SCULL device is to provide a simple, purely memory-based character device driver to help developers understand the basic structure and functionality of character device drivers.

1. Overview of SCULL Character Device Driver Design

The SCULL device driver simulates a character device, with its operations performed in main memory and without physical hardware. Common features of the SCULL driver include:

Design of Linux Kernel Character Device Driver SCULL2. Basic Design Structure of SCULL Program

The design of the SCULL driver generally includes the following components:

Design of Linux Kernel Character Device Driver SCULL

3. Code Example of SCULL Driver

1. Define Device Data Structure

SCULL usescircular buffers to store data. Each device corresponds to a buffer.

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define SCULL_SIZE 1024  // Define buffer size
#define SCULL_MAJOR 0    // Dynamically allocate major device number
#define DEVICE_NAME "scull"// SCULL device data structure
struct scull_dev {
    char *data;           // Data buffer
    size_t size;          // Current data size
    struct cdev cdev;     // Character device structure
};
struct scull_dev *scull_device;  // SCULL device instance
dev_t dev_no;                    // Device number

2. Implementation of File Operation Functions

<span>open()</span> function

Initialization checks are performed when the device is opened.

int scull_open(struct inode *inode, struct file *filp) {
    struct scull_dev *dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev;
    pr_info("SCULL device opened\n");
    return 0;
}

<span>release()</span> function

Resources are released when the device is closed.

int scull_release(struct inode *inode, struct file *filp) {
    pr_info("SCULL device released\n");
    return 0;
}

<span>read()</span> function

Reads data from the device to user space.

ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
    struct scull_dev *dev = filp->private_data;
    size_t available = dev->size - *f_pos;
    size_t to_read = min(count, available);
    if (to_read == 0) return 0; // No data to read
    if (copy_to_user(buf, dev->data + *f_pos, to_read)) {
        return -EFAULT;
    }
    *f_pos += to_read;
    pr_info("SCULL read %zu bytes\n", to_read);
    return to_read;
}

<span>write()</span> function

Writes data from user space to the device.

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;
    if (*f_pos + count > SCULL_SIZE)
        count = SCULL_SIZE - *f_pos;
    if (count == 0) return -ENOSPC;
    if (copy_from_user(dev->data + *f_pos, buf, count)) {
        return -EFAULT;
    }
    *f_pos += count;
    dev->size = max(dev->size, *f_pos);
    pr_info("SCULL wrote %zu bytes\n", count);
    return count;
}

File Operation Structure

The above functions are registered into the<span>file_operations</span> structure.

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .open = scull_open,
    .release = scull_release,
    .read = scull_read,
    .write = scull_write,
};

3. Character Device Registration and Initialization

Initialize the device when the module is loaded

static int __init scull_init(void) {
    int result;
    // Allocate device number
    result = alloc_chrdev_region(&amp;dev_no, 0, 1, DEVICE_NAME);
    if (result < 0) {
        pr_err("Failed to allocate device number\n");
        return result;
    }
    // Allocate device structure
    scull_device = kmalloc(sizeof(struct scull_dev), GFP_KERNEL);
    if (!scull_device) {
        unregister_chrdev_region(dev_no, 1);
        return -ENOMEM;
    }
    memset(scull_device, 0, sizeof(struct scull_dev));
    // Allocate data buffer
    scull_device->data = kmalloc(SCULL_SIZE, GFP_KERNEL);
    if (!scull_device->data) {
        kfree(scull_device);
        unregister_chrdev_region(dev_no, 1);
        return -ENOMEM;
    }
    // Initialize character device
    cdev_init(&amp;scull_device->cdev, &amp;scull_fops);
    scull_device->cdev.owner = THIS_MODULE;
    result = cdev_add(&amp;scull_device->cdev, dev_no, 1);
    if (result) {
        pr_err("Failed to add cdev\n");
        kfree(scull_device->data);
        kfree(scull_device);
        unregister_chrdev_region(dev_no, 1);
        return result;
    }
    pr_info("SCULL device initialized with major %d\n", MAJOR(dev_no));
    return 0;
}

Clean up resources when the module is unloaded

static void __exit scull_exit(void) {
    cdev_del(&amp;scull_device->cdev);
    kfree(scull_device->data);
    kfree(scull_device);
    unregister_chrdev_region(dev_no, 1);
    pr_info("SCULL device unregistered\n");
}module_init(scull_init);
module_exit(scull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple SCULL Character Device");

The SCULL driver simulates a character device through memory buffers, focusing on:

  • Implementation of file operation interfaces (<span>read</span>,<span>write</span>,<span>open</span>,<span>release</span>).
  • Device number allocation and registration.
  • Character device initialization and resource management.

Through the SCULL example, you can familiarize yourself with the overall design and development process of Linux character device drivers.

Leave a Comment