10.2手动推导linux中file, cdev, inode之间的关系

是时候可以手动推导一下linux里面基类父类和子类的关系了
代码放最后把
在这里插入图片描述

简单说明版

详细流程

第一步注册驱动

cdev结构体能看做是一个基类,那么链表里面都是字符设备驱动的cdev连载一起,啥串口,lcd的,通过cdev->list_head连接
那cdev结构体里有主次设备号
第一步 使用register_chrdev 在内核创建了新的cdev基类,同时把驱动的file_operation和主次设备号保存在cdev中
此时把cdev放入整体的链表中 : 链表里面都是字符设备驱动的cdev连载一起,啥串口,lcd的
我们的驱动中:
register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
struct cdev *cdev;//创造了cdev
cdev = cdev_alloc();
cdev->owner = fops->owner;
cdev->ops = fops;//cdev记录下f_op

kobject_set_name(&cdev->kobj, "%s", name); //给cdev的kobj赋值名字,也就是传入的名字
cdev_add(cdev, MKDEV(cd->major, baseminor), count);//cdev加入cdev链表cdev->dev = dev; //cdev保存主次设备号cdev->count = count;//cdev保存个设备的个数kobj_map(cdev_map, dev, count, NULL, //把这个新的dev注册进 kobj_map类型的表cdev_map中,map后续还能再升入一下exact_match, exact_lock, p); //cdev_map中就有了我们的cdevstruct probe *p;p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); //创建新的probe来保存cdevp->owner = module;p->dev = dev;p->data = data;//cdev赋值成功
kobj_map类型 起始就是包含了一堆probe 也能叫一个probe链表
一个porbe指向另一个probe,
struct kobj_map {struct probe {struct probe *next;dev_t dev;unsigned long range;struct module *owner;kobj_probe_t *get;int (*lock)(dev_t, void *);void *data;//就保存了上面的cdev指针,这样在这个map中,cedv就连在一起了} *probes[255];struct mutex *lock;
};
第二步创建用户空间能看见的文件,也就是初始化inode节点

inode 对象:描述文件系统中的某个文件,元数据(权限,类型是否为字符设备文件这种.创建时间)
也能理解为ls -l出来的信息
crw–w---- 1 root tty 4, 1 10月 3 23:09 tty1
struct inode {
umode_t i_mode;//模式,如ls-l 出来的 crwx-rx,能区分字符设备和普通文件
kuid_t i_uid;
kgid_t i_gid;
dev_t i_rdev;//如果文件时字符设备,inode里面就有设备号,//如上面的 4 1
const struct file_operations *i_fop;
}

这里图上有两种办法1 驱动初始化后,使用mknode 手动创建一个设备节点
mknode 需要带主次设备号 是为了构造一个inode节点
在这里插入图片描述
创建字符设备最后会调用到init_spectial_inode()

fs/inode.c
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev);//传入了inode节点,但是为字符设备,要继续构造inode->i_mode = mode;if (S_ISCHR(mode)) //因为是字符设备 crw--w---- 的cinode->i_fop = &def_chr_fops;//对这个inode节点的fop赋值默认的char_fop.open = chrdev_open,inode->i_rdev = rdev;

另一个是cdev_add的时候使用kobj_add这些进行增加inode节点,后面找个地方细聊kobj基类,这次没看见kobj_add等函数
这一步就是为了构造出 /dev/led这个文件

第三步应用程序打开文件,第四部关联驱动的file_operation

进程打开文件 内核空间的vfs层就会生成一个struct file 对象
struct file{
struct path f_path; //文件的路径
const struct file_operations *f_op;//f_op!!!
struct inode *f_inode; //还偷偷藏了inode
unsigned int f_flags;//标志
fmode_t f_mode;//模式
loff_t f_pos;//偏移
void *private_data;//万能指针
}
在这里插入图片描述

