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

相关推荐
李日灐14 小时前
< 7 > Linux 开发工具:git 版本控制器 和 cgdb/gdb 调试器
linux·运维·服务器·开发语言·git·调试器·gdb/cgdb
青木96015 小时前
前后端开发调试运行技巧
linux·服务器·前端·后端·npm·uv
c++之路15 小时前
C++ 模板
linux·开发语言·c++
云动课堂15 小时前
【运维实战】MySQL 8.0 数据库 · 一键自动化部署方案 (适配银河麒麟 V10 / 龙蜥 8 / Rocky Linux 8 / CentOS 8)
linux·运维·数据库
cui_ruicheng15 小时前
Linux进程间通信(一):管道与IPC基础
linux·运维·服务器
Lumos_77715 小时前
Linux -- 互斥锁
linux
一叶龙洲15 小时前
Ubuntu开机无法用向日葵远程控制
linux·运维·ubuntu
计算机安禾16 小时前
【Linux从入门到镜头】第29篇:文本处理三剑客(下)——awk 数据处理神器
linux·运维·服务器
xyx-3v16 小时前
信号量(二进制/计数)
java·linux·数据库
炘爚16 小时前
Linux(整理合集)
linux