函数介绍
// 动态地分配字符设备的主设备号和次设备号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);
/*
dev 是一个指向 dev_t 类型的指针,用于保存分配到的字符设备号。
firstminor 是第一个次设备号,表示分配的字符设备号的次设备号范围。
count 是分配的字符设备号数量,表示次设备号的数量。
name 是设备名称,表示分配的字符设备号所对应的设备名称。
*/
// 释放动态分配的字符设备号
void unregister_chrdev_region(dev_t from, unsigned int count);
/*
from 是要释放的字符设备号的起始值,通常是调用 alloc_chrdev_region 函数时传入的第一个参数。
count 是要释放的字符设备号的数量,通常是调用 alloc_chrdev_region 函数时传入的第三个参数。
*/
// 用于初始化一个字符设备结构 struct cdev 的函数
void cdev_init(struct cdev * cdev, const struct file_operations * fops);
/*
cdev: 要初始化的字符设备结构指针。
fops: 指向文件操作结构体的指针,该结构体定义了字符设备的操作函数,如 open、read、write、close 等。
这个结构体中包含了指向相应函数的指针,字符设备的实际操作将通过这些指针来执行。
*/
// 用于向内核添加一个已初始化的字符设备 (struct cdev)。一旦添加成功,内核将开始跟踪这个字符设备,
// 并将其与相应的设备号关联起来,以便用户空间可以通过设备文件访问这个设备。
int cdev_add(struct cdev * cdev, dev_t dev, unsigned count);
/*
cdev: 指向已初始化的字符设备结构的指针。
dev: 设备号,包括主设备号和次设备号。
count: 次设备号的数量。通常为 1,表示只有一个次设备。
*/
// 用于从内核中删除一个已经存在的字符设备 (struct cdev),内核将会删除指定的字符设备,并释放相关的资源。
// 这包括释放分配的设备号以及释放字符设备结构所占用的内存空间。
void cdev_del(struct cdev * cdev);
/*
cdev: 指向要删除的字符设备结构的指针。
*/
// 用于创建一个设备类的函数。设备类是一组相关设备的集合,这些设备具有类似的功能或属性。
// 在创建设备类之后,可以将具体的设备与该类关联起来
struct class * __must_check class_create(const char *name);
/*
name: 指定设备类的名称。这个名称在 /sys/class 目录下创建一个与之对应的子目录,用于存放该类的设备。
通常情况下,当创建了一个新的设备类后,可以通过其他函数,如 device_create() 或 device_create_with_groups() 等,
将具体的设备与该类关联起来,从而在 /sys/class 目录下创建设备类的子目录,并将设备文件添加到其中。
*/
// 用于销毁一个设备类,并释放与之关联的资源。设备类是一组相关设备的集合,
// 当不再需要这个设备类时,可以调用 class_destroy() 函数来销毁它。
void class_destroy(const struct class *cls);
/*
cls: 指向要销毁的设备类结构的指针。
*/
// 用于创建一个设备文件并将其关联到指定的设备类。
// 返回一个指向新创建的设备结构的指针,如果创建失败,则返回 NULL。
struct device *device_create(struct class *class, struct device *parent, dev_t devt,
void *drvdata, const char *fmt, ...);
/*
class:指向设备类的指针。
parent:如果设备位于其他设备之下,则指向父设备的指针;如果没有父设备,则为NULL。
devt:表示设备号的dev_t类型的变量,由主设备号和次设备号组成。
drvdata:指向与设备相关的数据的指针。通常是指向驱动程序特定的数据结构。
fmt:表示设备文件的名称的格式化字符串,用于生成设备文件的名称。
*/
// 函数用于销毁一个设备文件,并清理与之相关的资源,从 /dev 目录中删除设备文件,并释放设备号等。
void device_destroy(const struct class *cls, dev_t devt);
/*
cls: 指向设备类结构的指针。这是创建设备文件时传递给
devt: 要销毁的设备的设备号,包括主设备号和次设备号。
*/
// 用于在设备的 sysfs 目录下创建一个属性文件,并将该文件与指定的设备属性关联起来。
// 并将 show 和 store 函数与之关联,以便在读取或写入该文件时调用相应的函数。
// 这个属性文件可以用于向用户空间提供设备的信息或配置选项。
// 返回一个整数值,表示操作的成功与否。若成功,返回值为 0;若出错,则返回负值,表示错误码
int device_create_file(struct device *device,
const struct device_attribute *entry);
/*
device: 指向设备结构的指针,表示要创建属性文件的设备。
entry: 指向设备属性结构的指针。设备属性结构通常包含了属性文件的名称以及与之相关的读写函数等信息。
*/
struct device_attribute {
struct attribute attr; // attr 是一个 struct attribute 结构,用于描述属性的名称和权限等信息。
// 指向一个函数,用于读取属性的值。当用户空间尝试读取属性文件时,内核将调用该函数来获取属性的值。
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
// 指向一个函数,用于写入属性的值。当用户空间尝试写入属性文件时,内核将调用该函数来更新属性的值。
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
// 用于从设备的 sysfs 目录下移除一个属性文件,并取消与之关联的设备属性。
void device_remove_file(struct device *dev,
const struct device_attribute *attr);
/*
dev: 指向设备结构的指针,表示要移除属性文件的设备。
attr: 指向设备属性结构的指针,表示要移除的属性文件。
*/
// Linux 内核中用于创建/注册一个 proc 文件的函数。Proc 文件是一种虚拟的文件系统,
// 用于内核与用户空间之间的通信,通常用于向用户空间提供内核信息。
// 返回一个指向创建的 proc 文件结构体 struct proc_dir_entry 的指针,如果创建失败,则返回 NULL。
struct proc_dir_entry *proc_create(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct proc_ops *proc_ops);
/*
name: 指定要创建的 proc 文件的名称。
mode: 表示 proc 文件的权限模式。
parent: 指向父目录的指针,表示要将 proc 文件放置在哪个目录下。
可以将其设置为 NULL,表示将 proc 文件放置在根目录下。
proc_ops: 指向操作 proc 文件的函数指针集合的指针。
通常情况下,需要提供一组操作 proc 文件的函数指针集合,这些函数包括 proc_open()、proc_read()、
proc_write()、proc_lseek()、proc_release() 等。
这些函数用于处理打开、读取、写入、定位和关闭 proc 文件的操作。
这些操作函数会在用户空间对 proc 文件进行读写时被内核调用。
*/
// 用于从 proc 文件系统中移除一个已注册的 proc 文件或目录。
void remove_proc_entry(const char *, struct proc_dir_entry *);
/*
name: 要移除的 proc 文件或目录的名称。
parent: 指向父目录的指针,表示要从哪个目录下移除指定的 proc 文件或目录。
*/
代码编写
hello_driver.h
#ifndef HELLODRIVER_HELLO_DRIVER_H
#define HELLODRIVER_HELLO_DRIVER_H
#include <linux/cdev.h>
#include <linux/semaphore.h>
// 设备名称
#define HELLO_DEVICE_DEVICE_NAME "hello"
// proc文件名
#define HELLO_DEVICE_PROC_NAME "hello"
// 设备 class 类型
#define HELLO_DEVICE_CLASS_NAME "virtual"
#define VAL_SIZE 1024
struct hello_reg_dev {
char data[VAL_SIZE]; // 能够存储1k个字符
int length;
struct semaphore sem; // 信号量 ,用来同步访问 val 字符数组的
dev_t dev_no;
struct cdev dev; // 标准的 linux 字符设备结构体变量 ,用来标志该虚拟硬件设备的类型为字符设备
struct class *hello_class;
struct device *hello_device;
};
static struct hello_reg_dev *HELLO_DEV = NULL;
static ssize_t hello_get_value(struct hello_reg_dev *dev, char *buf, size_t count);
static ssize_t hello_set_value(struct hello_reg_dev *dev, const char *buf, size_t count);
// 传统设备文件的操作方法
static int hello_open(struct inode *inode, struct file *file);
static int hello_release(struct inode *inode, struct file *file);
static ssize_t hello_read(struct file *file, char *buf, size_t count, loff_t *ppos);
static ssize_t hello_write(struct file *file, const char *buf, size_t count, loff_t *ppos);
// 传统设备文件的操作方法表
static const struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
// 文件系统的设备属性操作方法
static ssize_t hello_val_show(struct device *dev, struct device_attribute *atr, char *buf);
static ssize_t hello_val_store(struct device *dev, struct device_attribute *atr, const char *buf, size_t count);
// 文件系统的设备属性
static DEVICE_ATTR(entry, S_IRUGO | S_IWUSR,
hello_val_show, hello_val_store);
// proc 文件操作
static ssize_t hello_proc_read(struct file *file, char __user *usr_buf, size_t count, loff_t *pos);
//static ssize_t hello_proc_write(struct file *file, const char __user *usr_buf, size_t count, loff_t *pos);
static struct file_operations proc_fops = {
.owner = THIS_MODULE,
.read = hello_proc_read,
// .write = hello_proc_write,
};
// 动态地分配字符设备的主设备号和次设备号
static int hello_alloc_init(void);
// 释放动态分配的字符设备号
static void hello_alloc_exit(void);
// 初始化 字符设备
static int hello_dev_init(void);
// 释放 字符设备
static void hello_dev_exit(void);
// 初始化 hello_reg_dev 结构体
static int hello_val_init(void);
// 释放 hello_reg_dev 结构体
static void hello_val_exit(void);
// 创建设备类型目录 /sys/class/virtual
static int hello_class_init(void);
// 删除设备类型目录 /sys/class/virtual
static void hello_class_exit(void);
// 创建 /dev/hello 文件
static int hello_device_init(void);
// 删除 /dev/hello 文件
static void hello_device_exit(void);
// 创建 /sys/class/virtual/entry
static int hello_device_file_init(void);
// 删除 /sys/class/virtual/entry
static void hello_device_file_exit(void);
// 创建 /pro/hello 文件
static int hello_proc_init(void);
// 卸载 /pro/hello 文件
static void hello_proc_exit(void);
// 模块初始化函数
static int hello_init(void);
// 模块卸载函数
static void hello_exit(void);
#endif //HELLODRIVER_HELLO_DRIVER_H
hello_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "hello_driver.h"
static ssize_t hello_get_value(struct hello_reg_dev *dev, char *buf, size_t count) {
if (down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
printk(KERN_INFO "hello_get_value success count = %d\n", dev->length);
ssize_t size = 0;
if (count < dev->length) {
size = -EFAULT;
goto out;
}
if (copy_to_user(buf, dev->data, dev->length)) {
size = -EFAULT;
goto out;
}
size = dev->length;
printk(KERN_INFO "hello_get_value success size = %d\n", size);
out:
up(&(dev->sem));
return size;
}
static ssize_t hello_set_value(struct hello_reg_dev *dev, const char *buf, size_t count) {
if (down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
printk(KERN_INFO "hello_set_value success count = %d\n", count);
ssize_t size = 0;
if (count >= VAL_SIZE) {
size = -EFAULT;
goto out;
}
if (copy_from_user(HELLO_DEV->data, buf, count)) {
size = -EFAULT;
goto out;
}
size = count;
HELLO_DEV->length = count;
printk(KERN_INFO "hello_set_value success size = %d\n", size);
out:
up(&(dev->sem));
return size;
}
static int hello_open(struct inode *inode, struct file *file) {
struct hello_reg_dev *dev;
dev = container_of(inode->i_cdev, struct hello_reg_dev, dev);
file->private_data = dev;
return 0;
}
static int hello_release(struct inode *inode, struct file *file) {
return 0;
}
static ssize_t hello_read(struct file *file, char *buf, size_t count, loff_t *f_pos) {
struct hello_reg_dev *dev = file->private_data;
return hello_get_value(dev, buf, count);
}
static ssize_t hello_write(struct file *file, const char *buf, size_t count, loff_t *f_pos) {
struct hello_reg_dev *dev = file->private_data;
return hello_set_value(dev, buf, count);
}
// 创建 /pro/hello 文件
static int hello_proc_init(void) {
if (!proc_create(HELLO_DEVICE_PROC_NAME, 0666, NULL, (const struct proc_ops *) &proc_fops)) {
printk(KERN_ERR "Failed to proc_create \n");
return -1;
}
printk(KERN_INFO "/proc/%s created\n", HELLO_DEVICE_PROC_NAME);
return 0;
}
// 卸载 /pro/hello 文件
static void hello_proc_exit(void) {
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
printk(KERN_INFO "/proc/%s removed\n", HELLO_DEVICE_PROC_NAME);
}
static ssize_t hello_val_show(struct device *device, struct device_attribute *atr, char *buf) {
printk(KERN_INFO "hello_val_show \n");
struct hello_reg_dev *dev = (struct hello_reg_dev *) (device);
return hello_get_value(dev, buf, PAGE_SIZE);
}
static ssize_t hello_val_store(struct device *device, struct device_attribute *atr, const char *buf, size_t count) {
printk(KERN_INFO "hello_val_store \n");
struct hello_reg_dev *dev = (struct hello_reg_dev *) (device);
return hello_set_value(dev, buf, count);
}
static ssize_t hello_proc_read(struct file *file, char __user *usr_buf, size_t count, loff_t *pos) {
if (*pos > 0) {
return 0;
}
printk(KERN_INFO "hello_proc_read \n");
ssize_t length = hello_get_value(HELLO_DEV, usr_buf, count);
*pos = length;
return length;
}
//static ssize_t hello_proc_write(struct file *file, const char __user *usr_buf, size_t count, loff_t *pos) {
// printk(KERN_INFO "hello_proc_write \n");
// return hello_set_value(HELLO_DEV, usr_buf, count);
//}
// 初始化 hello_reg_dev 结构体
static int hello_val_init(void) {
HELLO_DEV = kmalloc(sizeof(struct hello_reg_dev), GFP_KERNEL);
if (!HELLO_DEV) {
printk(KERN_ERR "Failed to kmalloc \n");
return -1;
}
printk(KERN_INFO "kmalloc success \n");
memset(HELLO_DEV, 0, sizeof(struct hello_reg_dev));
// 初始化 信号量
sema_init(&HELLO_DEV->sem, 1);
return 0;
}
static void hello_val_exit(void) {
kfree(HELLO_DEV);
}
static int hello_dev_init(void) {
// 初始化字符设备
cdev_init(&(HELLO_DEV->dev), &hello_fops);
HELLO_DEV->dev.owner = THIS_MODULE;
HELLO_DEV->dev.ops = &hello_fops;
// 将字符设备添加到系统中
if (cdev_add(&(HELLO_DEV->dev), HELLO_DEV->dev_no, 1)) {
printk(KERN_ERR "Failed to cdev_add \n");
return -1;
}
printk(KERN_INFO "cdev_add success \n");
return 0;
}
static void hello_dev_exit(void) {
cdev_del(&HELLO_DEV->dev);
}
static int hello_class_init(void) {
// 创建设备类型目录 /sys/class/virtual
struct class *hello_class = class_create(HELLO_DEVICE_CLASS_NAME);
if (IS_ERR(hello_class)) {
printk(KERN_ERR "Failed to class_create class\n");
return -1;
}
printk(KERN_INFO "class_create success\n");
HELLO_DEV->hello_class = hello_class;
return 0;
}
static void hello_class_exit(void) {
class_destroy(HELLO_DEV->hello_class);
printk(KERN_INFO "Device destroyed\n");
}
static int hello_device_init(void) {
// 创建 /dev/hello 文件
struct device *hello_device = device_create(HELLO_DEV->hello_class, NULL, HELLO_DEV->dev_no, NULL,
HELLO_DEVICE_DEVICE_NAME);
if (IS_ERR(hello_device)) {
printk(KERN_ERR "Failed to device_create\n");
return -1;
}
printk(KERN_INFO "device_create success\n");
HELLO_DEV->hello_device = hello_device;
return 0;
}
static void hello_device_exit(void) {
device_destroy(HELLO_DEV->hello_class, HELLO_DEV->dev_no);
}
static int hello_device_file_init(void) {
return device_create_file(HELLO_DEV->hello_device, &dev_attr_entry);
}
static void hello_device_file_exit(void) {
device_remove_file(HELLO_DEV->hello_device, (struct device_attribute *) &(dev_attr_entry.attr));
}
static int hello_alloc_init(void) {
return alloc_chrdev_region(&HELLO_DEV->dev_no, 0, 1, HELLO_DEVICE_DEVICE_NAME);
}
static void hello_alloc_exit(void) {
unregister_chrdev_region(HELLO_DEV->dev_no, 1);
}
// 模块加载方法
static int hello_init(void) {
int ret;
printk(KERN_INFO "Init hello device\n");
// 初始化 hello_reg_dev 结构体
ret = hello_val_init();
if (ret) {
goto err_kmalloc;
}
// 分配设备编号范围
ret = hello_alloc_init();
if (ret) {
printk(KERN_ERR "Failed to alloc_chrdev_region \n");
goto err_alloc_chrdev;
}
printk(KERN_INFO "alloc_chrdev_region success \n");
int hello_major = MAJOR(HELLO_DEV->dev_no);
int hello_minor = MINOR(HELLO_DEV->dev_no);
printk(KERN_INFO "hello_major %d ,hello_minor %d \n", hello_major, hello_minor);
// 注册 字符设备
ret = hello_dev_init();
if (ret) {
goto err_cdev_add;
}
// 创建 sys/class/virtual/ 目录
ret = hello_class_init();
if (ret) {
goto err_creat_class;
}
// 创建 /dev/hello 文件
ret = hello_device_init();
if (ret) {
goto err_create_device;
}
// 创建 sys/class/virtual/entry 文件
ret = hello_device_file_init();
if (ret) {
printk(KERN_ERR "Failed to device_create_file \n");
goto err_create_device_file;
}
printk(KERN_INFO "device_create_file success \n");
// 设置私有数据
dev_set_drvdata(HELLO_DEV->hello_device, HELLO_DEV);
// 创建 /proc/hello
ret = hello_proc_init();
if (ret) {
goto err_create_proc_file;
}
return 0;
err_create_proc_file:
hello_proc_exit();
err_create_device_file:
hello_device_file_exit();
err_create_device:
hello_device_exit();
err_creat_class:
hello_class_exit();
err_cdev_add:
hello_dev_exit();
err_alloc_chrdev:
hello_alloc_exit();
err_kmalloc:
hello_val_exit();
return ret;
}
// 模块卸载方法
static void hello_exit(void) {
// 删除 /proc/hello
hello_proc_exit();
// 删除 /sys/class/virtual/entry
hello_device_file_exit();
// 删除 /dev/hello
hello_device_exit();
// 删除 /sys/class/virtual 目录
hello_class_exit();
// 从系统中移除字符设备
hello_dev_exit();
// 释放分配的设备编号
hello_alloc_exit();
// 释放 HELLO_DEV
hello_val_exit();
}
module_init(hello_init)
module_exit(hello_exit)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cfeng25");
MODULE_DESCRIPTION("A hello Linux character device driver");
Makefile 文件
obj-m := hello_driver.o
# Path to the Linux kernel source code
KERNEL_SOURCE := /lib/modules/$(shell uname -r)/build
# Rules for building the module
all:
make -C $(KERNEL_SOURCE) M=$(PWD) modules
clean:
make -C $(KERNEL_SOURCE) M=$(PWD) clean
编译加载
安装内核编译器
sudo apt-get install gcc-12
进入源代码文件夹 执行 make 文件 会生成 hello_driver.ko 文件
加载驱动
sudo insmod hello_driver.ko
查看驱动
lsmod
卸载驱动
sudo rmmod hello_driver
查看内核日志
sudo dmesg
验证内核设备驱动
修改设备权限
sudo chmod 777 /dev/hello
读写驱动设备数据
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define DEVICE_PATH "/dev/hello"
int main() {
int fd;
char buffer[1024];
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
// 从设备中读取数据
if (read(fd, buffer, sizeof(buffer)) == -1) {
perror("read");
close(fd);
return 1;
}
// 输出读取的数据
printf("Data read from device: %s\n", buffer);
// 写入数据到设备
if (write(fd, "Hello from user space!\n", 23) == -1) {
perror("write");
close(fd);
return 1;
}
// 关闭设备文件
close(fd);
return 0;
}