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