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"命令,将把内容写入到设备文件中。

相关推荐
亚空间仓鼠20 小时前
OpenEuler系统常用服务(三)
linux·运维·服务器·网络
信工 180220 小时前
rk3568-Linux应用程序和驱动程序接口
linux·驱动开发·rk3568
倒酒小生21 小时前
4月7日算法学习小结
linux·服务器·学习
木子欢儿21 小时前
KasmVNC 指南:高性能网页原生 Linux 远程桌面方案
linux·运维·服务器
luoqice1 天前
嵌入式linux用nfs挂载ubuntu目录的配置步骤
linux
我科绝伦(Huanhuan Zhou)1 天前
分享一个很实用的K8S巡检脚本
linux·docker·kubernetes
Net_Walke1 天前
【Ubuntu】共享文件夹 /mnt/hgfs 下不显示问题解决
linux·运维·ubuntu
CHANG_THE_WORLD1 天前
PDFIUM如何处理宽度数组
java·linux·服务器
孙同学_1 天前
【Linux篇】应用层自定义协议与序列化
linux·服务器·网络
航Hang*1 天前
第3章:Linux系统安全管理——第1节:Linux 防火墙部署(firewalld)
linux·服务器·网络·学习·系统安全·vmware