同时把这个file结构体放在 fd_table中,每个file结构体对应了这个fd_table索引号,成功后返回这个索引号
此时应用空间调用fd = open(“dev/led”, O_RDWR(标志),0666(模式)); 返回来的fd数值,也就是这个应用程序在vfs空间中对应的fd值
通过 fd_table保存了该进程打开的所有文件

应用程序:
fd  = open("dev/led", O_RDWR(标志),0666(模式));
调用到glibc的open() 触发系统调用 产生0x80中断 进入内核层
vfs层:open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
上面这个系统调用的宏定义刚好就是说,建了一个系统调用的函数叫sys_open同时还传来三个参
这不就对应上了嘛
do_sys_open(AT_FDCWD, filename, flags, mode);fd = get_unused_fd_flags(flags);//找一个没有用过的fd给刚打开的文件进行分配fdstruct file *f = do_filp_open(dfd, tmp, &op);//创建了一个file结构体指针do_file_open()//pathname一层一层解析文件路径,可参考上次写的文件系统分析// 最后得到要打开文件的dentry和vfsmount,保存到结构体struct path中,// 而结构体struct path保存在struct nameidata nd变量中,nd变量也是path_openat的入参set_nameidata(&nd, dfd, pathname);// 用于根据nd寻找文件节点,并打开该文件注册的open函数filp = path_openat(&nd, op, flags | LOOKUP_RCU);//初始化filefile = alloc_empty_file(op->open_flag, current_cred());//创建一个filedo_last(nd, file, op)) > 0error = lookup_open(nd, &path, file, op, got_write);//终于寻找你的open了dentry_open(const struct path *path, int flags,const struct cred *cred)vfs_open(path, f);do_dentry_open(struct file *f, struct inode *inode,int (*open)(struct inode *, struct file *))f->f_op = fops_get(inode->i_fop);//获取inode保存的fop给file的fopopen = f->f_op->open;open(inode, f);//执行open了,这里也看得出来入参为啥是inode节点和file文件了.open = chrdev_open,//这里还有点复杂,反正最后调用了inoded的fop->open,也就是char_dev.c 的open,也是第二步赋值过来的openchrdev_open(struct inode *inode, struct file *filp)kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);//从cdev链表中,找到了kobj,关系为 kobj_map->data(cdev)->kobjcdev *new =  container_of(kobj, struct cdev, kobj);//顺带就把驱动注册的cdev找出来了inode->i_cdev = p = new;//同时还把inode节点里cdev一起赋值,相当于inode也知道驱动的cdevfops = fops_get(p->ops);//拿到了驱动的fopsreplace_fops(filp, fops);//替换file的fopsret = filp->f_op->open(inode, filp);//执行驱动中的fop->open函数//进入我们的驱动里.传来的inode是这个模块的ionde,file是进程打开的这个fileint chrdevbase_open(struct inode *inode, struct file *filp)fd_install(fd, f);//关联file文件和这个fd,那我我们进程的数fd_arry中就把这个文件记录了,也把fop_改了
第五步,执行其他的write等函数
应用执行了ioctrl(fd,cmd,arg);
调用glibc的ioctrl
系统调用
0x80中断
进入内核:
VFS层 fs/ioctrl.c
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg);
ksys_ioctl(fd, cmd, arg);struct fd f = fdget(fd);//根据应用层传递的fd编号,在数组中找到file结构体do_vfs_ioctl(f.file, fd, cmd, arg);//传入file结构体,和对应的指令//从file中取出inode节点//file->dentry->inodestruct inode *inode = file_inode(filp);if (S_ISREG(inode->i_mode)) //i_noded文件类型,如果是普通文件,S_isreg   regular常规的error = file_ioctl(filp, cmd, arg);else  //那就是字符文件了error = vfs_ioctl(filp, cmd, arg);error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
//因为file的operation在open的时候被改成我们的驱动里的了
static struct file_operations chrdevbase_fops = {unlocked_ioctl= ioctl,
}//现在就到我们驱动内部
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

