字符设备驱动基础

本文详细解释了Linux字符设备驱动中的关键函数class_create、device_create和register_chrdev的作用,包括设备类管理、设备节点创建以及字符设备注册过程。同时还展示了如何在内核模块中实现这些功能的示例代码和实际应用。
摘要由CSDN通过智能技术生成

字符设备驱动基础

前言:
	简单记录下理解不深刻且老是遗忘的一些点
	后面也可以直接在此博客中抄写修改成对应的字符设备驱动
博客状态:
	后续涉及到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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值