news 2026/4/16 11:00:48

杂项设备驱动/应用层与内核层数据传输

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
杂项设备驱动/应用层与内核层数据传输

声明:内容源于B站UP主——北京迅为电子

一、简介

字符设备:IO的传递传递过程是以字符设备为单位的,没有缓冲,比如I2C,SPI都是字符设备
块设备:IO传递过程是一块为单位的,跟存储相关的,都属于块设备
网络设备:与前面两个不一样,是以sockett套接字来访问的

1.1 杂项设备特点

杂项设备使字符设备的一种,可以自动生成设备节点
系统中有很多杂项设备,可以通过cat /proc/misc命令来查看

问题一:杂项设备除了比字符设备代码简单,还有别的区别吗?
杂项设备的主设备号都是相同的,均为10,次设备是不同的,主设备号相同就可以节省内核资源

问题二:主设备号与次设备号是什么?
设备号包含主设备号和此设备号,主设备号在Linux系统里面是唯一的,次设备号不一定唯一
主设备号可以通过命令cat /proc/devices

二、杂项设备

2.1 杂项设备描述

定义在内核源码路径下:include/linux/miscdevice

struct miscdevice { int minor; //次设备号。可设为 MISC_DYNAMIC_MINOR(通常为255),让内核自动分配一个空闲的。 const char *name; //设备名称。注册成功后,会在 /dev/ 下生成以此命名的设备节点 const struct file_operations *fops;//文件操作集。指向实现设备操作函数集(如 open、read、write、ioctl 等) struct list_head list; struct device *parent; struct device *this_device; const struct attribute_group **groups; const char *nodename; umode_t mode; }; extern int misc_register(struct miscdevice *misc); //注册杂项设备 extern void misc_deregister(struct miscdevice *misc); //注销杂项设备

file_operations文件操作集定义在incldue/linux/fs.h下面

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 (*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); int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64); int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64); int (*fadvise)(struct file *, loff_t, loff_t, int); ANDROID_KABI_RESERVE(1); ANDROID_KABI_RESERVE(2); ANDROID_KABI_RESERVE(3); ANDROID_KABI_RESERVE(4); } __randomize_layout;

里面的一个结构体成员都对应一个调用

2.2 注册杂项设备的流程

①填充miscdevice这个结构体
②填充file_operations这个结构体

③注册杂项设备并生成设备节点

/*模板*/ struct const file_operation xx_fops= { .owner = THIS_MODULE, //固定写法 ...... }; struct miscdevice xx_dev= { .minor = MISC_DYNAMIC_MINOR, //自动分配次设备号 .name = "xxx", //定义自己的杂项设备名称 .fops=&xx_fops //设备操作函数集合 }; static int xxx_init() { int ret; ret=misc_misc_register(&xx_dev); //注册设备 if(ret<0) { printk("misc register error\n"); return -1; } return 0; } void xxx_exit() { misc_deregister(&xx_dev); //注销设备 }

三、编写一个杂项设备驱动

编写misc_hello.c文件

#include <linux/module.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/fs.h> //2.填充file_operations结构体 struct file_operations misc_fops= { .owner = THIS_MODULE //固定写法 }; //1.填充miscdevice结构体 struct miscdevice misc_dev= { .minor = MISC_DYNAMIC_MINOR, //自动分配次设备号 .name = "hello_misc", //定义自己的杂项设备名称 .fops=&misc_fops //设备操作函数集合 }; static int misc_init(void) //驱动入口函数 { int ret; ret = misc_register(&misc_dev); //注册杂项设备 if(ret<0) { printk("misc device is register error\n"); return -1; } printk("misc device is register succeed\n");//注意: 内核打印用 printk 而不是 printf return 0; } static void misc_exit(void) //驱动出口函数 { misc_deregister(&misc_dev); //注销杂项设备 printk("misc device is deregister\n"); } module_init(misc_init); //告诉linux模块入口函数,加载模块代码到操作系统 module_exit(misc_exit); //卸载 MODULE_LICENSE("GPL v2"); //同意 GPL 开源协议

编写Makefile文件

export ARCH=arm64 #设置平台架构 export CROSS_COMPILE=aarch64-linux-gnu- #交叉编译器前缀 obj-m += helloworld.o #m表示将helloworld.o编译为模块 KDIR := ~/LubanCat_SDK/kernel #内核源码路径 PWD ?= $(shell pwd) #获取当前目录 all: make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules #make -C $(KDIR)切换到内核目录;M=$(PWD) modules将当前路径下的代码编译为模块 clean: make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean #清理生成的文件

注意:make -C $(KDIR) 会切换到内核源码目录并启动一个新的 make 进程。这个新进程的环境变量可能不会完全继承父进程(即Makefile)的导出变量,尤其是当内核源码目录的 Makefile 有自己的默认设置时。
显式传递参数(如 ARCH=arm64)可以确保内核的 make 系统使用正确的架构和编译器,而不受环境变量继承问题的影响。

四、文件操作函数集

问题:如果我们在应用层使用系统IO对设备节点进行打开,关闭读写等操作时,会发生什么呢?

当在应用层read设备节点的时候,就会触发驱动里面read这个函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

当在应用层write设备节点的时候,就会触发驱动里面write这个函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

当在应用层poll/select设备节点的时候,就会触发驱动里面poll/select这个函数
__poll_t (*poll) (struct file *, struct poll_table_struct *);

当在应用层ioctl设备节点的时候,就会触发驱动里面ioctl这个函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long)

