项目概述
本章节将引导您创建一个有趣的Linux内核驱动,该驱动实现一个简单的命令行计算器。用户可以向设备写入数学表达式(如 "5+3"),然后通过读取操作获取计算结果(如 "8")。这个项目不仅展示了字符设备驱动的基本操作,还演示了如何在内核空间处理字符串和进行数学运算。
驱动功能特点
- 支持基本的数学运算:加法、减法、乘法和除法
- 通过字符设备接口与用户空间交互
- 简单的字符串解析和计算逻辑
- 错误处理和边界情况处理
实现原理
该驱动通过创建一个字符设备,实现其 write
和 read
操作。当用户向设备写入数学表达式时,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;
}
测试方法
- 编译并加载驱动模块
- 创建设备节点:
mknod /dev/calc c major 0
- 向设备写入表达式:
echo "5+3" > /dev/calc
- 读取计算结果:
cat /dev/calc
注意事项
- 表达式格式应为 数字 操作符 数字,如 "5 + 3"
- 支持的操作符:+(加)、-(减)、*(乘)、/(除)
- 除法时除数不能为零
- 表达式长度限制为127字符