代码块

driver

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbase.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/#define CHRDEVBASE_MAJOR	200				/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/** @description	: 驱动入口函数 * @param 		: 无* @return 		: 0 成功;其他 失败*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}/* * 将上面两个函数指定为驱动的入口和出口函数 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SwiftFormer:基于Transformer的高效加性注意力用于实时移动视觉应用的模型

文章目录 摘要1、简介2、相关研究3、方法3.1、注意力模块概述3.2、高效的加性注意力3.3、SwiftFormer 架构4、实验4.1、实现细节4.2、基线比较4.3、图像分类4.4、目标检测和实例分割4.5、语义分割5、结论6、补充材料A、SwiftFormer的架构细节B、其他实现细节C、额外消融D、COCO…

VS2022新建项目时没有ASP.NET Web应用程序 (.NET Framework)

问题&#xff1a;如图&#xff0c;VS2022新建项目时没有“ASP.NET Web应用程序 &#xff08;.NET Framework&#xff09;”的选项解决方法&#xff1a;点击跳转至修改安装选项界面选择安装该项即可&#xff1a;

SpringMVC简介

SpringMVC概述 SpringMVC是一个基于Spring开发的MVC轻量级框架&#xff0c;Spring3.0后发布的组件&#xff0c;SpringMVC可以和Spring无缝整合&#xff0c;使用DispatcherServlet作为前端控制器&#xff0c;且内部提供了处理映射器、处理适配器、视图解析器等组件&#xff0c;…

C# 往多线程传递安全参数的方法

在C#构造一个线程时&#xff0c;要向其传递一个函数&#xff0c;这个函数可以试简单的无参函数&#xff0c;也可以是参数为Object类型的函数&#xff0c;但是由于参数类型为Object&#xff0c;因此编译器无法实行类型检查&#xff0c;看下面的例子&#xff1a; class Program{…

C++学习——C++函数的编译、成员函数的调用、this指针详解

以下内容源于C语言中文网的学习与整理&#xff0c;非原创&#xff0c;如有侵权请告知删除。 从博文的分析中可以看出&#xff0c;对象的内存中只保留了成员变量&#xff0c;除此之外没有任何其他信息&#xff0c;程序运行时不知道 stu 的类型为 Student&#xff0c;也不知道它…

求直角三角形第三点的坐标

文章目录 求直角三角形第三点的坐标1. 原理2. 数学公式3. 推导过程 求直角三角形第三点的坐标 1. 原理 已知内容有&#xff1a; P1、P2 两点的坐标&#xff1b; dis1 为 P1与P2两点之间的距离&#xff1b; dis2 为 P2与P3两点之间的距离&#xff1b; 求解&#xff1a; …

[Springboot]统一响应和异常处理配置

背景 前后端分离情况下&#xff0c;后端接口通常只需要返回JSON数据。 但有时候因为某些原因可能会导致得不到正确的结果。 比如 因为登录密码错误&#xff0c;你不能直接返回错误信息和null&#xff0c;这样前端很难处理。 又比如 因为后端接口爆出了异常&#xff0c;也不能直…

算法通关村第一关-链表青铜挑战笔记

欢迎来到 : 第一关青铜关 java如何创建链表链表怎么增删改查 我们先了解链表 单链表的概念 我们从简单的创建和增删改查开始. 链表的概念 线性表分为顺序表(数组组成)和链表(节点组成) . 链表又分: 单向 双向有哨兵节点 无哨兵节点循环 不循环 链表是一种物理存储单…

计算机毕业设计 高校实习信息发布网站的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

STM32_DMA_多通道采集ADC出现错位现象

STM32_DMA_多通道采集ADC出现错位现象 问题描述&#xff1a; adcSensorValue[0],adcSensorValue[3],adcSensorValue[6]… //存储通道1数据 adcSensorValue[1],adcSensorValue[4],adcSensorValue[7]… //存储通道2数据 adcSensorValue[2],adcSensorValue[5],adcSensorValue[8]……