当在应用层open设备节点的时候,就会触发驱动里面open这个函数
int (*open) (struct inode *, struct file *);

当在应用层close设备节点的时候,就会触发驱动里面close这个函数
int (*release) (struct inode *, struct file *);

/*驱动层代码*/ #include <linux/module.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/fs.h> //3.实现open函数 static int misc_open(struct inode *inode, struct file *file) { printk("misc device is opened:hello misc open\n"); return 0; } static int misc_release(struct inode *inode, struct file *file) { printk("misc device is released:hello misc release\n"); return 0; } static ssize_t misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("misc device is read:hello misc read\n"); return 0; } static ssize_t misc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { printk("misc device is write:hello misc write\n"); return 0; } //2.填充file_operations结构体 struct file_operations misc_fops= { .owner = THIS_MODULE, //固定写法 .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write }; //1.填充miscdevice结构体 struct miscdevice misc_dev= { .minor = MISC_DYNAMIC_MINOR, //自动分配次设备号 .name = "hello_misc", //定义自己的杂项设备名称 .fops = &misc_fops //设备操作函数集合 }; static int misc_init(void) //驱动入口函数 { int ret; ret = misc_register(&misc_dev); //注册杂项设备 if(ret<0) { printk("misc device is register error\n"); return -1; } printk("misc device is register succeed\n");//注意: 内核打印用 printk 而不是 printf return 0; } static void misc_exit(void) //驱动出口函数 { misc_deregister(&misc_dev); //注销杂项设备 printk("misc device is deregister\n"); } module_init(misc_init); //告诉linux模块入口函数,加载模块代码到操作系统 module_exit(misc_exit); //卸载 MODULE_LICENSE("GPL v2"); //同意 GPL 开源协议
/*上层应用,用于测试应用层调用open函数,驱动层是否有相应的反应*/ #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; char buf[64]={0}; fd = open("/dev/hello_misc", O_RDWR); //打开驱动对应的设备文件 if(fd < 0) { printf("misc device open failed\n"); return -1; } printf("app layer device open success\n"); read(fd,buf,sizeof(buf)); close(fd); return 0; }

设备节点就是连接上层应用和底层驱动的桥梁

问题:假如在驱动层代码中file_operitions 里面没有read,但是在应用层有read设备节点代码的时候会发生什么?
答:什么也不会发生,也不会报错

问题:应用层和内核层是不能直接进行数据传输的

