linux设备驱动程序框架(进阶1)——利用udev自动生成设备文件

设备文件/dev/mydev就是应用程序与设备驱动之间的交互文件接口。在《linux设备驱动程序框架(基础篇)》中,测试驱动时,需要执行"sudo mknod /dev/mydev c 508 0"命令来创建设备文件。本篇将利用udev(后台进程名:systemd-udevd)自动创建/dev/mydev设备文件。

一、修改代码

当加载设备驱动时,调用class_create创建了my_class设备子类;调用device_create创建设备实例。在设备实例创建好后,linux内核将自动发送uevent事件给systemd-udevd进程,由systemd-udevd进程负责创建/dev/mydev设备文件;当卸载设备驱动时,调用class_destroy和device_destroy注销设备子类和设备实例,linux内核将自动发送uevent事件给systemd-udevd进程,由systemd-udevd进程负责删除/dev/mydev设备文件。

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME  "my_device"
#define DEVICE_COUNT 1

MODULE_AUTHOR("ktest");
MODULE_LICENSE("GPL");

static char kernel_buf[1024] = "hello from my cdev driver!\n";

/*
 * open device
 */
static int my_cdev_open(struct inode* inode, struct file* filp)
{
    printk(KERN_INFO "%s: device opened\n", DEVICE_NAME);
    return 0;
}

/*
 * close device
 */
static int my_cdev_release(struct inode* inode, struct file* filp)
{
    printk(KERN_INFO "%s: device closed\n", DEVICE_NAME);
    return 0;
}

/*
 * read device
 */
static ssize_t my_cdev_read(struct file* filp, char __user* buf, size_t size, loff_t* loff)
{
    unsigned long int ret = 0;
    size_t reserve_size = 0;

    if (*loff >= sizeof(kernel_buf))
    {
        return 0;
    }

    reserve_size = sizeof(kernel_buf) - *loff;
    if (size > reserve_size)
    {
        size = reserve_size;
    }

    // copy_to_user失败时,返回还没有拷贝成功的字节数
    ret = copy_to_user(buf, kernel_buf + (*loff), (unsigned long int)size);
    if (ret != 0)
    {
        printk(KERN_ERR "%s: copy_to_user failed, size = %zu\n", DEVICE_NAME, size);
        return EFAULT;
    }

    *loff += size;
    printk(KERN_INFO "%s: read %zu bytes, current offset is %lld\n", DEVICE_NAME, size, *loff); 
    return size;
}

/*
 * write device
 */
static ssize_t my_cdev_write(struct file* filp, const char __user* buf, size_t size, loff_t* loff)
{
    unsigned long int ret = 0;
    size_t reserve_size = 0;

    if (*loff >= sizeof(kernel_buf))
    {
        printk(KERN_ERR "%s: no memory\n", DEVICE_NAME);
        return -ENOMEM;        
    }

    reserve_size = sizeof(kernel_buf) - *loff;
    if (size > reserve_size)
    {
        size = reserve_size;
    }

    ret = copy_from_user(kernel_buf + *loff, buf, size);
    if (ret != 0)
    {
        printk(KERN_ERR "%s: copy_from_user failed, size = %zu\n", DEVICE_NAME, size);
        return EFAULT;
    }

    *loff += size;
    printk(KERN_INFO "%s: write %zu bytes, current offset is %lld\n", DEVICE_NAME, size, *loff);
    return size;
}

static dev_t devno;
static struct cdev* my_cdev;
static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_cdev_open,
    .release = my_cdev_release,
    .read = my_cdev_read,
    .write = my_cdev_write
};
static struct class* my_class;
static struct device* my_device;

