文件系统 --- 文件结构体,文件fd以及文件描述符表
序言
在编程的世界里,文件操作是不可或缺的一部分。无论是数据的持久化存储、日志记录,还是简单的文本编辑,文件都扮演着至关重要的角色。然而,当我们通过编程语言如 C、Java
等轻松地进行文件读写时,背后隐藏的复杂机制和底层细节往往被我们所忽略。
本文将带着大家理解文件操作在底层的样子,原来文件不仅仅是被简单地读入或写入内存中。
1. 系统调用接口 🔁 用户编程接口
操作系统(简称 OS
)是计算机系统的核心软件,它管理计算机的硬件和软件资源,为上层应用程序提供一个稳定、统一的运行环境。
如果将你的电脑比作财产的话,那么操作系统就是你的管家,而且还是一个强势的管家。
1.1 系统调用接口
1. 系统调用接口的概念
当你想要访问你的计算机的软硬件资源时,必须符合操作系统的规定,而系统调用就是操作系统为用户空间程序提供的一种服务接口。
2. 为什么要存在系统调用接口
我的电脑我想干嘛就干嘛呀! 😤 为什么操作系统管的这么严呀,还要按照他的要求来使用,到底谁是主人呀!
操作系统的存在就是避免用户不合法的行为(比如:错误的修改数据,不正确的使用)导致争整个系统的崩溃!所以正是想要用户得到一个良好的运行环境,才约束用户的行为。
3. 系统调用接口的作用
系统调用是计算机操作系统中非常核心的概念,它的作用包含但不限于如下内容:
- 硬件保护与隔离:系统调用作为用户程序和硬件设备之间的中介,确保用户程序不能直接访问硬件,从而保护硬件资源免受非法访问和破坏。
- 资源管理与分配:操作系统通过系统调用来管理CPU时间、内存、文件和设备等系统资源,确保资源的公平分配和有效利用。
- 实现操作系统功能:系统调用是操作系统实现各种功能(如进程管理、内存管理、文件系统、网络通信等)的基础。
1.2 用户编程接口
1. 用户编程接口的概念
用户操作接口接口(API
)是操作系统或库函数提供给程序员的接口,在 C
语言环境中,API
通常以库函数的形式出现,这些函数封装了系统调用的细节,为程序员提供了更为简便、易用的编程接口。
2. 为什么要存在用户编程接口
API
提供了一种对系统调用抽象和封装。通过将复杂的系统细节隐藏起来,API
只暴露用户需要的功能和接口,使得用户(包括程序员)可以更容易地理解和使用这些功能。这种抽象和封装降低了直接与系统底层交互的难度和复杂性。
3. 编程接口的跨平台性
除了简化用户对系统调用的使用,编程接口还具有一个非常重要的性质,那就是 跨平台性
。
系统调用接口对应不同的系统如 Linux, Windows,Mac等
,实现的细节肯定是不一致的。所以你在 Linux
系统下使用了系统调用的程序,在 Mac
下就不一定能运行。
但是 API
通过特定的方法(如条件编译配置)可以实现在不同的平台下都能正常运行。
2. 利用系统调用接口进行文件操作🐧
在使用 C
语言进行文件操作时,我们利用 fopen
函数以特定的方式打开一个文件,利用 fread
读取文件或者是利用 fwrite
写文件,最后利用 fclose
函数关闭文件。
这都是将系统接口进行封装过后的 API
,今天我们尝试直接使用系统接口,这更加接近底层,更好的帮助我们引出后续的内容。😀
2.1 open
函数
系统提供的 open
函数有两个版本:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
下面多出的 mode
参数用于指定新创建文件的权限
,所以我们使用下面那个的版本。我们看看他整体的参数:
pathname
:指向要打开的文件的路径名flags
: 用于指定打开文件的方式以及其他选项(如是否创建文件)。这些选项可以通过位或操作组合起来。mode
: 指定新创建文件的权限 。- 返回值为一个
int
,叫做文件描述符(fd
)
我们在这里详细介绍 flags
参数 以及 mode
参数:
flags
参数 — 位图
某些函数需要我们传递标志位,该函数根据标志位执行特定的功能,就比如:
1 #include <iostream>2 using namespace std;3 4 5 int Func(int num, int flag1, int flag2, int flag3){6 if(flag1 == 1){7 num = num + 1;8 }9 if(flag2 == 1){10 num = num - 1;11 }12 if(flag3 == 1){13 num = num * 2;14 }15 16 return num;17 }18 19 20 int main(){21 int num = 10;22 int flag1 = 1, flag2 = 0, flag3 = 1;23 24 num = Func(num, flag1, flag2, flag3);25 cout << "num = " << num << endl; 26 27 return 0;28 }
我们根据 3 个标志位质的不同执行不同的逻辑,但现在只是 3 个标志位,如果是 10 个,20 个,那我们也设置相同数量的形参吗,这太麻烦,也太浪费空间了。
那怎么办呢?位图
。一个 int
变量在该环境下是 32 位,是否可以表示为 32 种状态呢?为了简单,就拿 8 位举例:
#define FLAG1 1 // 0000 0001
#define FLAG2 2 // 0000 0010
#define FLAG3 4 // 0000 0100
不同的状态对应的位置我就取 1 ,如果这 3 个状态我都想要呢?那就利用 |
或对他们进行运算:
FLAG1 | FLAG2 | FLAG3 // 0000 0111
可以看到,这就代表这三个状态我都表示,并且不同的状态之间是相互不干扰的。
根据这个方法,我们更新上述代码:
1 #include <iostream>2 using namespace std;3 4 #define FLAG1 1 // 0000 00015 #define FLAG2 2 // 0000 00106 #define FLAG3 4 // 0000 01007 8 // 利用 & 操作,判断是否选取该状态9 int Func(int num, int flags){10 if(flags & FLAG1){11 num = num + 1;12 }13 if(flags & FLAG2){14 num = num - 1;15 }16 if(flags & FLAG3){ 17 num = num * 2;18 }19 20 return num;21 }22 23 24 int main(){25 int num = 10;26 27 num = Func(num, FLAG1 | FLAG3);28 cout << "num = " << num << endl;29 30 return 0;31 }
flags
参数就采用了位图的方式,我将列举我们常用的选项:
O_RDONLY
:以只读方式打开文件。O_WRONLY
:以只写方式打开文件。O_RDWR
:以读写方式打开文件。O_CREAT
:如果文件不存在,则创建它。需要 mode 参数来指定新文件的权限。O_TRUNC
:如果文件已存在且为只写或读写模式打开,则清空文件内容。O_APPEND
:以追加方式打开文件,数据会被写入到文件尾。
mode
参数 — 文件权限
关于文件权限的知识在这里就不展开细说了,这样的话篇幅太长了,也不易消化😖。我专门有一篇文章 👉点击查看 详细地介绍了文件权限相关的知识,大家有兴趣可以看一下哈。
前置知识结束了,现在我们可以进入正题了,我们以写的方式创建一个新的文件:
16 // 写方式打开文件,并且文件不存在就创建一个,文件的权限是 rw-rw-r--17 int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);18 if(fd == -1) perror("open");
注意:这里就可以看出系统调用接口和编程接口的区别,在 C语言中的 fopen 函数,当文件不存在时会为我们自动创建一个文件,而系统调用接口就需要我们指定。
2.2 write
函数
write
函数的参数就稍显简单一些:
ssize_t write(int fd, const void *buf, size_t count);
fd
: 要写入数据的文件描述符。buf
:指向要写入数据的缓冲区的指针。count
:要写入文件的字节数。- 返回值:写入错位时返回 -1
我们就直接写入内容吧:
20 // 写入指定信息21 const char* msg = "Its a test!!!\n"; 22 ssize_t ret = write(fd, msg, strlen(msg));23 if(ret == -1) perror("write");
2.3 运行结果
系统按照我们的需求创建了一个指定权限的文件:
并且为我们写入了相应的内容:
3. 提出几个为什么
我们需要根据现有的现象来发现问题,不断地问自己为什么,这才能提升自己。
3.1 fd
文件描述符是什么?
我们通过接受 open
函数返回的 fd
文件描述符,并将 fd
传入到 write
函数的参数中,就可以向指定文件写入内容,这是为什么呢?怎么就可以根据一个 fd
的整数向指定文件写入内容呢?
3.2 open
函数的返回值?
我们先看一段代码:
21 int main(){22 int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 0666);23 printf("fd1: %d.\n", fd1);24 25 int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 0666);26 printf("fd2: %d.\n", fd2);27 28 int fd3 = open("log3.txt", O_WRONLY | O_CREAT, 0666);29 printf("fd3: %d.\n", fd3);30 31 int fd4 = open("log4.txt", O_WRONLY | O_CREAT, 0666);32 printf("fd4: %d.\n", fd4); 33 return 0;34 }
这段代码的输出结果是:
fd1: 3.
fd2: 4.
fd3: 5.
fd4: 6.
我们知道 open
函数的返回值是 fd
,但为什么他们的值是连续的?并且唯独缺少 0, 1, 2
,这是巧合吗?如果一次运行时这样,有理由怀疑是巧合。但是,我们多次运行还是这样,那就肯定不是了。
4. struct file
结构体
当一个文件没有被任何进程调度时,该文件就静静的躺在你的磁盘中。但是当文件被调度时,就会被送往内存中,所以内存中的文件就只是文件的内容吗?肯定不是!同一时间,内存中肯定存在着大量需要操作的文件,操作系统需要高效的管理文件那怎么办呢?
使用 结构体 + 双链表
的结构,类似于管理进程的结构 ,在这篇文章,有较详细的说明👉点击查看。
4.1 struct file
中的内容
该结构体中包含了文件的基本信息,包括:
f_op
:指向一个file_operations
结构体的指针,该结构体包含了指向文件操作函数的指针,如read、write、open、release
等。f_count
:表示有多少进程或文件描述符引用了这个文件。当f_count
降到 0 时,文件将被关闭。f_flags
:包含文件的标志,如O_RDONLY、O_WRONLY、O_RDWR
等,表示文件的打开模式。f_mode
:表示文件的访问模式(只读、只写、读写)。f_pos
:当前的文件偏移量,表示下一次读写操作将从哪个位置开始。f_owner
:包含文件的所有者信息,用于权限检查。- …
4.2 相关的内核级别的缓存区
还有一个空间叫做 缓冲区
尽管不直接受到该结构体的管理,但是和结构体紧密相关:
可以看出该 缓存
就是一块在内存中的空间,那有啥用呢?用处可大了!
当用户请求读取文件时,内核会首先检查缓冲区中是否已经缓存了所需的数据
。如果是,则直接从缓冲区中读取数据,避免了磁盘访问的延迟
。如果不是,内核会从磁盘读取数据到缓冲区中,并更新缓冲区的内容。
当用户更新文件内容时,内核并不会直接将该内容立马更新到磁盘上,而是先放在缓冲区等到累积到一定的量,在一次写入磁盘,减少 I/O
操作。
所以对于内存这种告诉设备来说,内核级缓冲区通过减少对磁盘等低速设备的直接访问次数,显著提高了文件I/O操作的性能。
4.3 理解 Linux
一切皆文件
相信大家肯定听过一句话,Linux 中一切皆文件。
但是对于我们的键盘,鼠标,显示屏来说,他们确实是实实在在的硬件呀!我该怎么把它看作文件呢?
属性 + 方法
对于任何的硬件设备都离不开两个概念 属性 + 方法
,但是本章节我们主要关注该结构体的 方法
。Linux
将一切的对象都视作一个文件,那么就拿键盘举例吧,请问键盘这个文件有什么方法呢?无非就是 读方法
或者是 写方法
!键盘你能读我理解,写方法
又是什么呢?键盘确实没有写方法,该方法置空不就好啦!
所以我们可以这么看待硬件:
对于操作系统来说,对于普通文件的管理使用的是一个 struct file
,里面包含文件的基本属性以及读写方法等,对于硬件我也可以一同看待呀!也还不是硬件的基本属性以及读写方法:
所以说当操作系统调用一个文件的时候,才不关心该文件本质是硬件还是啥,我该使用读方法就使用读方法,改写就使用写方法,反正所有文件的操作函数接口是一致的。
这就是使用 struct
封装的好处,尽管底层千差万别,但是上层的调用都是一致的!
5. fd
含义以及文件描述符表
上面说到,操作系统会将系统调度的所有文件的 struct
利用双链表的形式管理起来。我们的一个进程可能同一时间调度多个文件,又该如何管理呢?
5.1 文件描述符表
进程会将自己所控制的所有文件的 struct
结构体的指针放在一个文件描述表中:(注:该表中包含其他信息,但在本文中不关注。
)
该表包含一个结构体指针数组,每一个存储的元素就是你所控制文件结构体的指针,该表可以在进程的 PCB
(在 Linux
下叫做 struct_task
)中找到。
5.2 fd
的含义
到这里就不难理解 fd
的含义了,他为你想要操作文件的结构体指针在结构体指针数组里面的下标。
依靠他,就能找到想要操作文件的位置,环环相扣!
5.3 fd
的分配方式
那进程新加入文件描述符表的文件,怎么分配 fd
呢?会从上到下遍历该表,哪个位置是空闲的就放到哪个位置。
6. 解答疑惑
有了一定的知识基础之后,我们尝试解决在 3
中提出的问题。
6.1 fd
文件描述符是什么?
进程新使用的文件会将该文件的结构体指针放入进程描述符表的结构体指针数组中,该 fd
就是指针放入结构体指针数组的下标位置。有点绕口哈😇
6.2 open
函数的返回值?
为什么 open
函数的返回值缺少 0, 1, 2
呢?因为这几个文件描述符已经被标准输入(stdin
)、标准输出(stdout
)和标准错误输出(stderr
)在进程启动时占用。
0
通常被分配给标准输入键盘
(stdin
)1
通常被分配给标准输出显示器
(stdout
)2
通常被分配给标准错误输出显示器
(stderr
)
怎么证明呢?
证明 1:
14 int main(){15 16 const char* msg = "Its a test!!!\n";17 ssize_t ret = write(1, msg, strlen(msg)); 18 19 return 0;20 }
我们直接向 1
对应的文件写入信息,运行,成功在显示器打印:
Its a test!!!
证明 2:
13 int main(){14 15 umask(0);16 17 close(1);// 关闭显示器文件18 19 // 写方式打开文件,并且文件不存在就创建一个,文件的权限是 rw-rw-r--20 int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);21 if(fd == -1) perror("open");22 printf("fd = %d.\n", fd); 23 24 25 return 0; 26 }
在这里我们关闭 1 对应的文件,之后我们打开一个文件,并打印。
最后我们发现,并没有输出任何内容到屏幕上,反而内容在 log.txt
文件上?
大家思考一下这是为什么?
7. 总结
在这篇文章中介绍了部分文件系统的内容,希望大家有所收获!
相关文章:

文件系统 --- 文件结构体,文件fd以及文件描述符表
序言 在编程的世界里,文件操作是不可或缺的一部分。无论是数据的持久化存储、日志记录,还是简单的文本编辑,文件都扮演着至关重要的角色。然而,当我们通过编程语言如 C、Java 等轻松地进行文件读写时,背后隐藏的复杂机…...
【第三节】python中的函数
目录 一、函数的定义 二、函数的调用 三、函数的参数 3.1 可变与不可变对象 3.2 函数参数传递 3.3 参数类型 四、匿名函数 五、函数的return语句 六、作用域 七、python的模块化 八、 main 函数 一、函数的定义 函数是经过精心组织、可重复使用的代码片段࿰…...

“论云原生架构及其应用”写作框架软考高级论文系统架构设计师论文
论文真题 近年来,随着数字化转型不断深入,科技创新与业务发展不断融合,各行各业正在从大工业时代的固化范式进化成面向创新型组织与灵活型业务的崭新模式。在这一背景下,以容器和微服务架构为代表的云原生技术作为云计算服务的新…...

深度剖析Google黑科技RB-Modulation:告别繁琐训练,拥抱无限创意生成和风格迁移!
给定单个参考图像,RB-Modulation提供了一个无需训练的即插即用解决方案,用于(a)风格化和(b)具有各种提示的内容样式组合,同时保持样本多样性和提示对齐。例如,给定参考样式图像(例如“熔化的黄金3d渲染样式”)和内容图像(例如(a)“狗”),RB-Modulation方法可以坚持所需的提…...
react native 和 flutter 区别
React Native 和 Flutter 都是用于构建跨平台移动应用的优秀框架,各有其优点和适用场景。 1. React Native 1.1 优点 | 基于 JavaScript 生态:对于熟悉 JavaScript 和 React 的开发者来说,学习成本相对较低,能够利用大量现有的 …...

ITSS服务经理/ITSS服务工程师,招投标需要准备吗?
信息技术服务标准(ITSS)是中国首套完整的信息技术服务标准体系,全面规定了IT服务产品及其组成要素的标准化实施,旨在提供可信赖的IT服务。 在国际竞争日益激烈的背景下,推动国内标准的国际化已成为广泛共识࿰…...
eleven接口、多态
能够写出接口的定义格式 public interface 接口名 { public static final 数据类型 名称 数据值; //抽象方法: 必须使用实现类对象调用 void method(); //默认方法: 必须使用实现类对象调用 public default void show() {...} …...

重磅惊喜!OpenAI突然上线GPT-4o超长输出模型!「Her」高级语音模式已开放测试
在最近的大模型战争中,OpenAI似乎很难维持霸主地位。虽然没有具体的数据统计,但Claude3.5出现后,只是看网友们的评论,就能感觉到OpenAI订阅用户的流失: Claude3.5比GPT-4o好用,为什么我们不去订阅Claude呢&…...
解决问题 CUDA error: CUBLAS_STATUS_INVALID_VALUE when calling `cublasGemmEx
遇到问题如下: Traceback (most recent call last):File "run_warmup_a.py", line 431, in <module>main()File "run_warmup_a.py", line 142, in mainreturn main_worker(args, logger)File "run_warmup_a.py", line 207, in…...
【Python实战因果推断】67_图因果模型2
目录 Are Consultants Worth It? Crash Course in Graphical Models Chains Are Consultants Worth It? 为了展示有向无环图(DAG)的力量,让我们考虑一个更有趣但处理因素并未随机化的情况。假设你是某公司的经理,正在考虑是否聘请顶级咨询顾问。你…...

RK3588+MIPI+GMSL+AI摄像机:自动车载4/8通道GMSL采集/边缘计算盒解决方案
RK3588作为目前市面能买到的最强国产SOC,有强大的硬件配置。在智能汽车飞速发展,对图像数据矿场要求越来越多的环境下,如何高效采集数据,或者运行AI应用,成为刚需。 推出的4/8通道GMSL采集/边缘计算盒产品满足这些需求…...

智云-一个抓取web流量的轻量级蜜罐
智云-一个抓取web流量的轻量级蜜罐 安装环境要求 apache php7.4 mysql8 github地址 https://github.com/xiaoxiaoranxxx/POT-ZHIYUN 系统演示...
面向对象程序设计之sort排序
目录 java 升序 降序 c# 升序 倒序 小结 敲过排序算法的都会的,Sort排序与compareTo的改写。 java 升序 一般自带的sort方法就是升序的。 Arrays.sort(arr);//传入要排序的数组,默认升序 Collections.sort(list);//传入要排序的集合类&am…...

ARM学习(29)NXP 双coreMCU MCXN94学习
笔者来介绍一下NXP 双core板子 ,新系列的mcxn94 1、MCX 新系列介绍 恩智浦 MCU 系列产品包括 Kinetis 、LPC 系列,以及 i.MX RT 系列,现在又推出新系列产品 MCX 产品,包括四个系列,目前已经发布产品的是 MCX N 系列。…...

视频剪辑免费素材哪里能找到?
在创作视频时,素材的选择至关重要。为了让您的项目更具吸引力和专业性,我整理了8个剪辑必备素材网站,它们提供了丰富多样的资源,从高清视频到优质音乐,应有尽有。让我们一起探索这些资源丰富、质量上乘的平台ÿ…...
多线程为什么是你必需要掌握的知识
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、多线程是什么?二、多线程学习的必要性1.提升代码性能2.更优秀的软件设计和架构3.更好的工作机会 总结 前言 相信每一位开发者,都应…...

转转上门履约服务拆分库表迁移实践
文章目录 1 背景2 数据迁移方案2.1 方案一:双写新旧库2.2 方案二:灰度开关切换新旧库 3 迁移细节3.1 业务代码改造3.2 数据同步3.3 数据一致性校验 4 总结5 参考资料 1 背景 随着业务不断发展,一个服务中部分功能模块适合沉淀下来作为通用的…...

upload-labs 1-19关 攻略 附带项目下载地址 小白也能看会
本文章提供的工具、教程、学习路线等均为原创或互联网收集,旨在提高网络安全技术水平为目的,只做技术研究,谨遵守国家相关法律法规,请勿用于违法用途,如有侵权请联系小编处理。 环境准备: 1.靶场搭建 下…...
如何设置SQL Server的端口:详细步骤指南
如何设置SQL Server的端口:详细步骤指南 在SQL Server中,配置端口是确保数据库服务能够正确通信的重要步骤。无论是为了提高安全性还是满足特定的网络配置需求,正确设置SQL Server的端口都是必要的。本文将详细介绍如何设置SQL Server的端口…...

昇思25天学习打卡营第16天|Diffusion扩散模型,DCGAN生成漫画头像
Diffusion扩散模型 关于扩散模型(Diffusion Models)有很多种理解,本文的介绍是基于denoising diffusion probabilistic model (DDPM),DDPM已经在(无)条件图像/音频/视频生成领域取得…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...