信号量模拟器

实现一个字符设备,模拟系统信号量,支持 up/down 操作

项目简介

在本章节中,我们将创建一个Linux内核模块,该模块实现一个字符设备,用于模拟系统信号量的行为。信号量是操作系统中用于同步和互斥的重要机制。通过这个项目,你将学习如何在Linux内核中实现一个简单的信号量,并支持标准的 up 和 down 操作。

信号量的 down 操作(也称为 wait 或 P 操作)会减少信号量的值,如果值变为负数,则进程会被阻塞。up 操作(也称为 signal 或 V 操作)会增加信号量的值,并唤醒可能被阻塞的进程。

核心概念

实现步骤

  1. 创建内核模块的基本结构。
  2. 定义信号量数据结构。
  3. 实现字符设备的文件操作(file_operations)。
  4. 实现 read、write 和 ioctl 方法以支持 up/down 操作。
  5. 处理并发访问,确保信号量操作是原子性的。
  6. 测试模块的功能。

代码实现

数据结构定义

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "semaphore_device"
#define CLASS_NAME "semaphore_class"

static int major_number;
static struct class* semaphore_class = NULL;
static struct device* semaphore_device = NULL;
static struct cdev semaphore_cdev;

struct semaphore_state {
    struct semaphore sem;
    int value;
};

static struct semaphore_state dev_state;

文件操作实现

static int device_open(struct inode *inode, struct file *file) {
    return 0;
}

static int device_release(struct inode *inode, struct file *file) {
    return 0;
}

static ssize_t device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) {
    char value_str[10];
    int len = snprintf(value_str, sizeof(value_str), "%d\n", dev_state.value);
    return simple_read_from_buffer(buffer, length, offset, value_str, len);
}

static ssize_t device_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset) {
    char op;
    if (copy_from_user(&op, buffer, 1))
        return -EFAULT;

    if (op == 'U' || op == 'u') {
        up(&dev_state.sem);
        dev_state.value++;
    } else if (op == 'D' || op == 'd') {
        down(&dev_state.sem);
        dev_state.value--;
    } else {
        return -EINVAL;
    }
    return length;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

模块初始化和退出

static int __init semaphore_init(void) {
    // 分配主设备号
    major_number = register_chrdev(0, DEVICE_NAME, &fops);
    if (major_number < 0) {
        printk(KERN_ALERT "Failed to register a major number\n");
        return major_number;
    }

    // 创建设备类
    semaphore_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(semaphore_class)) {
        unregister_chrdev(major_number, DEVICE_NAME);
        printk(KERN_ALERT "Failed to register device class\n");
        return PTR_ERR(semaphore_class);
    }

    // 创建设备
    semaphore_device = device_create(semaphore_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
    if (IS_ERR(semaphore_device)) {
        class_destroy(semaphore_class);
        unregister_chrdev(major_number, DEVICE_NAME);
        printk(KERN_ALERT "Failed to create the device\n");
        return PTR_ERR(semaphore_device);
    }

    // 初始化信号量
    sema_init(&dev_state.sem, 1);
    dev_state.value = 1;

    printk(KERN_INFO "Semaphore device module loaded\n");
    return 0;
}

static void __exit semaphore_exit(void) {
    device_destroy(semaphore_class, MKDEV(major_number, 0));
    class_unregister(semaphore_class);
    class_destroy(semaphore_class);
    unregister_chrdev(major_number, DEVICE_NAME);
    printk(KERN_INFO "Semaphore device module unloaded\n");
}

module_init(semaphore_init);
module_exit(semaphore_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple semaphore device driver");

软件架构图

用户空间 应用程序 内核空间 字符设备驱动 信号量实现 up/down 操作 硬件 QEMU 虚拟设备

测试方法

编译并加载模块后,可以通过以下命令测试信号量设备:

# 编译模块
make

# 加载模块
sudo insmod semaphore.ko

# 查看设备号
cat /proc/devices | grep semaphore_device

# 创建设备节点
sudo mknod /dev/semaphore_device c [major_number] 0

# 测试 down 操作
echo "D" > /dev/semaphore_device

# 测试 up 操作
echo "U" > /dev/semaphore_device

# 读取当前值
cat /dev/semaphore_device

关键函数说明

函数 描述
sema_init 初始化信号量结构
down 执行 down 操作,减少信号量值
up 执行 up 操作,增加信号量值
register_chrdev 注册字符设备
device_create 创建设备节点

注意事项

🔧 确保在编译模块时使用正确版本的内核头文件。

⚠️ 信号量操作是原子性的,但在多处理器系统中可能需要额外的同步机制。

📝 在实际应用中,可能需要处理信号量的超时和中断。