访问设备文件与调用驱动函数是怎样关联的?

📅 2026/6/16 2:30:52
访问设备文件与调用驱动函数是怎样关联的?
author: hjjdebugdate: 2026年 06月 15日 星期一 10:28:30 CSTdescrip: 访问设备文件与调用驱动函数是怎样关联的?文章目录1. 应用层2. 内核层(提供虚拟文件系统, 实现函数转发)3. 驱动层(实现回调函数).4. 执行结果:我对于打开一个设备文件对其read,write 就能调用到驱动中的read,write函数这一点一直理解不太深刻,今天就给一个实例,演示内核通过怎样的机制实现的函数回调.是的,你注册了什么函数,我就回调什么函数. 但如何做到的呢?真正的内核实现太过复杂, 我们这里是一个纯应用层模拟,但这不影响对转发机制的理解.架构分三层. 应用层,内核层(实现函数转发),驱动层1. 应用层main 函数.应用层先向内核注册文件名和FileOps对象然后调用内核层提供的函数 sys_open, sys_read, sys_write, sys_close2. 内核层(提供虚拟文件系统, 实现函数转发)关键: 维护着一张文件名到设备操作集的映射表.对外提供系统接口,根据文件名称,找到操作集指针,对内提供函数转发,根据注册的对象, 调用驱动层函数.内核层是红娘.3. 驱动层(实现回调函数).每个驱动都需要提供一张函数表地址,叫FileOps 对象,这个FileOps 对象和驱动的名字需要注册给系统(这叫初始化, 例子是在应用层注册的)实现了myopen,myread,mywrite,myclose.函数是sys_open,sys_read,sys_write,sys_close的下半部实现.这里都是回调函数,等着应用来调用.源代码:$cat simulate_drv.c#includestdio.h#includestring.h#includestdlib.h// 1. 模拟驱动层文件操作函数集(对应内核 file_operations) // 模拟驱动回调函数原型typedefint(*dev_open_t)(constchar*path);typedefssize_t(*dev_read_t)(char*buf,size_t len);typedefssize_t(*dev_write_t)(constchar*buf,size_t len);typedefint(*dev_release_t)(void);// 模拟内核 struct file_operationstypedefstruct{dev_open_t open;dev_read_t read;dev_write_t write;dev_release_t release;}FileOps;// -------- 模拟一个自定义设备驱动 /dev/mydev 回调实现 --------intmydev_open(constchar*path){printf([驱动 /dev/mydev] 设备被打开: %s\n,path);return0;}ssize_tmydev_read(char*buf,size_t len){constchar*strdata from driver;size_t copy_lenstrlen(str);if(copy_lenlen)copy_lenlen;memcpy(buf,str,copy_len);printf([驱动 /dev/mydev] 执行 read\n);returncopy_len;}ssize_tmydev_write(constchar*buf,size_t len){printf([驱动 /dev/mydev] 执行 write, 收到数据: %.*s\n,(int)len,buf);returnlen;}intmydev_release(void){printf([驱动 /dev/mydev] 设备被关闭\n);return0;}// 该设备绑定的操作函数集,每个驱动都需要提供一张函数表地址,叫FileOps 对象FileOps mydev_fops{.openmydev_open,.readmydev_read,.writemydev_write,.releasemydev_release};// 2. 模拟 VFS 层内核转发核心 // 设备节点映射表路径名 - 对应的驱动操作集typedefstruct{charpath[64];FileOps*ops;}DevMap;#defineMAX_DEV8DevMap sys_map[MAX_DEV];//核心架构intdev_cnt0;// VFS注册设备节点对应内核 cdev_add / device_createvoidsys_register_dev(constchar*path,FileOps*ops){if(dev_cntMAX_DEV)return;strncpy(sys_map[dev_cnt].path,path,63);sys_map[dev_cnt].opsops;dev_cnt;printf([VFS] 注册设备节点: %s\n,path);}// VFS根据路径查找对应驱动操作集FileOps*sys_lookup(constchar*path){for(inti0;idev_cnt;i){if(strcmp(sys_map[i].path,path)0){returnsys_map[i].ops;}}returnNULL;}// -------- VFS 对外提供接口模拟系统调用) 对内提供转发调用 --------intsys_open(constchar*path){FileOps*opssys_lookup(path);if(!ops){printf([VFS] 找不到设备: %s\n,path);return-1;}printf([VFS] 转发 open 请求到驱动\n);returnops-open(path);}ssize_tsys_read(constchar*path,char*buf,size_t len){FileOps*opssys_lookup(path);if(!ops)return-1;printf([VFS] 转发 read 请求到驱动\n);returnops-read(buf,len);}ssize_tsys_write(constchar*path,constchar*buf,size_t len){FileOps*opssys_lookup(path);if(!ops)return-1;printf([VFS] 转发 write 请求到驱动\n);returnops-write(buf,len);}intsys_close(constchar*path){FileOps*opssys_lookup(path);if(!ops)return-1;printf([VFS] 转发 close 请求到驱动\n);returnops-release();}// 3. 应用层用户进程 intmain(void){charrbuf[128]{0};constchar*wbufhello driver;// 1. 驱动向 VFS 注册设备节点insmod 时或者probe时,驱动向内核注册时调用sys_register_dev(/dev/mydev,mydev_fops);printf(------------------------\n);// 2. 应用层调用文件接口模拟 open/read/write/close 系统调用sys_open(/dev/mydev);sys_read(/dev/mydev,rbuf,sizeof(rbuf));printf([应用] 读到内容: %s\n,rbuf);sys_write(/dev/mydev,wbuf,strlen(wbuf));sys_close(/dev/mydev);return0;}4. 执行结果:$ ./simulate_drv[VFS] 注册设备节点: /dev/mydev------------------------[VFS] 转发 open 请求到驱动[驱动 /dev/mydev] 设备被打开: /dev/mydev[VFS] 转发 read 请求到驱动[驱动 /dev/mydev] 执行 read[应用] 读到内容: data from driver[VFS] 转发 write 请求到驱动[驱动 /dev/mydev] 执行 write, 收到数据: hello driver[VFS] 转发 close 请求到驱动[驱动 /dev/mydev] 设备被关闭阅读,调试完这个程序,我们就深刻的知道,内核层其实没有干什么,它只是根据驱动名称找到操作对象.根据调用入口转去调用注册的函数.而函数要实现什么功能,那是驱动中函数的任务.