Linux - 基础IO(Linux 当中的文件,文件系统调用接口,文件描述符)- 上篇

news2024/12/26 14:05:04

前言

首先,关于文件我们最先要理解的是,文件不仅仅存储的是数据,一个文件包括 内容 + 数据。内容好理解,就是我们先要这文件存储哪一些数据,这些数据就是文件的内容。

但是,在计算机当中,有两种文件,一种是 正在打开的文件 ,另一种是 没有被打开文件。

文件没有被打开,那么就是存储在外设当中,最常见的存储数据的外设比如 磁盘,u盘,硬盘等等,那么我们知道,外设当中存储速度是比不上内存的,但是,外设有一个好处,就是存储容量大。

那么也就意味着,在外设当中存储了很多个文件, 我们如何 对这些文件进行 增删查改 ,其实靠的就是文件的属性,文件的属性有很多,比如文件的最近一次的修改时间,文件的创建的时间,文件的大小,文件的存储位置等等···  这些,在windows 当中右键某一个文件,我们也可以查看文件的属性:

而,对于未被打开的文件,文件的属性就是为了方便我们来对文件 进行 增删查改 。也就是说,如何在“浩瀚”的外设当中找到文件,进行 增删查改,文件的属性不能少。

就好比是在快递站当中找快递,这些快递不是随机的,混乱的堆放在某处,然后让我们自己的一个一个找的;而是,把这个快递 编写一个快递号,比如是 12 - 3 - 4444,那么我就在 12 号货架的 第 3 层的位置,来找 4444 的快递就行了,这样不仅方便了 快递站的人来存放,也方便了 找快递的人来拿。 不会显得很乱。


文件要被打开,也就是要被修改,那么肯定是要加载到内存当中 ,而且,一个进程可能不只是打开一个文件,一般大多数情况下, 一个进程打开的文件数 是 1 : n 的关系。因为,存储进程的代码 数据,本身就是一个可执行文件当中存储的,同时,进程很可能还会打开其他的多个文件。

所以,不仅仅是外设当中有很多的文件,在内存当中,操作系统也要维护很多的 文件。

所以,这就要谈一个六字真言了 -- 先描述在组织

其实在操作系统 在 内存当中管理这么多个进程,和 文件,本质上就是管理这些进程 和 文件 的属性。操作系统 像 Linux 使用 C 语言实现的,所以,其实本质上就是用 一个个 struct 结构体来表述 一个进程 / 文件 的属性(也就是属性的集合)(这叫做描述),然后操作系统只要管理好了这些个 strutc 结构体就可以管理好 所以的进程 / 文件(这叫做组织)。而管理(组织)这些 结构体对象,本质上就是用 某种 数据结构来维护某种关系。

比如 进程 等待 硬件资源,但是这个硬件当前可能是多个进程在等待资源,那么就有先后顺序,就要等待。所以,在Linux 当中,基本上每一个 硬件在操作系统当中都有一个等待队列,用于进程之间在这个硬件上的排队。

所以,我们才说,在Linux 当中一切都是文件,为什么,就因为上述我们所说的 -- 先描述在组织


所以文件也是一样,操作系统在内存当中维护这些个资源,本质上就是维护 操作系统为 这个加载到内存当中文件 所 创建的 文件结构体对象

你在很多的编程语言当中看到的 ,为什么 像类似 fopen()这样的函数,返回的往往是一个 文件对象的指针,其实就离不开 操作系统当中为了更好管理文件,为这个文件所单独创建的 文件结构体对象。两者实际上是大同小异的。

文件

在C 语言当中的 各个文件接口(函数)

首先当然是 fopen()函数打开一个文件:
 

可知道 fopen()函数返回的是一个 FILE*(文件指针 / 文件句柄) 。

  • path : 打开文件的路径;(相对路径,绝对路径,或者不带路径)
  • mode:打开文件的方式;

