news 2026/1/27 2:59:47

字符设备驱动(5)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
字符设备驱动(5)

一 、简介

字符设备与杂项设备的区别:
(1)杂项设备的主设备号是固定为10,学习字符类设备就需要自己或者系统来分配
(2)杂项设备可以自动生成设备节点,字符设备需要自己生成设备节点

二、申请设备号

注册字符类设备号的两个方法:
第一种:静态分配一个设备号
第二中:

2.1 静态分配设备号

静态分配一个设备号需要知道哪些设备号被用了,哪些设备号没有被用

register_chrdev_region(dev_t, unsigned, const char *);

头文件:

函数原型:
函数功能:
函数参数:
返回值:

头文件:#incldue<linux/fs.h>
函数原型:int register_chrdev_region(dev_t from, unsigned count, const char *name)
函数功能:静态分配一段连续的字符设备号,供字符设备驱动程序使用
函数参数:
@param1 from:要分配的起始设备号(包含主次设备号)(类型为dev_t数据类型)
@param2 连续分配的设备号数量
@param3 设备名称(出现在/proc/devices中)
返回值:成功返回0,失败返回错误码

头文件:#incldue<linux/types.h>
宏定义:

typedef u32 __kernel_dev_t;

typedef __kernel_dev_t dev_t;

作用:dev_t 是用来保存设备号的,是一个32位数;高12为用来保存主设备号,低20位用来保存次设备号

LInux 提供了几个宏定义来操作设备号
头文件:#incldue<linux/kdev_t.h>

#define MINORBITS 20 //次设备号位数,一共20位 #define MINORMASK ((1U << MINORBITS) - 1) //次设备号掩码 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))//在dev_t中获取主设备号 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //在dev_t中获取次设备号 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //将获取的主设备号和次设备号组成一个dev_t类型,第一个参数是住设备号,第二个参数是次设备号

2.2 动态分配设备号

头文件:#incldue<linux/fs.h>
函数原型:int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *)
函数功能:动态分配设备号
函数参数:
@param1 dev_t * 保存生成的起始设备号

@param2 unsigned 请求的第一个次设备号,通常为0

@param3 unsigned 连续申请的设备号个数

@param4 const char *name:设备名称(出现在/proc/devices中)

返回值:成功返回0,失败返回错误码

使用动态分配会优先使用255~234之间的设备号

2.3 注销设备号

头文件:#incldue<linux/fs.h>

函数原型:void unregister_chrdev_region(dev_t, unsigned);
函数功能:释放注册过的设备号
函数参数:

@param1 dev_t * 要释放的起始设备号

@param2 unsigned 要释放的连续设备号数量

2.4 代码部分

建议使用动态分配设备号;可通过终端命令cat /proc/devices查看设备号是否存在

#include <linux/init.h> #include <linux/module.h> #include<linux/fs.h> #include<linux/kdev_t.h> #define DEV_NUM 1 //定义分配连续申请的设备号数量 #define MION_DEV_NUM 0 //定义请求的第一个次设备号 #define DEV_SNAME "schrdev" //定义静态分配的设备号名称 #define DEV_ANAME "achrdev" //定义动态分配的设备号名称 static int major_num,minor_num;//定义两个变量用于接受主次设备号 dev_t dev_num;//定义dev_t变量用于接收设备设备号 module_param(major_num,int,0644); module_param(minor_num,int,0644); static int chrdev_init(void) { int ret; if(major_num)//静态分配设备号 { printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); dev_num= MKDEV(major_num,minor_num);//合成设备号 ret=register_chrdev_region(dev_num,DEV_NUM,DEV_SNAME); if(ret!=0) { printk("register_chrdev_region failed!\n"); return -1; } printk("register_chrdev_region succeed!\n"); } else { ret=alloc_chrdev_region(&dev_num,MION_DEV_NUM,DEV_NUM,DEV_ANAME); if(ret!=0) { printk("alloc_chrdev_region succeed!\n"); return -1; } major_num=MAJOR(dev_num);//取出主设备号 minor_num=MINOR(dev_num);//取出此设备号 printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); } return 0; } static void chrdev_exit(void) { unregister_chrdev_region(dev_num,DEV_NUM); printk("param exit rmmod succeed!\n"); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE("GPL");

