【Linux driver】一个简单的字符设备虚拟驱动

提示:本篇文章旨在记录个人学习笔记,如有错误欢迎各位大佬指出。


前言

提示:欢迎评论留言补充不足的地方

本人主要研习的是Windows驱动,对Linux驱动相关知识理解的比较浅显,该文章也只是记录一下,怎么写一个简单的字符设备驱动。


一、创建共享文件夹

因为用的虚拟机开发,修改代码相对来说比较熟悉Windows系统,也不想另开新篇章记录,所以就在本篇文章介绍了。
Note: 系统版本Ubuntu 22.04.4 LTS

1.安装工具

#安装vmtools
sudo apt install open-vm-tools-desktop
#如果上面这个命令行不通,试试这个
sudo apt install open-vm-tools

2.设置共享文件夹
VM setting
3.挂载共享文件

#查询是否有共享文件夹
vmware-hgfsclient

#如果没有挂载文件夹(单次挂载,重启无效)
sudo vmhgfs-fuse -o allow_other -o auto_unmount .host:/ /mnt/hgfs

#自动挂载,打开下面的文件
sudo vim /etc/fstab
#文件末尾添加如下命令,重启生效
.host:/ /mnt/hgfs fuse.vmhgfs-fuse allow_other,auto_unmount 0 0

二、Sample Driver

1.写入数据

代码如下(示例):

static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {  
    unsigned long missing;  

    printk(KERN_WARNING "%s: Device write.\n",DEVICE_NAME);

    if (count > BUF_LEN)  
        count = BUF_LEN;  
    
    missing = copy_from_user(buffer, buf, count);  
    if (missing) {  
        printk(KERN_ALERT "%s:copy_from_user failed, wrote %zd bytes.\n", DEVICE_NAME, count - missing);  
        return -EFAULT;  
    }
   
    memcpy(write_buffer, buffer, count);
    write_buffer[count] = '\0';
  
    printk(KERN_WARNING "%s: %s - Wrote %zd bytes to device.\n", DEVICE_NAME, write_buffer, count);  
    return count;  
} 

写入用户请求的数据。保留用户的数据于write_buffer数组中,其中“ write_buffer[count] = ‘\0’”可以确保输出正确的字符数据,在用户调用read时再传回去。

2.读取数据

代码如下(示例):

static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t * offset) {  
    int bytes_read = 0;  

    printk(KERN_WARNING "%s: Device read.\n",DEVICE_NAME);
  
    if (*offset >= BUF_LEN)  
        return 0;  
  
    if (length > BUF_LEN - *offset)  
        length = BUF_LEN - *offset;  
  
    if (copy_to_user(buffer, write_buffer + *offset, length)) 
        return -EFAULT;  
  
    *offset += length;  
    bytes_read = length;  
    
    printk(KERN_WARNING "%s: %s - read %d bytes to device.\n", DEVICE_NAME, write_buffer, bytes_read);  
  
    return bytes_read;  
} 

读取用户请求的数据。当用户调用read函数读取数据时,我们将应用层发给内核层的数据转发回去。

3.Makefile

# Makefile for simple_driver  
obj-m += simple_driver.o  
  
all:  
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules  
  
clean:  
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

4.安装驱动

make编译完后,会在当前路径下生成simple_driver.ko的文件,这也是我们需要的内核二进制文件。

# install driver
sudo insmod simple_driver.ko

# view driver
lsmod

# remove driver
sudo rmmod simple_driver.ko

使用“ls /dev/”可以看到目录下存在“simple_driver”这个设备,如果找不到,那可能是没有创建设备节点,本文代码已创建设备节点。

5.查看驱动日志

在查看驱动日志前可以看一下当前的日志打印等级:

cat /proc/sys/kernel/printk
#result: 4        4        1        7

#change print level
echo 8 > /proc/sys/kernel/printk
dmesg -n 5

“4 4 1 7” 分别对应console_loglevel、default_message_loglevel、minimum_c onsole_loglevel、default_console_loglevel,意味着只有优先级高于KERN_WARNING(4)的打印消息才能输出到终端。听说可以修改打印等级,但我试了,好像行不通,所以我在代码中用的是KERN_WARNING级别的。打印级别如下:

MicroValueExplanation
KERN_EMERG“0”system is unusable
KERN_ALERT“1”action must be taken immediately
KERN_CRIT“2”critical conditions
KERN_ERR“3”error conditions
KERN_WARNING“4”warning conditions
KERN_NOTICE“5”normal but significant condition
KERN_INFO“6”informational
KERN_DEBUG“7”debug-level messages
KERN_DEFAULT“”the default kernel log level

可以通过下面的命令查看驱动的日志信息。

# view kernel driver information 
sudo cat /proc/kmsg
# add filter info 
sudo cat /proc/kmsg | grep "simple_driver"

# another way
sudo dmesg | grep "simple_driver"

运行结果:
run driver

6.Source Code

// simple_driver.c  
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/cdev.h>  
#include <asm/uaccess.h>  
  
#define DEVICE_NAME "simple_device"  
#define BUF_LEN 1024  
  
static dev_t first_dev;  
static struct cdev cdev;
static struct class *simple_class;
static char buffer[BUF_LEN] = {0};
static char write_buffer[BUF_LEN+1] = {0};
//static char msg[BUF_LEN] = "Hello from simple device driver!";  