后续我们对这个文件的操作都需要 fopen()函数的返回值,也就是这个 FILE* 文件指针。

  • 如果你以 "w" 写的方式打开一个文件的话,如果文件在你传入的路径之下不存在,那么就会自动创建一个文件;
  • 而且,"w" 方式写入数据到文件的话,如果文件当中有数据,那么就先把文件的长度清零,然后在文件的最开始处写入。(这种方式 和 Linux 命令行当中的 ">" 输出重定向 向文件当中的写入方式是一样的。如 echo "hello Linux!" > text.txt  这个文件当中输入 "hello Linux!"字符串,就是以 'w' 的方式来写入的。所以像 "> text.txt"  这样的方式就可以 清空 text.txt 这个文件当中的数据
  • "a" 是在文件末尾处以 追加的方式来写。 同样 , 如果文件在你传入的路径之下不存在,那么就会自动创建一个文件;(这种方式 和 Linux 命令行当中的 ">>" 输出重定向 向文件当中的写入方式是一样的。">>" 就是追加的方式来在文件当中输入数据的
  • 对于 path 路径,如果你只是直接写上文件名,没有带上路径,那么就会默认是在当前 路径下查找这个文件。(此处的 "当前路径"指的是 调用 fopen()函数的 进程的当前路径)。

在 /proc/进程PID 当中就有这个进程对应的 当前工作路径:

所以,如果把这个进程的  cwd,也就是这个进程的当前工作路径给改掉了,那么像 fopen("text.txt",'w'); 这样的操作,就会在新修改的 工作路径的下创建这个新文件。


c 程序在运行之时,会自动打开 三个 标准输入输出流

 像 fprintf()这些函数可以像写入数据到文件,或者是读取数据到程序当中的一样,来像上述的 三种 标准输入输出流当中输入和输出数据:


上述只是简单的把 C 语言当中的一下文件操作浅谈一下,因为不是本篇博客的主要内容,所以不在过多阐述。

被打开的文件(被加载到内存当中的文件)

 我们说文件是存储在 磁盘上的,而磁盘是外部设备,访问文件的操作,其实是在访问硬件!!所以,其实 三个标准输入输出流也是在访问文件。

 又因为,像在 C 语言当中使用 printf/fprintf/fscanf/fwrite/fread/fgets/gets ···· 这些函数,想访问文件的话,其实就是想访问对应的硬件。这些函数使用用户所书写的,用户不能直接绕过中间的层级,直接访问到最底层的 硬件:

 如上图所示,用户和底层硬件之间 还有 很多层级。只能一层一层去访问到 底层硬件

所以,这些访问到硬件的函数,本质上是 用户操作接口,然后在这些函数的实现当中,就需要使用到 系统调用接口几乎所有的库,只要是想要访问到底层硬件设备,都必定是要封住 系统调用接口的。

Linux 当中常见的 文件系统调用接口介绍

 open()函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

    pathname: 要打开或创建的目标文件
    flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

参数:
    O_RDONLY: 只读打开
    O_WRONLY: 只写打开
    O_RDWR : 读,写打开

上述这三个常量,必须指定一个且只能指定一个

    O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
    O_APPEND: 追加写

返回值:

    成功:新打开的文件描述符
    失败:-1

上述的 类似 O_RDONLY 的5个参数,其实是宏,上述的 flags 这个参数,其实是作 标志的作用,用户表示某一些状态是否存在,比如 O_RDONLY 就表示 只读打开。

以往我们使用一个变量作为一个标志位的时候,一般是这一个变量只能作一个标志位,也就只能表示一种状态。但是其实,一个 int 变量,有 32 个bit 位,如果每一个位表示一个状态的话,那么按道理这个 int 变量是可以表示 32 种状态是否存在的。

上述的 flags 变量就是一个 比特位方式的标志位传递方式

我们可以利用像上述一样的 二进制操作符 取出 int 变量 每一个 bit 位的值,从而判断某一个 function 状态是否存在,从而实现不同的功能。 

这样的话,我们在外部使用这个函数,就可以在 show 函数当中传入这些宏,而且,顺序无关,在 show 函数内部,就会一次按位与上 每一个 宏,从而解析出 当前传入了多少个宏(状态)。 


O_WRONLY 参数  和 O_CREAT   参数

而且,O_WRONLY 这个参数,如果路径当中没有 此文件的话,不会创建的新的文件。除非又引入了 O_CREAT  参数。

此时,在进程的当前目录下是没有 log.txt 这个文件的。发现:文件打开失败了,而且在进程的工作目录下没有 创建新的文件。

 所以,要想上述一样 传入O_CREAT    参数告诉操作系统,如果打开文件之时,没有该文件,就创建一个新的文件。

但是我们又发现了问题:
 

使用上述  O_CREAT   参数来创建 文件的话,发现权限完全是不对的。

因为 在 Linux 当中新建一个文件的时候,必须要告诉 操作系统,要 创建的 新文件的 访问权限。

 我们要指定 新创建文件的权限,那么就要使用 带 mode 参数的 open()函数:

向下述一样传参:

 此时发现,新创建的 log.txt 文件就已经创建成功了,而且 文件的 访问权限还是 664 权限。

为什么不是 666 权限呢?因为 ,Linux 创建文件时候,有一个 umask 来控制 新创建的文件的权限。 

 如果必须要使用 666 权限的话,有一个 umask()系统调用接口
 

进程的代码当中调用 umask ()函数,可以把当前进程的 umask 值修改为传入的参数的值。 

 调用 umask ()函数之后,在系统当中是系统的 umask,在进程当中是进程自己的 umask

两个 umask,那么进程此时应该使用 哪一个 umask 呢?使用 就近原则。此时在进程当中,最近就有一个新刷新的 umask,那么就使用 这个 umask,如果没有,就使用系统当中的。


open()函数的返回值是一个 整形

 这个返回值 称之为 -- file descriptor ,换句话说 就是 文件描述符。代表的意思就是:如果打开文件成功,返回的是一个  >0 的数,此时这个数代表的就是一个 文件。


O_TRUNC参数 

O_WRONLY  参数不仅不会 帮我们创造新的文件,而且,它不是像 fopen()函数当中的 "w" 一样,先把文件当中的数据进行清空,然后从文件的开始处进行书写;它虽然依然是从文件的开始处进行书写,但是, O_WRONLY 不会 把文件当中的数据进行清空;

 所以,如果先要实现 fopen()函数当中的 "w" 参数一样的效果的话,需要加上 O_TRUNC 这个参数,它所实现的功能就是 把文件当中原本的数据清空

此时才能实现   fopen()函数当中的 "w" 参数一样的效果。


O_APPEND 参数

 这就是追加模式,在文件末尾追加数据。

很显然,追加 参数 和 清空 参数,两个参数是冲突的。

此时我们进行追加的方式在文件当中输入数据:
 


close()函数

关闭文件。

fildes: 文件描述符,也就是上诉 open()函数当中,打开文件成功返回的  file descriptor。


 write()函数

像文件当中写入数据:
 


 文件系统调用接口 和 文件库函数的关系

 我们之前说过,库函数是用户层面使用的接口,用户如果想要访问到 底层硬件的话,必须要一层一层往下去调用,才能调用到 底层硬件,或者是下层应用,数据或者说是 接口。

操作系统不会容许 用户,或者是上层的各种接口 跨层级 去访问到下层当中的数据,硬件,接口等等。

 所以,才会出现了上述所说的 文件系统调用接口 和 文件库函数的关系。库函数要想访问下层,只能通过系统调用接口。

 他一定是像下述类似的方式来在调用系统调用接口的:


 fd 文件描述符 

 操作系统也要维护 打开了的文件,这些文件都是被加载到内存当中的来才能进行 增删查改的操作的。

操作系统如何进行管理这么多的文件,用的就是 --- 先描述在组织

 谁管理谁来描述这个被打开的文件,操作系统要把这个 被打开的文件的属性,放到一起(也就是放到一个 struct 一个结构体当中),每一个被打开的文件,都会在内核当中创建一个 内核结构体。比如 我们把这个内核结构体 称职为 --- struct file。

在这个 struct file 当中,之间或者间接的包含如下属性:

  • 在磁盘的什么位置?
  • 基本属性:权限,大小,读写位置(当前要从那个位置开始读写(开始位置的偏移量)),谁打开的·······
  • 文件的内核的缓冲区信息(文件被打开时,这个文件在内核当中对应的内核空间位置)(将来要想文件当中写数据,会先写到这个缓冲区当中,然后操作系统会定期从 缓冲区当中 向磁盘当中拷贝数据)
  • ·····

上述只是举了一些例子,其实一个文件还有很多信息,每一个被打开的文件,这些信息都会保存到一个 struct file 当中,操作系统可能要维护很多个 struct file;

操作系统会以 双链表的形式,把这一个一个的 struct file 链接在一起
 

现在,我们对被打开的文件进行操作的话,就变成了对 这个 文件结构体对象双联表  的 增删查改
这个过程就是  先描述在组织 的过程,和进程当中  先描述在组织  没什么区别。


在前言当中也进行了说明:一个进程打开的文件数 是 1 : n 的关系。所以,我们如何知道,哪一个进程 打开了那些文件?或者说是,这个进程 和 那些文件有什么关系?  进程 如何和 自己打开的文件做关联的?    这些都是需要 进程 的 PCB对象当中需要去维护 的。

进程PCB 当中会存在一个指针,比如是 struct files_struct* f ; 这个

源码当中是这样写的:

这个指针指向一个 files_struct 结构体对象,在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[]

struct file * fd_array[]  数组是一个指针数组,数组当中每一个元素都指向一个文件对象,而,在这个数组当中的文件对象 所 对应的文件,就是这个进程所 打开的文件

 如上如图所示,这个 数组维护的就是一个一个 文件对象,所以我们使用 open()函数之时,open()函数的返回值就是一个 int 类型的返回值,其实对应的就是 这个 struct file * fd_array[]  数组 下标。而这个 struct file * fd_array[]  数组 我们称之为 -- 文件描述符表

在这个 文件描述符表当中就存储了 这个进程所打开的 各个文件的 struct file 文件的对象的地址

 所以,当我们调用open()函数打开一个文件,那么 就会帮这个文件创建一个 struct file 文件对象,然后,在 struct file * fd_array[]  数组当中,找到一个没有被使用过的 成员位置,把这个文件对象的 地址 存储到这个成员位置。

然后,把这个 成员 的下标,返回给用户。


 所以,之所以 被打开的文件(被加载到内存当中文件)为什么要单独用一个双链表来连接起来呢?

因为 ,进程可能会中途退出,但是文件可能还会被访问修改,而且打开文件,把文件内容加载到内存当中是操作系统做的事情,不是进程做的事情,进程只能向操作系统申请某一个文件的打开,但是不能直接访问到这个文件。

所以,关于文件的加载和操作系统所做的事情,那么操作系统就要用 一个数据结构来关联 为各个文件创建的 文件对象。方便操作系统进行管理。

进程要想和 操作系统维护的 文件对象数据结构 的话,就要用到上述所说的  files_struct 结构体对象。在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[] 。这个数组就存储了这个进程当前所打开的文件的文件对象的地址。所以,就用这个数组的下标的方式 和 操作系统维护的 文件对象数据结构 联系起来了。

 stdin,stdout,stderr 三个标准输入输出流的 文件描述符

在上图的程序当中,fd1 - fd4 是分别打开的四个文件的 下标返回值。那么输出结果是什么呢?

输出:
 

 发现,输出结果是 3 - 6 四个连续的下标,但是,fd1 不是我们打开的第一个文件吗?为什么fd 是从 3 开始而不是 从 0 号开始存储呢?

是不是 前三个 (0 - 2 下标的三个位置)已经存储了其他的文件了呢?

是的

在上述我们说过,运行一个C语言程序,默认就会打开三个标准输入输出流, stdin,stdout,stderr。而这  三个 标准输入输出流 其实本质上就是  文件

 而这 stdin,stdout,stderr 三个 标准输入输出流的名字,其实是 C语言当中规定的,不是操作系统当中规定的

操作系统当中,对于文件,只认 fd(文件描述符)。

 这三个对应的输入输出流的, stdin,stdout,stderr  对应 fd 就是 0 ,1 , 2

所以,对于 stdout,stderr   两个文件当中写入数据的话,程序执行就会直接 在屏幕上输出我们在文件当中写入的内容:
 

输出:

 同理,我们还可以从 stdin 文件当中读取数据到 程序当中,使用 read()函数可以从文件当中读取数据出来:
 

输出:
 

在上两个例子当中,我们什么文件都没有打开,(0,1,2)三个 标准输出输入的文件是 默认打开的。


操作系统当中的  三个标准输入输出流

 stdin,stdout,stderr 三个标准输入输出流 是 C语言的特性吗?

肯定不是

各种语言肯定会已有自己的  三个标准输入输出流,这  三个标准输入输出流 不是C语言特有的。

所以,其实不是 C语言的程序在运行之时 自动打开 这 三个标准输入输出流。

  • 而是,这三个 标准输出输入流 文件,在操作系统启动之时,就已经被打开了
  • 所有的进程,只是把操作系统在 文件对象双链表当中,找到这三个文件,把这个三个文件 链接到 自己的  文件描述符表 当中。

 因为使用进程 和调试进程的 用户和程序员,就是需要程序的输入输出来查看 进程运行结果


 在 C 当中的 FILE 结构体,是 C库当中自己封装的一个结构体。因为操作系统当中只认 fd 文件描述符,所以在 FILE 这个结构体当中,一定封装得 有 fd 文件描述符

 而, C 当中的 stdin,stdout,stderr 三个标准输入输出流 他们的类型就是 FILE 结构体,所以在这 stdin,stdout,stderr 三个标准输入输出流  当中,一定有 对应的 保存 fd 文件描述符  的 成员属性

 所以,我们现在就来验证一下这 stdin,stdout,stderr 三个标准输入输出流 当中的  fd 文件描述符:

程序输出:
 

发现:

 stdin,stdout,stderr 三个标准输入输出流 对应的 fd 就是 0 , 1 , 2。

 同样的,如果使用 close ()接口之间关闭了 fd 为   某一个 文件(0 , 1 , 2,那么就相当于是这个文件没有在这个进程当中打开了

 输出:

发现程序没有任何输出,因为 printf()函数就是在 stdout 当中输出,所以,这个 stdout 文件已经被关闭了,就不能在输出了 printf()函数底层必定用了 1 号文件

 其实这里的 printf()函数是写入成功了,已经在 操作系统当中的 显示器文件 当中输入了,但是当前进程已经把这个 显示器文件 从 数组当中剔除了,不能再打印了。

操作系统 文件对象双链表 中的 引用计数

 操作系统维护的 文件对象双链表 当中的文件对象,肯定是被很多的进程所指向的。一个文件对象,可能要多很多个 进程所服务。

比如 stdin,stdout,stderr 三个标准输入输出流 ,这三个文件,就是要本很多文件所使用的。

那么,操作系统如何知道一个文件被多少个 进程所使用呢?

答案就是使用 引用计数

 在 每一个文件对象当中都有一个 count 的字段,这个字段就 记录了 当前有多少 个 文件描述符指向这个文件对象。

 如果 count 字段不为了0,说明当前,虽然有进程 不再引用这个 文件对象了,但是还有进程 在引用 这个文件对象,那么就不能释放这个 文件对象空间。必须要等到 count 为0 才能释放这个文件对象空间。


 所以,一个进程 关闭文件 打开文件,本质上其实就是 把 这个文件对象当中的 count 字段           -- 或者 ++。修改 进程自己的 文件描述符表当中 对应下标当中的指针,比如 置空一个 或者 增加一个 下标位置的 指针。

在 各种语言当中 的 文件流,本质其实就是 用 结构体 或者 类 等等的方式把 fd 文件描述符 封装起来了,其中一定是有  fd 文件描述符 的。

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

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

相关文章

【Maven教程】(十):使用 Hudson 进行持续集成—— 从Hudson的安装到任务创建 ~

Maven 使用 Hudson 进行持续集成 1️⃣ 持续集成的作用、过程和优势2️⃣ Hudson 简介与安装3️⃣ 准备 Subversion 仓库4️⃣ Hudson 的基本系统设置5️⃣ 创建 Hudson 任务5.1 Hudson 任务的基本配置5.2 Hudson 任务的源码仓库配置5.3 Hudson 任务的构建触发配置5.4 Hudson …

Linux:权限篇 (彻底理清权限逻辑!)

shell命令以及运行原理&#xff1a; Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;kernel&#xff09;“ &#xff0c;但我们一般用户&#xff0c;不能直接使用kernel。而是通过kernel的“外壳”程序&#xff0c;也就是所谓的shell&#xff0c;来…

Ubuntu18.04.6安装qt5.7.1(超级详细教程)

目录 1、下载对应Linux版本的qt 2、安装完qt&#xff0c;可能也要安装下对应的编译工具 1、下载对应Linux版本的qt &#xff08;1&#xff09;准备安装的是qt5.7.1&#xff1a;qt-opensource-linux-x64-5.7.1.run &#xff08;2&#xff09;在虚拟机进入存放qt安装包的目录…

jQuery HTML/CSS 参考文档

jQuery HTML/CSS 参考文档 文章目录 应用样式 示例属性方法示例 jQuery HTML/CSS 参考文档 应用样式 addClass( classes ) 方法可用于将定义好的样式表应用于所有匹配的元素上。可以通过空格分隔指定多个类。 示例 以下是一个简单示例&#xff0c;设置了para标签 <p&g…

CVE-2023-25194 Kafka JNDI 注入分析

Apache Kafka Clients Jndi Injection 漏洞描述 Apache Kafka 是一个分布式数据流处理平台&#xff0c;可以实时发布、订阅、存储和处理数据流。Kafka Connect 是一种用于在 kafka 和其他系统之间可扩展、可靠的流式传输数据的工具。攻击者可以利用基于 SASL JAAS 配置和 SASL …

django|报错SQLite 3.8.3 or later is required的解决方案

迁移原同事写的程序&#xff0c;到新服务器上边。运行报错。解决方案有三种 降低django版本升级sqlite3&#xff0c;不低于3.8.3版本修改django源码 方案一、降低django版本 卸载高版本django pip uninstall django安装低版本&#xff0c;如 pip install django2.1.7注意&…

ARM 基础学习记录 / 异常与GIC介绍

GIC概念 念课本&#xff08;以下内容都是针对"通用中断控制器&#xff08;GIC&#xff09;"而言&#xff0c;直接摘录的&#xff0c;有的地方可能不符人类的理解方式&#xff09;&#xff1a; 通用中断控制器&#xff08;GIC&#xff09;架构提供了严格的规范&…

【C语言 | 预处理】C语言预处理详解(三)——内存对齐、手把手教你计算结构体大小

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Elasticsearch:Lucene 中引入标量量化

作者&#xff1a;BENJAMIN TRENT 我们如何将标量量化引入 Lucene。 Lucene 中的自动字节量化 虽然 HNSW 是一种强大而灵活的存储和搜索向量的方法&#xff0c;但它确实需要大量内存才能快速运行。 例如&#xff0c;查询 768 维的 1MM float32 向量大约需要 1,000,000*4*(7681…

Spring Boot 集成 ElasticSearch

1 加入依赖 首先创建一个项目&#xff0c;在项目中加入 ES 相关依赖&#xff0c;具体依赖如下所示&#xff1a; <dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.1.0</version&g…

「算法小记」-2:矩阵链相乘的方案数【迭代/递归/动态规划/区域化DP/记忆化搜索】(C++ )

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;程序员洲洲。 &#x1f388; 本文专栏&#xff1a;本文…

PHP 使用递归方式 将其二维数组整合为层级树 其中层级id 为一个uuid的格式 造成的诡异问题 已解决

不啰嗦 直接上源代码 <?php function findChildren($list, $p_id){$r array();foreach ($list as $k > $item) {if ($item[fid] $p_id) {unset($list[$k]);$length count($r);$r[$length] $item;if ($t findChildren($list, $item[id])) {$r[$length][children] …

Godot Shader -变量的声明

变量的声明 uniform 可以将值传递给着色器。这些值对整个着色器来说是全局的&#xff0c;被称为 uniform。当一个着色器后来被分配给一个材质时&#xff0c;uniform 将作为可编辑的参数出现在其中。uniform 不能从着色器内部写入。 可以在材质编辑器的修改这些uniform的值&a…

万宾科技内涝积水监测仪使用效果一览

当一个城市突降暴雨&#xff0c;对城市管理部门来讲首当其中的是防止积水成患。随着城市人口快速增长&#xff0c;基础设施建设也日益受到更多的关注&#xff0c;城市内涝问题频繁增加&#xff0c;会给城市带来严重的经济损失和人员的安全问题。城市生命线工程建设过程中&#…

黑客(网络安全)技术——高效自学1.0

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习…

屏幕提词软件Presentation Prompter mac中文版使用方法

Presentation Prompter for mac是一款屏幕提词器软件&#xff0c;它可以将您的Mac电脑快速变成提词器&#xff0c;支持编写或导入&#xff0c;可以在一个或多个屏幕上平滑地滚动&#xff0c;Presentation Prompter 下载是为适用于现场表演者&#xff0c;新闻广播员&#xff0c;…

Ps:选区的基本操作

在 Photoshop 中&#xff0c;选区是为处理局部图像而自行定义的一个区域。 定义选区后&#xff0c;操作被限制在选区之内。选区周围显示的虚线边框&#xff0c;俗称“蚂蚁线”。 全选 Select All Ps菜单&#xff1a;选择/全部 Select/All 快捷键&#xff1a;Ctrl A 提示&#…

JVM字符串常量池StringTable

目录 一、StringTable为什么要调整 二、String的基本特性 三、String的内存分配 四、字符串拼接操作 五、intern()方法 六、Stringtable的垃圾回收 七、G1中String去重操作 一、StringTable为什么要调整 jdk7之前&#xff0c;hotspot对于方法区的实现是永久代&#xff…

C语言之文件操作(详解版)

不知不觉我们已经学到C语言的文件操作部分了&#xff0c;这部分内容其实很有意思&#xff0c;因为它可以直接把我们代码中的数据写入硬盘&#xff0c;而不是我们关掉这个程序&#xff0c;代码就没有了&#xff0c;让我们开始学习吧&#xff01; 目录 1.为什么使用文件 2.什么…

伪造referer [极客大挑战 2019]Http1

打开题目 没有发现什么&#xff0c;我们查看源代码 在这里我们发现了提示 访问一下页面得到 提示说不能来自于https://Sycsecret.buuoj.cn&#xff0c;我们尝试访问一下这个url 发现访问不了 我们bp抓包一下 伪造个referer头 referer:https://Sycsecret.buuoj.cn 发包过去…