三、注册设备到内核

步骤一:定义一个cdev结构体

cdev结构体

头文件:#include <linux/cdev.h>

struct cdev { struct kobject kobj; // 1. 内核对象基础结构 struct module *owner; // 2. 所属模块 const struct file_operations *ops; // 3. 设备操作函数集 struct list_head list; // 4. 链表节点 dev_t dev; // 5. 设备号 unsigned int count; // 6. 设备数量 };

步骤二:初始化一个字符设备结构体(cdev)

头文件:#include <linux/cdev.h>
函数原型:void cdev_init(struct cdev *, const struct file_operations *);

函数功能:初始化一个字符设备结构体(cdev),将其与指定的文件操作函数集(fops)关联,为后续添加到内核系统做准备

函数参数:

@param1:指向要初始化的字符设备结构体(cdev)的指针
@param2:指向设备对应的文件操作函数集(file_operations)的指针

步骤三:将初始化好的字符设备注册到内核

头文件:#include <linux/cdev.h>
函数原型:int cdev_add(struct cdev *cdev, dev_t dev, unsigned count)
函数功能:将初始化好的字符设备注册到内核,使其生效
函数参数:
@param1cdev已初始化的字符设备结构体
@param2dev:起始设备号
@param3count:连续设备号的数量
返回值:成功返回0,失败返回错误码(负数)

注销字符设备

头文件:#include <linux/cdev.h>
函数原型:void cdev_del(struct cdev *cdev)
函数功能:从内核中移除字符设备
函数参数:
@param1cdev:要删除的字符设备结构体

注意:该函数在代码中的执行顺序

字符设备注册完以后会自动生成设备节点,但是字符类设备经过设备号申请、注册设备到内核、这来两步是没有办法生成设备节点的,需要通过mknod命令手动添加设备节点

格式:

mknod 名称 类型 主设备号 次设备号

eg:mknod /dev/test c 247 0 将主设备号为247,次设备号为0的字符类设备生成/dev/test节点

代码

驱动层:

#include <linux/init.h> #include <linux/module.h> #include<linux/fs.h> #include<linux/kdev_t.h> #include <linux/cdev.h> #define DEV_NUM 1 //定义分配连续申请的设备号数量 #define MION_DEV_NUM 0 //定义请求的第一个次设备号 #define DEV_SNAME "schrdev" //定义静态分配的设备号名称 #define DEV_ANAME "achrdev" //定义动态分配的设备号名称 static int major_num,minor_num;//定义两个变量用于接受主次设备号 struct cdev cdev; //定义一个cdev类型结构体 static int cdev_open(struct inode *inode, struct file *file) { printk("open this cdev succeed\n"); return 0; } struct file_operations cdev_fops= { .owner =THIS_MODULE, .open=cdev_open }; //定义文件操作集结构体 module_param(major_num,int,0644); module_param(minor_num,int,0644); static int chrdev_init(void) { int ret1,ret2; dev_t dev_num;//定义dev_t变量用于接收设备设备号 /*申请设备号*/ if(major_num)//静态分配设备号 { printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); dev_num= MKDEV(major_num,minor_num);//合成设备号 ret1=register_chrdev_region(dev_num,DEV_NUM,DEV_SNAME); if(ret1!=0) { printk("register_chrdev_region failed!\n"); return -1; } printk("register_chrdev_region succeed!\n"); } else { ret1=alloc_chrdev_region(&dev_num,MION_DEV_NUM,DEV_NUM,DEV_ANAME); if(ret1!=0) { printk("alloc_chrdev_region succeed!\n"); return -1; } major_num=MAJOR(dev_num);//取出主设备号 minor_num=MINOR(dev_num);//取出此设备号 printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); } /*将设备号注册到内核*/ cdev.owner=THIS_MODULE; cdev_init(&cdev,&cdev_fops);//初始化一个字符设备结构体(cdev),将其与指定的文件操作函数集(fops)关联 ret2=cdev_add(&cdev,dev_num,DEV_NUM);//注册设备到内核 if(ret2!=0) { printk("cdev_add failed\n"); return -2; } return 0; } static void chrdev_exit(void) {//注意注销顺序 unregister_chrdev_region(MKDEV(major_num,minor_num),DEV_NUM);//注销设备号 cdev_del(&cdev);//注销设备到内核 printk("param exit rmmod succeed!\n"); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE("GPL");

手动生成设备节点

应用层:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main(int argc, char *argv[]) { int fd; fd = open("/dev/my_cdev", O_RDWR); //打开驱动对应的设备文件 if(fd < 0) { printf("my_cdev device open failed\n"); return -1; } printf("app layer device open success\n"); close(fd); return 0; }

四、自动创建设备节点

1.问题:怎么自动创建一个设备节点?
在嵌入式Linux中使用mdev来实现设备节点文件的自动创建和删除

2.什么是mdev?
mdev是udev的简化版,是busybox中所带的程序,最适合用在嵌入式系统

3.什么是udev?
udev是一种工具,它能够根据系统中的银行间设备状态动态更新设备文件,包括文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC上的linux中,相对mdev来说要复杂一些

4.怎么自动创建设备节点?
自动创建设备节点分为两个步骤:
步骤一:使用class_create函数出创建一个类
步骤二:使用device_create函数在创建的类下面创建一个设备

4.1创建和删除类函数

创建类:

头文件:#include<linux/device.h>

extern struct class * __must_check __class_create(struct module *owner, const char *name, struct lock_class_key *key); #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })

功能:创建的类
参数:
@param1 owner 一般为THIS_MODULE
@param2 name 创建的类的名字(在/sys/class下生成对应名字的类)

返回值:指向结构体class的指针

头文件:#include<linux/device.h>

void class_destroy(struct class *cls);

函数参数:cls 要删除的类

函数功能:删除创建的类

在创建的类下面创建设备节点

头文件:#include<linux/device.h>

struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

函数功能:在类下面创建设备节点
函数参数:
@param1 cls 设备要创建哪个类下面

@param2 parent 父设备,一般为NULL,也就是没有父设备

@param3 devt 设备号

@param4 drvdata 是设备可能会使用的一些数据,一般为NULL

@param5 fmt 设备节点名称,若是fmt=xxx,则会生成/dev/xxx这个设备文件
返回值:创建好的设备

头文件:#include<linux/device.h>

void device_destroy(struct class *cls, dev_t devt);

函数功能:删除设备节点

函数参数:
@param1 cls 设备节点所在的类

@param2 devt 设备号

4.2 代码

驱动代码

#include <linux/init.h> #include <linux/module.h> #include<linux/fs.h> #include<linux/kdev_t.h> #include <linux/cdev.h> #include <linux/device.h> #define DEV_NUM 1 //定义分配连续申请的设备号数量 #define MION_DEV_NUM 0 //定义请求的第一个次设备号 #define DEV_SNAME "schrdev" //定义静态分配的设备号名称 #define DEV_ANAME "achrdev" //定义动态分配的设备号名称 #define DEV_CLASS_NAME "chrdev_class" //定义创建的类的名字 #define DEV_NODE_NAME "my_cdev" static int major_num,minor_num;//定义两个变量用于接受主次设备号 struct cdev cdev; //定义一个cdev类型结构体 struct class*class; //定义一个class类型的结构体指针 struct device*device; // 定义一个device类型的结构体指针 static int cdev_open(struct inode *inode, struct file *file) { printk("open this cdev succeed\n"); return 0; } struct file_operations cdev_fops= { .owner=THIS_MODULE, .open=cdev_open }; //定义文件操作集结构体 module_param(major_num,int,0644); module_param(minor_num,int,0644); static int chrdev_init(void) { int ret1,ret2; dev_t dev_num;//定义dev_t变量用于接收设备设备号 /*申请设备号*/ if(major_num)//静态分配设备号 { printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); dev_num= MKDEV(major_num,minor_num);//合成设备号 ret1=register_chrdev_region(dev_num,DEV_NUM,DEV_SNAME); if(ret1!=0) { printk("register_chrdev_region failed!\n"); return -1; } printk("register_chrdev_region succeed!\n"); } else//动态分配设备号 { ret1=alloc_chrdev_region(&dev_num,MION_DEV_NUM,DEV_NUM,DEV_ANAME); if(ret1!=0) { printk("alloc_chrdev_region succeed!\n"); return -1; } major_num=MAJOR(dev_num);//取出主设备号 minor_num=MINOR(dev_num);//取出次设备号 printk("major_num=%d\n",major_num); printk("minor_num=%d\n",minor_num); } /*将设备号注册到内核*/ cdev.owner=THIS_MODULE; cdev_init(&cdev,&cdev_fops);//初始化一个字符设备结构体(cdev),将其与指定的文件操作函数集(fops)关联 ret2=cdev_add(&cdev,dev_num,DEV_NUM);//注册设备到内核 if(ret2!=0) { printk("cdev_add failed\n"); return -2; } /*自动生成设备节点*/ class=class_create(THIS_MODULE,DEV_CLASS_NAME); //创建类 device=device_create(class,NULL,dev_num,NULL,DEV_NODE_NAME);//生成设备节点 return 0; } static void chrdev_exit(void) { unregister_chrdev_region(MKDEV(major_num,minor_num),DEV_NUM);//注销设备号 cdev_del(&cdev);//注销设备到内核 device_destroy(class,MKDEV(major_num,minor_num));//注销设备节点 class_destroy(class);//注销创建的类 printk("param exit rmmod succeed!\n"); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE("GPL");

应用层

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main(int argc, char *argv[]) { int fd; fd = open("/dev/my_cdev", O_RDWR); //打开驱动对应的设备文件 if(fd < 0) { printf("my_cdev device open failed\n"); return -1; } printf("app layer device open success\n"); close(fd); return 0; }

4.3 结果

通过 cat /proc/devices查看申请的设备号以设备号名称

通过ls /sys/class查看生成的类

通过ls /dev/命令查看生成的设备节点

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/17 0:15:52

Flutter 状态管理全家桶:Provider、Bloc、GetX 实战对比

Flutter 状态管理全家桶&#xff1a;Provider、Bloc、GetX 实战对比 在 Flutter 开发中&#xff0c;状态管理是贯穿项目全生命周期的核心议题。从简单的按钮点击状态切换&#xff0c;到复杂的跨页面数据共享与业务逻辑联动&#xff0c;选择合适的状态管理方案直接决定了项目的…

作者头像 李华
网站建设 2026/1/22 3:13:53

Flutter 网络请求完全指南:Dio 封装与拦截器实战

Flutter 网络请求完全指南&#xff1a;Dio 封装与拦截器实战 在 Flutter 开发中&#xff0c;网络请求是连接前端与后端服务的核心桥梁&#xff0c;直接影响应用的交互体验与数据流转效率。Dio 作为 Flutter 生态中最主流的网络请求库&#xff0c;支持 RESTful API、FormData、…

作者头像 李华
网站建设 2026/1/19 16:49:17

DPO微调

&#x1f34b;&#x1f34b;AI学习&#x1f34b;&#x1f34b;&#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主…

作者头像 李华
网站建设 2025/12/19 15:56:05

内容管理系统(CMS)的7个关键特点

一套高效的内容管理系统&#xff08;CMS&#xff09;能帮你节省时间、开辟内容个性化的空间&#xff0c;并提升在线形象——从而改善业务成效。合适的CMS可以保持数字形象井然有序、品牌风格统一&#xff0c;并让内容流程顺畅运转&#xff0c;有助于在营销各个环节吸引并留住潜…

作者头像 李华
网站建设 2026/1/22 6:42:19

Prompt Tuning

&#x1f34b;&#x1f34b;AI学习&#x1f34b;&#x1f34b;&#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主…

作者头像 李华
网站建设 2025/12/17 0:11:59

网盘直链解析工具:零基础实现全平台高速下载

还在为网盘下载速度慢而烦恼吗&#xff1f;这款基于JavaScript开发的网盘直链解析工具能够帮助您获取文件的真实下载地址。项目基于"网盘直链下载助手"6.1.4版本优化&#xff0c;为用户提供更纯净、更高效的使用体验。 【免费下载链接】Online-disk-direct-link-down…

作者头像 李华