//用户层向内核层传递数据 static __always_inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n) //内核层向用于层传递数据 static __always_inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

注意:这两个函数只能在驱动代码中使用

函数:copy_from_user(void *to, const void __user *from, unsigned long n)
函数作用:从用户层读取数据到内核层
函数参数
@param1 to 目的地址。数据拷贝的目的地,位于内核空间的缓冲区。
@param2 from 源地址。数据来源,位于用户空间的缓冲区,由应用层传入
@param3 n 要拷贝的字节数
返回值
0:n个字节完全拷贝
>0:未能成功拷贝的剩余字节数

函数:copy_to_user(void __user *to, const void *from, unsigned long n)
函数作用:将内核层数据写入用户空间
函数参数
@param1 to 目的地址。数据拷贝的目的地,位于用户空间的缓冲区,由应用层提供
@param2 from 源地址。数据来源,位于内核空间的缓冲区
@param2 n 要拷贝的字节数
返回值
0:n个字节完全拷贝
>0:未能成功拷贝的剩余字节数

当应用层运行:

read(fd,buf,sizeof(buf)); printf("app layer read data : %s\n",buf);

驱动层会运行:

static ssize_t misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { char kbuf[64]="嵌入式linux驱动"; printk("misc device is read:hello misc read\n"); if(copy_to_user(buf,kbuf,sizeof(kbuf))!=0) { printk("misc device copy to user error\n"); return -1; } return 0; }

read函数中的buf是应用层的缓存地址,传递给驱动层的misc_read中buf,通过copy_to_user,将kbuf中的内容拷贝到buf中在传递到应用层,最终在应用层printf输出

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

百度网盘直链解析实战手册:突破限速封锁的完整解决方案

还在为百度网盘蜗牛般的下载速度而焦虑吗&#xff1f;当你急需下载重要文件&#xff0c;却只能眼睁睁看着几十KB/s的进度条缓慢爬行&#xff0c;那种无助感确实令人沮丧。现在&#xff0c;通过百度网盘直链解析工具的巧妙应用&#xff0c;你将彻底告别这种困境&#xff0c;实现…

作者头像 李华
网站建设 2026/4/16 10:42:42

Springboot医院门诊管理系统fcdrv(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表项目功能&#xff1a;用户,医院简介,医生,科室信息,扣减金额,余额充值,用户钱包,就医指南,挂号预约,医生诊疗开题报告内容Spring Boot医院门诊管理系统开题报告一、选题依据&#xff08;一&#xff09;研究背景在医疗行业快速发展的当下&#xff0c;医院门诊作…

作者头像 李华
网站建设 2026/4/8 22:29:39

删除有序数组中的重复项(C++)

一、问题描述给你一个非严格递增排列的数组 nums&#xff0c;请你原地删除重复出现的元素&#xff0c;使每个元素只出现一次&#xff0c;返回删除后数组的新长度。元素的相对顺序应该保持一致&#xff0c;然后返回 nums 中唯一元素的个数。去重后&#xff0c;nums 的前 k 个元素…

作者头像 李华
网站建设 2026/4/16 10:44:58

Flutter Android APK 命名终极教程

一、前提说明&#xff08;Flutter 项目通用&#xff09;1.1 APK 默认生成位置与名称执行&#xff1a;flutter build apk --debugflutter build apk --release最终会在&#xff1a;build/app/outputs/flutter-apk/看到&#xff1a;app-debug.apkapp-release.apk1.2 Flutter 版本…

作者头像 李华
网站建设 2026/4/15 1:39:01

PSP金手指整合版

一共3版本&#xff1a;1、PSP实体机金手指插件金手指文件&#xff0c;同时兼容PSP PSV肾上腺。2、PPSPP安卓版整合金手指3、PPSPP电脑版整合金手指因为PSP实体机&#xff0c;1000系列内存只有32M 2000 3000 GO系列是64M所以不要用PPSPP模拟器版本&#xff0c;会卡死&#xff0c…

作者头像 李华