static int device_open(struct inode *inode, struct file *file) {  
    printk(KERN_WARNING "%s: Device opened.\n",DEVICE_NAME);
    return 0;  
}  
  
static int device_release(struct inode *inode, struct file *file) {  
    printk(KERN_WARNING "%s: Device released.\n",DEVICE_NAME);  //KERN_INFO
    return 0;  
}  
  
static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t * offset) {  
    int bytes_read = 0;  

    printk(KERN_WARNING "%s: Device read.\n",DEVICE_NAME);
  
    if (*offset >= BUF_LEN)  
        return 0;  
  
    if (length > BUF_LEN - *offset)  
        length = BUF_LEN - *offset;  
  
    if (copy_to_user(buffer, write_buffer + *offset, length))    //msg + *offset  
        return -EFAULT;  
  
    *offset += length;  
    bytes_read = length;  
    
    printk(KERN_WARNING "%s: %s - read %d bytes to device.\n", DEVICE_NAME, write_buffer, bytes_read);  
  
    return bytes_read;  
}  

static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {  
    unsigned long missing;  

    printk(KERN_WARNING "%s: Device write.\n",DEVICE_NAME);

    if (count > BUF_LEN)  
        count = BUF_LEN;  
    
    //if (!access_ok(VERIFY_READ, buf, count)) {
        // If the access is illegal, error handling can be performed
        //return -EFAULT;
    //}  
  
    missing = copy_from_user(buffer, buf, count);  
    if (missing) {  
        printk(KERN_ALERT "%s:copy_from_user failed, wrote %zd bytes.\n", DEVICE_NAME, count - missing);  
        return -EFAULT;  
    }
   
    memcpy(write_buffer, buffer, count);
    write_buffer[count] = '\0';
  
    printk(KERN_WARNING "%s: 1.%s; 2.%s - Wrote %zd bytes to device.\n",DEVICE_NAME,buffer,write_buffer, count);  
    return count;  
} 
  
static struct file_operations fops = {  
    .read = device_read,  
    .write = device_write,
    .open = device_open,  
    .release = device_release  
};  
  
int init_module(void) {  
    int result;  
    dev_t dev_no;  
  
    result = alloc_chrdev_region(&first_dev, 0, 1, DEVICE_NAME);  
    if (result < 0) {  
        printk(KERN_ALERT "%s:Failed to allocate char device region.\n", DEVICE_NAME);  
        return result;  
    }  
  
    dev_no = MKDEV(MAJOR(first_dev), 0);  
  
    cdev_init(&cdev, &fops);  
    cdev_add(&cdev, dev_no, 1);  
  
    printk(KERN_WARNING "I was assigned major number %d. To talk to\n"  
           "the driver, create a dev file with\n"  
           "'mknod /dev/%s: c %d 0'.\n"  
           "Try various minor numbers. Don't forget to\n"  
           "rm the dev files and module when you're done.\n",  
           MAJOR(first_dev), DEVICE_NAME, MAJOR(first_dev));
    
    simple_class = class_create(DEVICE_NAME);
    device_create(simple_class, NULL, first_dev, NULL, DEVICE_NAME);  
  
    return 0;  
}  
  
void cleanup_module(void) {  
    cdev_del(&cdev);  
	class_destroy(simple_class);
    unregister_chrdev_region(first_dev, 1);  
}  
  
MODULE_LICENSE("Dual BSD/GPL");



三、Test Application

测试的应用程序

在Linux环境下,以下是一个示例,展示了如何创建一个简单的测试程序。

// test_driver.c  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
  
#define DEVICE_FILE "/dev/simple_device"  
#define BUF_LEN 256  
  
int main() {  
    int fd;  
    char buffer[BUF_LEN];  
    ssize_t bytesRead;
    char write_buf[BUF_LEN] = "Hello, simple write driver!";
    ssize_t bytes_written;
  
    // 尝试以只读方式打开设备文件  
    fd = open(DEVICE_FILE, O_RDWR);  
    if (fd == -1) {  
        perror("Error opening device file");
        exit(EXIT_FAILURE);  
    }

    // 向设备文件写入数据
    bytes_written = write(fd, write_buf, strlen(write_buf));
    if (bytes_written == -1) {
        perror("Error writing to device");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Wrote %zd bytes to the device.\n", bytes_written);
  
    // 读取设备文件的内容  
    memset(buffer, 0, BUF_LEN);  
    bytesRead = read(fd, buffer, BUF_LEN - 1);  
    if (bytesRead == -1) {  
        perror("Error reading from device");  
        close(fd);  
        exit(EXIT_FAILURE);  
    }  
  
    // 打印从设备读取的数据  
    printf("Read from device: %s\n", buffer);  
  
    // 关闭设备文件  
    close(fd);  
  
    return 0;  
}

编译代码:gcc test_driver.c -o test_driver
在这个简单的例子中,我们通过open函数打开设备,获得设备句柄(不太严谨,暂且这么说),然后调write函数用向设备文件写入数据,接着调用read函数读取设备文件的内容。


总结

以上就是今天要讲的内容,本文仅仅简单介绍了字符设备虚拟驱动,而实际情况会更复杂,还有块设备和网络设备这两大类没有介绍,也不打算开坑了。对于ioctl的通信方式本文没有给出,看看以后有没有空再填补吧。

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值