static int __init my_cdev_init(void) 
{
    int ret = 0;

    ret = alloc_chrdev_region(&devno, 0, DEVICE_COUNT, DEVICE_NAME);
    if (ret < 0)
    {
        printk(KERN_ERR "%s: alloc_chrdev_region failed\n", DEVICE_NAME);
        return ret;
    }

    my_cdev = cdev_alloc();
    if (my_cdev == NULL)
    {
        unregister_chrdev_region(devno, DEVICE_COUNT);
        printk(KERN_ERR "%s: cdev_alloc failed\n", DEVICE_NAME);
        return -ENOMEM;
    } 
        
    cdev_init(my_cdev, &fops);
    my_cdev->owner = THIS_MODULE;
   
    ret = cdev_add(my_cdev, devno, DEVICE_COUNT);
    if (ret < 0)
    {
        kfree(my_cdev);
        unregister_chrdev_region(devno, DEVICE_COUNT);
        printk(KERN_ERR "%s: cdev_add failed\n", DEVICE_NAME);
        return ret;
    }

    my_class = class_create("my_class");
    if (IS_ERR(my_class))
    {
        cdev_del(my_cdev);
        kfree(my_cdev);
        unregister_chrdev_region(devno, DEVICE_COUNT);
        printk(KERN_ERR "%s: class_create failed\n", DEVICE_NAME);
        return PTR_ERR(my_class);
    }

    my_device = device_create(my_class, NULL, devno, NULL, "mydev");
    if (IS_ERR(my_device))
    {
        class_destroy(my_class);
        cdev_del(my_cdev);
        kfree(my_cdev);
        unregister_chrdev_region(devno, DEVICE_COUNT);
        printk(KERN_ERR "%s: class_create failed\n", DEVICE_NAME);
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "%s: module loaded, major = %d\n", DEVICE_NAME, MAJOR(devno));
    return 0;
}

static void __exit my_cdev_exit(void)
{
    device_destroy(my_class, devno);
    class_destroy(my_class);
    cdev_del(my_cdev);
    kfree(my_cdev);
    unregister_chrdev_region(devno, DEVICE_COUNT);
    printk(KERN_INFO "%s: module unloaded\n", DEVICE_NAME);
}

module_init(my_cdev_init);
module_exit(my_cdev_exit);

编译ktest_cdev.c的Makefile文件如下:

bash 复制代码
CC := x86_64-linux-gnu-gcc-13
PWD := $(shell pwd)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += ktest_cdev.o

default:
	$(MAKE) CC=$(CC) -C $(KERNELDIR) M=$(PWD) modules 

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

二、编译代码

执行"make clean"命令会清理上次编译后的文件;

执行"make"命令开始编译驱动模块。

三、安装驱动

编译的驱动程序是以模块(.ko)的形式呈现,所以执行"sudo insmod ktest_cdev.ko"命令将安装此驱动程序。

四、卸载驱动

执行"sudo rmmod ktest_cdev"命令将卸载此驱动程序。

执行rmmod时,如果发现设备忙,无法卸载掉驱动时,可以使用"sudo fuser -k /dev/mydev"命令强制杀死设备占用,然后再卸载驱动。

五、测试驱动

1、执行"sudo chmod 666 /dev/mydev"命令,给/dev/mydev设备文件加上读写权限。

2、执行"cat /dev/mydev"命令,将看到设备文件中的内容。

3、执行"echo 'hello, say from my first char device' > /dev/mydev"命令,将把内容写入到设备文件中。

相关推荐
爱丶狸7 小时前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin
k4m7v2pz8 小时前
把 Bevy 0.14 游戏移植到 R36S 掌机:一场与“无窗口系统“的搏斗
linux·rust·bevy·r36s·rk3326·开源掌机
木子欢儿10 小时前
Intel 架构的 MacBook Pro 运行 Linux 发热量高解决
linux·运维·服务器
会编程的土豆13 小时前
Go 模板初识
linux·运维·服务器
regret~15 小时前
【记录】网络路由、转发与网卡隔离
linux·服务器·网络
aFakeProgramer15 小时前
Linux版本兼容问题及RTA-VRTE环境配置笔记
linux·笔记
Zhu75815 小时前
在Ubuntu24快速配置专用SFTP
linux·sftp
码农客栈16 小时前
Linux 内核顶层 Makefile 详解
linux
Embedded-Xin16 小时前
Rust学习——Cargo工具
linux·学习·架构·rust·嵌入式
weixin_4357761116 小时前
2026年长沙托育vi设计从风格定位到元素运用的设计方法
linux·运维·服务器·python