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