当前位置: 首页 > news >正文

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

声明:内容源于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输出

http://www.cnnetsun.cn/news/42839.html

相关文章:

  • 基于SpringBoot的人事管理系统设计与实现
  • 【论文阅读】Multi-modal Spatial Clustering for Spatial Transcriptomics Utilizing High-resolution Histology
  • Day36官方文档的阅读
  • Windows右键菜单终极优化指南:让你的右键菜单重获新生
  • ZTools v1.1.2:桌面应用启动器与搜索工具
  • Flutter Android APK 重命名 签名验证操作
  • MarchingCubes 网格数据体素化并提取等值面
  • 基于SpringBoot的餐厅推荐系统 计算机毕业设计选题 计算机毕设项目 前后端分离 【源码-文档报告-代码讲解】
  • 禁用MinIO后的7种企业级替代方案评测
  • document.querySelector在电商网站中的5个实战应用
  • 企业级应用:OpenJDK1.8在生产环境中的部署实践
  • Homebrew实战:从安装到开发环境搭建全流程
  • 企业级Git仓库SSH连接安全最佳实践
  • Day12 贝叶斯优化可视化和随机森林的解读
  • 数据湖不是湖,是江湖:Delta Lake / Iceberg / Hudi 到底该选谁?
  • 告别开题报告模板拼凑!虎贲等考 AI 智能生成,让选题逻辑从模糊想法变身可执行研究计划
  • 【LeetCode刷题】跳跃游戏
  • 鸿蒙PC UI控件库 - PasswordInput 密码输入框详解
  • day37简单的神经网络@浙大疏锦行
  • 【水果识别】基于机器视觉苹果和香蕉的成熟度和大小检测附Matlab代码
  • JAVA的平凡之路——此峰乃是最高峰JVM-附加小菜-04
  • 【电力系统】电力系统优化与控制热液调度附Matlab代码和报告
  • 基于6种最新算法(小龙虾优化算法COA、MSA、RTH、NOA、BFO、SWO)求解机器人路径规划研究附Matlab代码
  • Golang实战:构建综合多头(逾期+反欺诈)风险查询的高性能客户端
  • 【TSP问题】基于蜣螂算法DBO和改进的蜣螂算法FADBO求解旅行商TSP问题(可根据自己的经纬度设置自己想要到达的地区)附Matlab代码
  • 【太阳能学报EI复现】基于粒子群优化算法的风-水电联合优化运行分析附Matlab代码
  • 数据结构:二叉排序树,平衡二叉树,红黑树的介绍
  • 软件复用的分类与实现
  • google服务
  • 进程PCB