内核命令行计算器

向设备写入 "5+3" 这样的字符串,读取操作返回 8

项目概述

本章节将引导您创建一个有趣的Linux内核驱动,该驱动实现一个简单的命令行计算器。用户可以向设备写入数学表达式(如 "5+3"),然后通过读取操作获取计算结果(如 "8")。这个项目不仅展示了字符设备驱动的基本操作,还演示了如何在内核空间处理字符串和进行数学运算。

用户空间 写入 "5+3" → 读取 "8" 内核空间 字符设备驱动

驱动功能特点

实现原理

该驱动通过创建一个字符设备,实现其 writeread 操作。当用户向设备写入数学表达式时,write 操作将表达式存储在内核缓冲区中。当用户读取设备时,read 操作解析表达式,计算结果,并将结果返回给用户。

核心数据结构

struct calc_device {
    char expression[128];    // 存储数学表达式
    char result[128];        // 存储计算结果
    struct mutex lock;       // 互斥锁,防止并发访问
    struct cdev cdev;        // 字符设备结构
};

操作函数

函数 描述
calc_write 接收用户输入的数学表达式,存储到设备缓冲区
calc_read 解析表达式,计算结果,并返回给用户
calc_open 打开设备时初始化设备结构
calc_release 释放设备时清理资源

代码实现示例

初始化函数

static int __init calc_init(void)
{
    int ret;
    dev_t devno = MKDEV(calc_major, 0);

    // 申请设备号
    if (calc_major) {
        ret = register_chrdev_region(devno, 1, "calc");
    } else {
        ret = alloc_chrdev_region(&devno, 0, 1, "calc");
        calc_major = MAJOR(devno);
    }

    if (ret < 0) {
        printk(KERN_WARNING "calc: can't get major %d\n", calc_major);
        return ret;
    }

    // 初始化字符设备
    cdev_init(&calc_cdev, &calc_fops);
    calc_cdev.owner = THIS_MODULE;
    calc_cdev.ops = &calc_fops;

    ret = cdev_add(&calc_cdev, devno, 1);
    if (ret) {
        printk(KERN_WARNING "calc: error %d adding calc\n", ret);
        return ret;
    }

    printk(KERN_INFO "calc: initialized with major %d\n", calc_major);
    return 0;
}

写入操作

static ssize_t calc_write(struct file *filp, const char __user *buf,
                         size_t count, loff_t *f_pos)
{
    struct calc_device *dev = filp->private_data;
    ssize_t retval = -ENOMEM;

    if (mutex_lock_interruptible(&dev->lock))
        return -ERESTARTSYS;

    if (count > sizeof(dev->expression) - 1) {
        retval = -EINVAL;
        goto out;
    }

    if (copy_from_user(dev->expression, buf, count)) {
        retval = -EFAULT;
        goto out;
    }

    dev->expression[count] = '\0';
    retval = count;

    // 清空之前的结果
    dev->result[0] = '\0';

out:
    mutex_unlock(&dev->lock);
    return retval;
}

读取操作

static ssize_t calc_read(struct file *filp, char __user *buf,
                        size_t count, loff_t *f_pos)
{
    struct calc_device *dev = filp->private_data;
    ssize_t retval = 0;

    if (mutex_lock_interruptible(&dev->lock))
        return -ERESTARTSYS;

    // 如果还没有计算结果,先计算
    if (dev->result[0] == '\0') {
        int a, b;
        char op;
        int res;

        if (sscanf(dev->expression, "%d %c %d", &a, &op, &b) != 3) {
            snprintf(dev->result, sizeof(dev->result), "错误: 无效表达式");
        } else {
            switch (op) {
                case '+': res = a + b; break;
                case '-': res = a - b; break;
                case '*': res = a * b; break;
                case '/': 
                    if (b == 0) {
                        snprintf(dev->result, sizeof(dev->result), "错误: 除零");
                        break;
                    }
                    res = a / b; 
                    break;
                default:
                    snprintf(dev->result, sizeof(dev->result), "错误: 未知操作符 %c", op);
                    goto out;
            }
            snprintf(dev->result, sizeof(dev->result), "%d", res);
        }
    }

    // 将结果拷贝到用户空间
    if (*f_pos >= strlen(dev->result))
        goto out;

    if (count > strlen(dev->result) - *f_pos)
        count = strlen(dev->result) - *f_pos;

    if (copy_to_user(buf, dev->result + *f_pos, count)) {
        retval = -EFAULT;
        goto out;
    }

    *f_pos += count;
    retval = count;

out:
    mutex_unlock(&dev->lock);
    return retval;
}

测试方法

  1. 编译并加载驱动模块
  2. 创建设备节点:mknod /dev/calc c major 0
  3. 向设备写入表达式:echo "5+3" > /dev/calc
  4. 读取计算结果:cat /dev/calc

注意事项

用户写入 "5+3" 驱动解析并计算 用户读取得到 "8"