2. 实现基本的文件操作接口
任务目标
为虚拟视频设备驱动实现基本的文件操作接口(open、release、read、write),并验证用户空间程序能够成功打开设备节点并进行数据读写。
环境与依赖
- Linux内核源码树(本案例基于内核 5.15+)
- 内核编译工具链(gcc、make、linux-headers)
- 开发板或虚拟机(具备root权限)
核心实现
本案例使用虚拟内存缓冲区模拟视频设备,实现完整的字符设备文件操作。驱动注册后创建 /dev/virtvideo0 设备节点,用户空间通过标准文件I/O接口访问。
#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 "virtvideo"
#define CLASS_NAME "v4l2virt"
#define BUFFER_SIZE 4096
static int major_num;
static struct class *video_class = NULL;
static struct device *video_device = NULL;
struct virtvideo_dev {
char *buffer;
size_t buffer_size;
struct mutex lock;
struct cdev cdev;
};
static struct virtvideo_dev *video_dev;
static int dev_open(struct inode *inode, struct file *filp)
{
struct virtvideo_dev *dev = container_of(inode->i_cdev,
struct virtvideo_dev, cdev);
filp->private_data = dev;
mutex_lock(&dev->lock);
dev->buffer_size = BUFFER_SIZE;
memset(dev->buffer, 0, BUFFER_SIZE);
mutex_unlock(&dev->lock);
pr_info("virtvideo: device opened\n");
return 0;
}
static int dev_release(struct inode *inode, struct file *filp)
{
pr_info("virtvideo: device closed\n");
return 0;
}
static ssize_t dev_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
{
struct virtvideo_dev *dev = filp->private_data;
ssize_t ret;
mutex_lock(&dev->lock);
if (*offset >= dev->buffer_size) {
mutex_unlock(&dev->lock);
return 0;
}
if (count > dev->buffer_size - *offset)
count = dev->buffer_size - *offset;
if (copy_to_user(buf, dev->buffer + *offset, count)) {
mutex_unlock(&dev->lock);
return -EFAULT;
}
*offset += count;
ret = count;
mutex_unlock(&dev->lock);
return ret;
}
static ssize_t dev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offset)
{
struct virtvideo_dev *dev = filp->private_data;
ssize_t ret;
mutex_lock(&dev->lock);
if (*offset >= dev->buffer_size) {
mutex_unlock(&dev->lock);
return -ENOSPC;
}
if (count > dev->buffer_size - *offset)
count = dev->buffer_size - *offset;
if (copy_from_user(dev->buffer + *offset, buf, count)) {
mutex_unlock(&dev->lock);
return -EFAULT;
}
*offset += count;
ret = count;
mutex_unlock(&dev->lock);
return ret;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = dev_open,
.release = dev_release,
.read = dev_read,
.write = dev_write,
};
static int __init virtvideo_init(void)
{
dev_t dev_num;
int result;
result = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (result < 0) {
pr_err("virtvideo: failed to allocate major number\n");
return result;
}
major_num = MAJOR(dev_num);
video_dev = kzalloc(sizeof(struct virtvideo_dev), GFP_KERNEL);
if (!video_dev) {
unregister_chrdev_region(dev_num, 1);
return -ENOMEM;
}
video_dev->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!video_dev->buffer) {
kfree(video_dev);
unregister_chrdev_region(dev_num, 1);
return -ENOMEM;
}
mutex_init(&video_dev->lock);
cdev_init(&video_dev->cdev, &fops);
video_dev->cdev.owner = THIS_MODULE;
result = cdev_add(&video_dev->cdev, dev_num, 1);
if (result) {
kfree(video_dev->buffer);
kfree(video_dev);
unregister_chrdev_region(dev_num, 1);
return result;
}
video_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(video_class)) {
cdev_del(&video_dev->cdev);
kfree(video_dev->buffer);
kfree(video_dev);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(video_class);
}
video_device = device_create(video_class, NULL, dev_num, NULL, DEVICE_NAME);
if (IS_ERR(video_device)) {
class_destroy(video_class);
cdev_del(&video_dev->cdev);
kfree(video_dev->buffer);
kfree(video_dev);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(video_device);
}
pr_info("virtvideo: driver loaded, major=%d\n", major_num);
return 0;
}
static void __exit virtvideo_exit(void)
{
dev_t dev_num = MKDEV(major_num, 0);
device_destroy(video_class, dev_num);
class_destroy(video_class);
cdev_del(&video_dev->cdev);
kfree(video_dev->buffer);
kfree(video_dev);
unregister_chrdev_region(dev_num, 1);
pr_info("virtvideo: driver unloaded\n");
}
module_init(virtvideo_init);
module_exit(virtvideo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("V4L2 Course");
MODULE_DESCRIPTION("Virtual video device with basic file operations");
编译与加载
# Makefile
obj-m += virtvideo.o
# 编译命令
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载驱动
sudo insmod virtvideo.ko
# 验证设备节点
ls -l /dev/virtvideo0
用户空间验证
# 编译测试程序
cat > test_virtvideo.c << 'EOF'
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd = open("/dev/virtvideo0", O_RDWR);
if (fd < 0) {
perror("open failed");
return 1;
}
char write_buf[] = "V4L2 file operation test";
char read_buf[64] = {0};
ssize_t wret = write(fd, write_buf, strlen(write_buf) + 1);
if (wret < 0) {
perror("write failed");
close(fd);
return 1;
}
lseek(fd, 0, SEEK_SET);
ssize_t rret = read(fd, read_buf, sizeof(read_buf));
if (rret < 0) {
perror("read failed");
close(fd);
return 1;
}
printf("Read %zd bytes: %s\n", rret, read_buf);
close(fd);
return 0;
}
EOF
gcc test_virtvideo.c -o test_virtvideo
sudo ./test_virtvideo
验证判定标准
- 加载成功:
dmesg | tail显示virtvideo: driver loaded,且/dev/virtvideo0节点存在 - 打开成功:测试程序执行后输出
Read 25 bytes: V4L2 file operation test,无错误信息 - 卸载正常:
sudo rmmod virtvideo后设备节点消失,dmesg 显示driver unloaded
常见问题
- 设备节点未创建:确认 udev 规则或手动执行
mknod /dev/virtvideo0 c $(grep virtvideo /proc/devices | awk '{print $1}') 0 - 权限拒绝:使用
sudo chmod 666 /dev/virtvideo0或加入 video 组 - 模块加载失败:检查内核版本与头文件是否匹配,
dmesg查看具体错误