字符设备驱动基础
文章目录
前言:
简单记录下理解不深刻且老是遗忘的一些点
后面也可以直接在此博客中抄写修改成对应的字符设备驱动
博客状态:
后续涉及到cdev_alloc和cdev_add的时候还会继续添加进来
闲聊:
大家觉得不错,记得一键三连,有什么好的建议也可以评论区评论,你们的支持就是我创作的动力,谢谢大家!!!
1.API
1.1 class_create
struct class *class_create(struct module *owner, const char *name);
作用:用于创建一个设备类,设备类是用来将设备组织起来并提供一些额外的信息的结构。
参数:
owner:指定设备类所属的模块,一般使用 THIS_MODULE 表示当前模块。
name:设备类的名称,可以通过 /sys/class/ 目录找到对应的设备类文件夹。
返回值:
成功:返回一个指向新创建的设备类的指针。
失败:返回一个指针类型的错误码(ERR_PTR())
1.2 device_create
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
作用:用于在设备类下创建一个设备节点。
参数:
class:指定设备节点所属的设备类。
parent:父设备指针,通常为 NULL。
devt:设备号,使用 MKDEV() 宏来生成。
drvdata:指向与设备相关的数据的指针。
fmt:设备节点的名称格式字符串。
返回值:
成功:返回一个指向新创建的设备节点的指针。
失败:返回一个指针类型的错误码(ERR_PTR())。
1.3 register_chrdev
函数原型:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
参数说明:
major:表示要注册的字符设备的主设备号,如果传入 0,则表示由内核自动分配主设备号。
name:字符设备的名称,在 /proc/devices 中会显示该名称。
fops:指向字符设备操作函数的指针,通常是一个 struct file_operations 结构体指针,包含了设备的操作函数。
返回值:
成功:返回注册成功的主设备号(如果之前传入的是 0 则表示由内核分配的主设备号)。
失败:返回一个负数作为错误码。
功能和作用:
注册字符设备:通过调用 register_chrdev 函数,可以向系统注册一个字符设备。注册成功后,系统会为该字符设备分配一个主设备号,并将该设备加入到字符设备列表中。
创建设备文件:注册字符设备后,我们可以通过创建设备文件来和该设备进行通信。设备文件通常会出现在 /dev/ 目录下,其中的名称就是在注册时传入的 name 参数。
指定设备操作函数:通过传入 fops 参数,我们可以指定字符设备的操作函数,比如读取、写入、控制等操作。这些操作函数会被内核调用来处理用户空间与设备之间的数据传输。
内核模块开发:register_chrdev 在 Linux 驱动开发中经常用于注册字符设备,特别是在编写内核模块时,需要注册自定义的字符设备以便应用程序能够与内核模块进行通信。
2.示例代码
2.1 只使用register_chrdev
irq_base.h
#ifndef __IRQ_BASE_H__
#define __IRQ_BASE_H__
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#define KMD_ERR(str) \
printk("%s %s line: %d %s \n", __FILE__, __FUNCTION__, __LINE__, str);
typedef struct irq_base
{
unsigned int major;
const char *name;
} irq_base_t;
// 字符设备初始化
static int irq_cdev_init(void);
#endif
irq_base.c
#include "irq_base.h"
#include "irq_sr501.h"
#define IRQ_BASE_NAME "IRQ_BASE"
irq_base_t irq_base = {
.name = IRQ_BASE_NAME,
};
static int irq_base_open(struct inode *inode, struct file *file)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -ret;
}
static ssize_t irq_base_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return ret;
}
static ssize_t irq_base_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return ret;
}
static int irq_base_release(struct inode *inode, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static const struct file_operations irq_base_fops = {
.owner = THIS_MODULE,
.open = irq_base_open,
.read = irq_base_read,
.write = irq_base_write,
.release = irq_base_release,
};
static int irq_cdev_init(void)
{
irq_base.major = register_chrdev(0, irq_base.name, &irq_base_fops);
if (irq_base.major < 0)
{
KMD_ERR("irq_base.major get ERR");
goto ERR_IO;
}
return 0;
ERR_IO:
return -EIO; //没有这样的设备或地址
}
/* 在入口函数 */
static int __init irq_base_init(void)
{
int ret = 0;
// printk(KERN_ERR "irq_sr501_init 测试\n");
if(irq_sr501_init())
{
printk(KERN_ERR "irq_sr501_init ERR\n");
return -EINVAL;
}
if((ret = irq_cdev_init()) != 0)
{
KMD_ERR("irq_cdev_init ERR");
return ret;
}
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
*/
static void __exit irq_base_exit(void)
{
irq_sr501_exit();
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_init(irq_base_init);
module_exit(irq_base_exit);
MODULE_DESCRIPTION("irq_base_driver");
MODULE_LICENSE("GPL");
其他的模块.c和.h文件这里就不贴代码了,只写了入口出口函数
现象:
小结:
register_chrdev()函数的作用:将file_operations操作结构体和字符设备绑定,
同时会在/proc/devices文件中添加对应的字符设备信息和主设备号
但是这个函数有缺陷,现在几乎很少用到,有兴趣可以去搜一下
2.1完整版的自动创建设备节点的字符设备驱动基础框架代码
irq_base.h
#ifndef __IRQ_BASE_H__
#define __IRQ_BASE_H__
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#define KMD_ERR(str) \
printk("%s %s line: %d %s \n", __FILE__, __FUNCTION__, __LINE__, str);
typedef struct irq_base
{
unsigned int major;
const char *name;
struct class *class;
struct device * device;
} irq_base_t;
// 字符设备初始化
static int irq_cdev_init(void);
#endif
irq_base.c
#include "irq_base.h"
#include "irq_sr501.h"
#define IRQ_BASE_NAME "IRQ_BASE"
static irq_base_t irq_base = {
.name = IRQ_BASE_NAME,
};
static int irq_base_open(struct inode *inode, struct file *file)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -ret;
}
static ssize_t irq_base_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return ret;
}
static ssize_t irq_base_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return ret;
}
static int irq_base_release(struct inode *inode, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static const struct file_operations irq_base_fops = {
.owner = THIS_MODULE,
.open = irq_base_open,
.read = irq_base_read,
.write = irq_base_write,
.release = irq_base_release,
};
static int irq_cdev_init(void)
{
// 在/proc/devices文件中添加对应的字符设备信息和主设备号
irq_base.major = register_chrdev(0, irq_base.name, &irq_base_fops);
if (irq_base.major < 0)
{
KMD_ERR("irq_base.major get ERR");
goto ERR_IO;
}
// 在/sys/class/目录下添加了一个IRQ_BASE的目录,
irq_base.class = class_create(THIS_MODULE, irq_base.name);
if(IS_ERR(irq_base.class))
{
KMD_ERR("irq_base.class get ERR");
return PTR_ERR(irq_base.class);
goto class_ERR;
}
irq_base.device = device_create(irq_base.class,NULL, MKDEV(irq_base.major,0), NULL, "%s", irq_base.name);
if(IS_ERR(irq_base.device))
{
KMD_ERR("irq_base.device get ERR");
return PTR_ERR(irq_base.device);
goto device_ERR;
}
return 0;
device_ERR:
class_destroy(irq_base.class);
class_ERR:
unregister_chrdev(irq_base.major, irq_base.name);
ERR_IO:
return -EIO; //没有这样的设备或地址
}
/* 在入口函数 */
static int __init irq_base_init(void)
{
int ret = 0;
// printk(KERN_ERR "irq_sr501_init 测试\n");
if(irq_sr501_init())
{
printk(KERN_ERR "irq_sr501_init ERR\n");
return -EINVAL;
}
if((ret = irq_cdev_init()) != 0)
{
KMD_ERR("irq_cdev_init ERR");
return ret;
}
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
*/
static void __exit irq_base_exit(void)
{
device_destroy(irq_base.class, MKDEV(irq_base.major, 0));
class_destroy(irq_base.class);
unregister_chrdev(irq_base.major, irq_base.name);
irq_sr501_exit();
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_init(irq_base_init);
module_exit(irq_base_exit);
MODULE_DESCRIPTION("irq_base_driver");
MODULE_LICENSE("GPL");
3.device_create和class_create 作用
class_create()接口添加之后,
在/sys/class/目录下添加了一个IRQ_BASE的目录,
device_create()接口添加之后,
在/sys/class/IRQ_BASE目录下再建一个目录,
其中dev文件里面存放着该设备的主次设备号信息
在/dev目录下新建立一个可供操作设备的文件,应用层操作这个文件就相当于操作对应的设备
4.makefile文件
arch ?= arm
modname ?= irq_base
TARGET := IRQ_MAIN
ifeq ($(arch),arm)
KERNELDIR := /home/psd/code_space/100ask_imx6ull-sdk/Linux-4.9.88
else
KERNELDIR := /lib/modules/$(shell uname -r)/build/
endif
PWD := $(shell pwd)
$(TARGET)-objs := irq_sr501.o irq_base.o #依赖的中间文件
obj-m += $(TARGET).o #生成最终TARGET.ko文件
all:
make -C $(KERNELDIR) M=$(PWD) modules
@echo "Copying module files to ~/nfs_rootfs/"
@if [ -d ~/nfs_rootfs ]; then \
if [ -e ~/nfs_rootfs/*.ko -o -e ~/nfs_rootfs/*.sh ]; then \
rm -f ~/nfs_rootfs/*.ko ~/nfs_rootfs/*.sh; \
fi; \
if [ -e ./*.ko ]; then \
cp -f ./*.ko ~/nfs_rootfs/; \
fi; \
if [ -e ./*.sh ]; then \
cp -f ./*.sh ~/nfs_rootfs/; \
fi; \
fi
clean:
make -C $(KERNELDIR) M=$(PWD) clean
#obj-m是模块化编译会生成.ko文件,obj-y则是编译进内核不会生成.ko文件
#-objs代表需要编译的依赖中间.o文件
5.shell脚本文件
lsmod
dmesg -c
rmmod *.ko
lsmod
dmesg
# 删除完之后打印下
insmod *.ko
dmesg
cat /proc/devices
ls /sys/class
ls /dev