该文章参考了很多资料和博主,以及自己的一些体会,放在这里仅供参考学习,大家共同进步
若出现侵权,请告知博主本人,本人会立即删除相应的文章和代码
字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写是分先后顺序的
1. 字符设备驱动结构
1.1 cdev结构体
路径:linux-5.10.186\include\linux\cdev.h
该结构体是Linux内核中用于表示字符设备的一个结构体
struct cdev {
struct kobject kobj; //内嵌的Kobject结构体
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作结构体
struct list_head list; //链表
dev_t dev; //设备号
unsigned int count; //多少文件(或进程)打开了该字符设备
} __randomize_layout;
1.1.1 dev_t dev 设备号
dev 是一个设备号,用于Linux设备的唯一标识。在Linux中,设备号由主设备号(major)和次设备号(minjor)组成,主设备号用于区分设备驱动程序,而次设备号则用于区分同一驱动程序下不同的设备
在注册字符设备(cdev_add)之前,是需要向系统申请设备号的,调用函数:
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
alloc_chrdev_region:用于设备号未知,动态的申请未被占用的设备号的情况下
register_chrdev_region:已知起始设备的设备号的情况下
register_chrdev:老版本使用的,可忽略
在注销字符设备(cdev_del)之后,需要释放申请的设备号,调用函数
unregister_chrdev_region(dev_t, unsigned)
MAJOR(dev_t dev);获取设备的主设备号
MINJOR(dev_t dev);获取设别的次设备号
MKDEV(int major, int minor);该宏通过主次设备号生成dev_t
1.1.2 file_operations结构体
该结构体中的成员函数是字符设备驱动程序设计的主题内容,这些函数实际会在应用程序进行Linux的open()/close()/read()/write()等系统调用时最终被内核调用
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
bool may_pollfree;
} __randomize_layout;
1.2 cdev结构体操作函数
1.2.1 cdev_init
该函数用于初始化cdev成员,并建立cdev和file_operations之间的连接
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
1.2.2 cdev_alloc
该函数用于动态申请一个cdev内存
/**
* cdev_alloc() - allocate a cdev structure
*
* Allocates and returns a cdev structure, or NULL on failure.
*/
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
1.2.3 cdev_add
该函数用于将一个字符设备添加到系统中,使其能够被访问,完成字符设备的注册。
该函数的调用常发生在字符设备驱动模块的加载函数**module_init()**中
cdev_add函数还会为每个指定的设备号初始化一个cdev结构体,并将其添加到内核的字符设备列表中,以便后续可以通过设备号来访问这些设备。如果cdev_add调用成功,它会返回0;如果失败,则返回一个负的错误码。
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
if (WARN_ON(dev == WHITEOUT_DEV))
return -EBUSY;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
1.2.4 cdev_del
和cdev_add刚好相反,从系统中删除一个字符设备,完成字符设备的注销
该函数的调用常发生在字符设备驱动模块的卸载函数module_exit() 中
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*
* NOTE: This guarantees that cdev device will no longer be able to be
* opened, however any cdevs already open will remain and their fops will
* still be callable even after cdev_del returns.
*/
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
1.2.1 cdev_put
void cdev_put(struct cdev *p)
{
if (p) {
struct module *owner = p->owner;
kobject_put(&p->kobj);
module_put(owner);
}
}
1.3 设备节点
设备节点是用于文件系统中表示设备的特殊文件,设备节点位于 /dev 目录下,文件名由设备类型和设备号组成,通过设备节点,应用程序可以与设备进行交互,打开关闭/读取写入等操作
1.3.1 手动创建设备节点
手动创建节点时通过mknod指令
基本语法
mknod [OPTIONS] NAME TYPE [MAJOR MINOR]
OPTIONS:可选参数,如-m用于设置文件权限,-Z用于设置安全的上下文等。
NAME:要创建的设备文件或命名管道的名称。
TYPE:设备文件的类型,可以是b(块设备)、c(字符设备)或p(命名管道)。
MAJOR 和 MINOR:当TYPE为b或c时,需要指定主设备号和次设备号。这两个号码用于唯一标识设备。
如果需要创建一个名为/dev/mychar的字符设备文件,主设备号为230,次设备号为0,可以使用:
sudo mknod /dev/mychar c 230 0
1.3.2 自动创建设备节点
自动创建节点依赖于两个函数:class_create()/device_create()
节点的删除依赖与:device_destroy()/class_destroy();
需要注意使用的顺序:先创建类,再创建设备,先删除设备再删除类
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
//class:给哪个类创建设备
//parent:父设备,一般为NULL
//devt:设备号
//drvdata:设备可能会使用到的数据
//fmt:设备名字
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, vargs);
va_end(vargs);
return dev;
}
2. 字符设备驱动的组成
Linux编码习惯是位设别定义一个设备相关的结构体,该结构体包含设备的cdev、私有数据、锁等
struct chrtest_dev {
struct cdev cdev;
struct class * class;
struct device * device;
dev_t devid;
int major;
int minjor;
}test_dev;
2.1 字符设备驱动模块的加载和卸载
- 在加载函数中应实现设备号的申请和cdev注册
- 在卸载函数中应实现设备号的释放和cdev注销
//设备驱动加载函数
static int __init test_init(void) {
...
cdev_init(&test_dev.cdev, &test_fops);
test_dev.cdev.owner = THIS_MODULE;
if (test_major) {
register_chrdev_region(test_dev.cdev.dev, 1, DEV_NAME);
}
else {
alloc_chrdev_region(&test_dev.cdev.dev, 0, 1, DEV_NAME);
}
ret = cdev_add(&test_dev.cdev, &test.cdev.dev, count)
return ret;
...
}
module_init(test_init);
//驱动模块卸载函数
static void __exit test_exit(void) {
...
cdev_del(&test_dev.cdev); //注销设备
unregister_chrdev_region(&test_dev.cdev.dev);//注销设备号
...
}
module_exit(test_exit);
2.2 file_operations成员实现
该结构体中的成员函数的字符设备驱动与内核虚拟文件系统的接口,是用户空间对Linux进行系统调用最终的落实者,大多数驱动都会实现read()/write()/open()/release()/ioctl()
static int chr_open(struct inode *inode, struct file *file);
static int chr_release(struct inode *, struct file *);
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt);
static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt);
3. 代码示例
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define CHR_TEST_NAME "chr_test"
#define CHR_DEV_COUNT 1
#define CHEDEV_TASK_0 0
#define CHEDEV_TASK_1 1
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区
static int __init test_init(void);
static void __exit test_exit(void);
static int chr_open(struct inode *inode, struct file *file);
static int chr_release(struct inode *, struct file *);
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt);
static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt);
struct chrtest_dev {
struct cdev cdev;
struct class * class;
struct device * device;
dev_t devid;
int major;
int minjor;
}chrtest;
static const struct file_operations test_fops = {
.owner = THIS_MODULE,
.read = chr_read,
.write = chr_write,
.open = chr_open,
.release = chr_release,
};
static int __init test_init(void) {
// u32 val = 0;
// /* 初始化 LED */
// /* 1、寄存器地址映射 */
// IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
// SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
// SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
// GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
// GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
// /* 2、使能 GPIO1 时钟 */
// val = readl(IMX6U_CCM_CCGR1);
// val &= ~(3 << 26); /* 清楚以前的设置 */
// val |= (3 << 26); /* 设置新值 */
// writel(val, IMX6U_CCM_CCGR1);
// /* 3、设置 GPIO1_IO03 的复用功能,将其复用为GPIO1_IO03,最后设置 IO 属性*/
// writel(5, SW_MUX_GPIO1_IO03);
// /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
// writel(0x10B0, SW_PAD_GPIO1_IO03);
// /* 4、设置 GPIO1_IO03 为输出功能 */
// val = readl(GPIO1_GDIR);
// val &= ~(1 << 3); /* 清除以前的设置 */
// val |= (1 << 3); /* 设置为输出 */
// writel(val, GPIO1_GDIR);
// /* 5、默认关闭 LED */
// val = readl(GPIO1_DR);
// val |= (1 << 3);
// writel(val, GPIO1_DR);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (chrtest.major) {
chrtest.devid = MKDEV(chrtest.major, 0);
register_chrdev_region(chrtest.devid, CHR_DEV_COUNT, CHR_TEST_NAME);
} else {
alloc_chrdev_region(&chrtest.devid, 0, CHR_DEV_COUNT, CHR_TEST_NAME);
}
/* 2、初始化 cdev */
cdev_init(&chrtest.cdev, &test_fops);
chrtest.cdev.owner = THIS_MODULE;
/* 3、添加一个 cdev */
cdev_add(&chrtest.cdev, chrtest.devid, CHR_DEV_COUNT);
/* 5、创建类和设备 */
chrtest.class = class_create(THIS_MODULE, CHR_TEST_NAME);
chrtest.device = device_create(chrtest.class, NULL, chrtest.devid, NULL, CHR_TEST_NAME);
return 0;
}
static void __exit test_exit(void) {
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备 */
cdev_del(&chrtest.cdev);/* 删除 cdev */
unregister_chrdev_region(chrtest.devid, CHR_DEV_COUNT);
device_destroy(chrtest.class, chrtest.devid);
class_destroy(chrtest.class);
}
static int chr_open(struct inode *inode, struct file *file) {
file -> private_data = &chrtest;
return 0;
}
static int chr_release(struct inode *, struct file *) {
return 0;
}
/*
* @description : 从设备读取数据
* @param - file : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - count : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt) {
int ret = 0;
memcpy(readbuf, CHR_TEST_NAME, sizeof(CHR_TEST_NAME));
ret = copy_to_user(buf, readbuf, count);
if(ret == 0) {
printk("from kernel read ok!!!\r\n");
} else {
printk("from kernel read err!!!\r\n");
return -1;
}
return count;
}
static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt) {
int ret = 0;
long int num;
ret = copy_from_user(writebuf, buf, count);
if(ret == 0) {
printk("write to kernel ok == %s!!!\r\n", writebuf);
} else {
printk("from kernel read err!!!\r\n");
return -1;
}
num = simple_strtol(writebuf, NULL, 10);
switch (num)
{
case CHEDEV_TASK_0:
printk("CHEDEV_TASK_0\r\n");
break;
case CHEDEV_TASK_1:
printk("CHEDEV_TASK_0\r\n");
break;
default:
printk("default\r\n");
break;
}
return count;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qiao");