11_注册字符类设备
1.01_Linux最简单驱动-helloworld2.02_Linux下编译驱动模块实践3.03_make menuconfig图形化配置4.04_Linux下把驱动编译进内核5.05_杂项设备驱动6.06_应用层和内核层实现数据交互7.07_Linux物理地址到虚拟地址映射8.08_第一个相对完整的驱动实践编写9.09_驱动模块传参数10.10_申请字符类设备号
11.11_注册字符类设备
12.12_自动创建设备节点13.13_Platform 设备驱动14.14_Linux 设备树15.15_pinctl和gpio子系统16.16_ioctl接口17.17_Linux中断18.18_内核定时器19.19_输入子系统20.20_Linux I2C 驱动21.21_FT5X06触摸驱动实验注册字符类设备
注册字符类设备简介
在 Linux 内核中, 使用 cdev 结构体描述一个字符设备, cdev 结构体的定义如下:
struct cdev { //描述字符设备的一个结构体
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev 结构体的 dev_t 成员定义了设备号, 为 32 位, 其中 12 位为主设备号, 20 位为次设备号。 使用下列宏可以从 dev_t 获得主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和次设备号生成 dev_t
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_add( ) 函数和 cdev_del( ) 函数分别向系统添加和删除一个 cdev, 完成字符设备的注册和注销。 对cdev_add( ) 的调用通常发生在字符设备驱动模块加载函数中, 而对 cdev_del( ) 函数的调用则通常发生在字符设备驱动模块卸载函数中
头文件
#include <linux/cdev.h>
void cdev_init(struct cdev *, const struct file_operations *);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
生成设备节点
字符设备注册完以后不会自动生成设备节点。 我们需要使用 mknod 命令创建一个设备节点
格式: mknod 名称 类型 主设备号 次设备号
举例:
mknod /dev/test c 247 0
示例
chrdev.c
#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define DEVICE_NUMBER 1 // 次设备号的个数
#define DEVICE_SNAME "schrdev" // 静态注册设备的名称
#define DEVICE_ANAME "achrdev" // 动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 // 次设备号的起始地址
static int major_num; // 主设备号
static int minor_num; // 次设备号
struct cdev cdev; //字符设备
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
}
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open
};
static int hello_init(void)
{
dev_t dev_num; // 设备号
int ret;
if (major_num)
{
dev_num = MKDEV(major_num, minor_num); // 主设备号和次设备号组成一个 dev_t 设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //静态分配设备号
if(ret < 0)
{
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
else
{
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); //动态分配设备号
if(ret < 0)
{
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major_num = MAJOR(dev_num); //在 dev_t 里面获取我们的主设备号
minor_num = MINOR(dev_num); //在 dev_t 里面获取我们的次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops); //初始化字符设备
cdev_add(&cdev, dev_num, DEVICE_NUMBER); //注册字符设备
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); //注销设备号
cdev_del(&cdev); //注销字符设备
printk("byby\n"); // 内核模块卸载的时候打印"byb byb
}
module_init(hello_init); // 驱动模块的入口
module_exit(hello_exit); // 驱动模块的出口
MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证
app.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char* argv[])
{
int fd;
fd = open("/dev/test", O_RDWR);
if(fd < 0)
{
perror("open error\n");
return -1;
}
close(fd);
return 0;
}
Makefile
obj-m +=chrdev.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」