Git 安装和配置教程:Windows / Mac / Linux 三平台详细图文教程,带你一次性搞定 Git 环境

Git是一款免费、开源的分布式版本控制系统&#xff0c;广泛应用于软件开发领域。随着开源和云计算的发展&#xff0c;Git已经成为了开发者必备的工具之一。本文将为大家介绍Git在Windows、Mac和Linux三个平台上的安装和配置方法&#xff0c;带你一次性搞定Git环境 Windows平台 …

Windows 应用程序监控重启

执行思路 1.定时关闭可执行程序&#xff0c;2.再通过定时监控启动可执行程序 定时启动关闭程序.bat echo off cd "D:\xxxx\" :: 可执行程序目录 Start "" /b xxxx.exe :: 可执行程序 timeout /T 600 /nobreak >nul :: 600秒 taskkill /IM xxxx.exe /…

Docker 容器中固化配置文件

在 Docker 容器中固化配置文件 在 Docker 中&#xff0c;容器是临时的&#xff0c;当容器停止或重新启动时&#xff0c;容器内的任何更改都会丢失。然而&#xff0c;有时我们希望容器内的配置文件在容器重启后仍然保持不变。为了实现这一点&#xff0c;我们可以使用 Docker 的…

mysql面试题47:MySQL中Innodb的事务实现原理

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Innodb的事务实现原理 InnoDB是MySQL中一种常用的存储引擎,它支持事务和行级锁等特性。以下是InnoDB事务实现的简要原理: 事务定义: 事务是指一…

面向C++模块的开源 IFC SDK

早在 VS2019 v16.10 版本的时候&#xff0c;我们就官宣了对 C 模块(以及几乎所有其他 C 20 特性)的全面支持&#xff0c;包括 MSVC 编译器工具集&#xff0c;静态分析&#xff0c;智能感知和调试器等&#xff0c;而实现模块需要将 C 代码实现为一种内部的临时表示形式。 今天&…

Jetpack:009-kotlin中的lambda、匿名函数和闭包

文章目录 1. 概念介绍2. 使用方法2.1 函数类型的变量2.2 高阶函数 3. 内容总结4.经验分享 我们在上一章回中介绍了Jetpack中Icon和Imamg相关的内容&#xff0c;本章回中主要介绍Kotlin中的 lambda、匿名函数和闭包。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&…

C/C++基础讲解(一百三十二)之经典篇(贪食蛇)

C/C++基础讲解(一百三十二)之经典篇(贪食蛇) 程序之美 前言 很多时候,特别是刚步入大学的学子们,对于刚刚开展的计算机课程基本上是一团迷雾,想要弄明白其中的奥秘,真的要花费一些功夫,我和大家一样都是这么啃过来的,从不知到知知,懵懂到入门,每一步都走的很艰辛,课…

java 每种设计模式的作用,与应用场景

文章目录 前言java 每种设计模式的作用&#xff0c;与应用场景 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#xff0…

机器学习之Sigmoid函数

文章目录 Sigmoid函数是一种常用的数学函数&#xff0c;通常用于将实数映射到一个特定的区间。它的形状类似于"S"形状曲线&#xff0c;因此得名。Sigmoid函数在机器学习、神经网络和统计学中经常被使用&#xff0c;主要用于二元分类和处理概率值。 Sigmoid函数的一般…

冲刺十五届蓝桥杯P0006平面切分

文章目录 题目思路代码总结 题目 平面切分 思路 这道题是一个思维题把&#xff0c;之前没有接触过平面几何的知识&#xff0c;做起来感觉还是比较难的&#xff0c;用到的set集合和自己创建一个类 首先我们知道&#xff0c;一根直线A是可以将平面切分成两块的&#xff0c;如…