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

相关推荐
程序猿编码2 小时前
轻量又灵活:一款伪造TCP数据包的iptables扩展实现解析(C/C++代码实现)
linux·c语言·网络·c++·tcp/ip·内核·内核模块
_OP_CHEN2 小时前
【Linux网络编程】(二)计算机网络概念进阶:彻底搞懂协议本质、传输流程与封装分用
linux·运维·服务器·网络·网络协议·计算机网络·c/c++
风曦Kisaki2 小时前
# 云计算基础Day06:Linux权限管理
linux·云计算
勇闯逆流河2 小时前
【Linux】linux进程概念(fork,进程状态,僵尸进程,孤儿进程)
linux·运维·服务器·开发语言·c++
牛十二2 小时前
宝塔安装openclaw+企业微信操作手册
linux·运维·服务器
开开心心_Every2 小时前
免费抽奖软件支持内定名单+防重复中奖
linux·运维·服务器·edge·pdf·c5全栈·c4python
feng68_2 小时前
Discuz! X5 高性能+高可用
linux·运维·服务器·前端·后端·高性能·高可用
IMPYLH2 小时前
Linux 的 chgrp 命令
linux·运维·服务器
optimistic_chen2 小时前
【Vue入门】scoped与组件通信
linux·前端·javascript·vue.js·前端框架·组件通信