【Linux】文件描述符——万字详解

目录​​​​​​​

前言

预备知识 

复习C语言的文件接口

写方式打开文件

追加方式打开文件

读方式打开文件

系统的文件接口

open

close 

write

read

文件描述符

0 & 1 & 2 

理解文件描述符

文件描述符的分配规则

重定向的本质

dup2

理解Linux下一切皆文件


前言

本篇文章内容有点多,但是非常的重要,,希望你可以坚持看下去,看会你将会对Linux下一切皆文件有一个全新的理解。

如果内容有错或者不足,还望你能指出,让我们共同进步

预备知识 

知识点1 

当我们创建了一个空文件时,这个空文件是否会占空间呢?

答案是:当然啦,因为文件 = 内容 + 属性,文件的属性也是数据当然会占空间啦

因此我们对文件的操作无外乎是对文件的内容或者文件的属性做操作。

知识点2

文件会被加载到磁盘中,我们在C语言或者其它语言中通过编写代码并运行来访问文件,那么这本质是谁在访问文件呢?

答:是进程通过C语言或者其它语言给我们提供的文件接口来访问的。

知识点3 

我们的磁盘是属于硬件,我们普通用户是没有权利向硬件中写入的,只有操作系统才有这个权利,而我们也想要向硬件中写入的话必须通过操作系统提供给我们的接口才能向磁盘中写入。

知识点4

显示器也是属于硬件,我们在C语言中使用printf向显示器中打印时,其实和向磁盘中写入是一样的,没有本质的区别。 

知识点5

所有的语言给我们都提供了有文件访问接口,其实在底层实现都封装的是系统调用的接口,那么这些语言为什么要封装其接口呢?

封装的原因是系统调用的接口比较难,为了能让这些接口更好的给用户使用,所以在语言层面上对这些接口进行了封装。并且这些语言为了实现跨平台性,在底层把所用平台提供的文件接口都实现了一遍,通过条件编译的方式将这些代码进行动态裁剪(就跟多态才不多),这样就能实现在不同平台调用的文件访问接口都是一样的。

知识点6

Linux下一切皆文件,在这里先有个感性的认识,站在你写代码的角度,你会认为加载到内存中的就是文件,但站在系统的角度,能够被读取或者能够被写入的设备就叫做文件。

狭义上理解的文件:普通的磁盘文件

广义上理解的文件:几乎所有外设都可被称为文件。

复习C语言的文件接口

文件打开的方式

写方式打开文件

 先来看看下面的代码

#include <stdio.h>

int main()
{
  FILE *fp = fopen("log.txt", "w");
  if(fp == NULL)
  {
    perror("fopen");
    return 1;
  }


  fclose(fp);
  return 0;
}

我们都知道当我们执行这份代码时,如果当前路径下没有log.txt文件,w方式会在当前路径下创建log.txt文件,那么什么叫做当前路径呢?

所以当前路径是指当你的进程运行起来时所处的工作路径

在来看看下面的代码

#include <stdio.h>
#include <string.h>

int main()
{
  FILE *fp = fopen("log.txt", "w");
  if(fp == NULL)
  {
    perror("fopen");
    return 1;
  }
  
  const char *s1 = "hello man\n";
  fwrite(s1, strlen(s1), 1, fp);


  fclose(fp);
  return 0;
}

我们知道字符串的末尾是会加上一个'\0',那么这里的strlen(s1)是否要加1呢?

答案肯定是不要的,因为'\0'结尾是C语言的规定,文件不需要遵守,并且文件中要保存的是有效数据,'\0'不是有效数据。如果你加上1的话就会出现以下乱码的情况。

当我们以w方式写入时,是先清空文件中的内容再写入。

把上个图片中的hello man和乱码清除再写入hello Linux

这个就和输出重定向很像

追加方式打开文件

#include <stdio.h>
#include <string.h>

int main()
{
  FILE *fp = fopen("log.txt", "a");
  if(fp == NULL)
  {
    perror("fopen");
    return 1;
  }
  
  const char *s1 = "hello Linux\n";
  fwrite(s1, strlen(s1), 1, fp);


  fclose(fp);
  return 0;
}

 

读方式打开文件

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])//命令行参数
{
  if(argc != 2)
  {
    printf("argv error");
    return 1;
  }
  FILE *fp = fopen(argv[1], "r");
  if(fp == NULL)
  {
    perror("fopen");
    return 2;
  }

  char line[64];
  while(fgets(line, sizeof(line), fp) != NULL)
  {
    fprintf(stdout, "%s", line);//向显示器输出
  }

  fclose(fp);
  return 0;
}

这样就实现了和cat一样的功能

在上面的代码中用到了一个stdout,这个stdout为标准输出

在C语言中会默认打开三个标准输入输出流:stdin、stdout、stderr

并且这三个流的类型都是FILE*的(解释在后面)

系统的文件接口

在上面已经说到了C语言标准库中给我们提供的文件操作的函数在底层其实是调用的系统提供的文件接口,那么系统给我们提供了哪些文件接口呢?下面让我们来认识一下这些文件接口

open

打开成功返回的是一个文件描述符,失败则返回-1

参数介绍

pathname:要打开的目标文件

flags:打开文件的方式。提供了多个选项,可传入多个选项共同构成这个参数

下面三个选项必须指定一个且只能指定一个

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWR:可读可写

上面选项配合其它选项一起使用,如

  • O_APPEND 表示追加方式的写入文件
  • O_CREAT 表示如果指定文件不存在,则创建这个文件
  • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
  • ……

这些选项还有好多这里就不一一列举了,大家可以通过man进行查看

可以观察到这些选项都是大写,一般这种大写的基本上是宏定义,这里也不例外。

我们通过或(|)运算就可以将这些选项结合在一起使用了,例如:O_WRONLY|O_CREAT

为什么使用或运算就可以将这些选项结合在一起使用了呢?

其实这里是用到了一个位图的思想,看了下面代码想必你应该就会明白了

#include <stdio.h>
#include <string.h>

//用不重复的位就可以标识一个状态
#define ONE 0x1 //0000 0001
#define TWO 0x2 //0000 0010
#define THREE 0x4 //0000 0100

void print(int flags)
{
  //&运算:有0则为0,全1才为1
  if(flags & ONE) printf("I am ONE\n");
  if(flags & TWO) printf("I am TWO\n");
  if(flags & THREE) printf("I am THREE\n");
}

int main()
{
  print(ONE);
  printf("--------------------------------\n");
  print(TWO);
  printf("--------------------------------\n");
  print(THREE);
  printf("--------------------------------\n");
  print(ONE | TWO | THREE);//0000 0001 | 0000 0010 | 0000 0100 = 0000 0111
  printf("--------------------------------\n");
  print(ONE | TWO);//0000 0001 | 0000 0010 = 0000 0011
  printf("--------------------------------\n");
  print(ONE | THREE);//0000 0001 | 0000 0100 = 0000 0101
  printf("--------------------------------\n");
  return 0;
}

库中也确实是这样干的

mode:为设置文件访问的权限

红色区域即为文件的权限,新建文件夹的默认权限是0666,但实际看到的权限不是这个值,因为还会受到umask的影响,umask默认是0002

使用

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

int main()
{
  int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("open success, fd:%d\n", fd);

  return 0;
}

close 

关闭一个文件描述符,成功返回0,失败返回-1并且设置对应的错误码

和fclose的用法就差不多

write

返回值为实际写入的有效数据,ssize_t为有符号整形

参数介绍

fd:为文件描述符

buf:为要写入的数据

count:为写入数据的大小

使用演示

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("open success, fd:%d\n", fd);

  const char *s1 = "hello write\n";
  write(fd, s1, strlen(s1));
    
  close(fd);
  return 0;
}

 

read

 

返回值的读取到的有效数据 

参数和write一样就不过多介绍了

使用演示

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  int fd = open("log.txt", O_RDONLY);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("open success, fd:%d\n", fd);
  
  char buffer[64];
  memset(buffer, '\0', sizeof(buffer));
  read(fd, buffer, sizeof(buffer));
  printf("%s\n", buffer);
  
  close(fd);
  return 0;
}

这里系统接口可不会给我们在末尾加上\0,所以我们需要手动在末尾加上'\0'。 

文件描述符

0 & 1 & 2 

先来看一下下面的代码 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  int fd1 = open("log1.txt", O_WRONLY|O_CREAT);
  int fd2 = open("log2.txt", O_WRONLY|O_CREAT);
  int fd3 = open("log3.txt", O_WRONLY|O_CREAT);
  int fd4 = open("log4.txt", O_WRONLY|O_CREAT);

  printf("open success, fd1:%d\n", fd1);
  printf("open success, fd2:%d\n", fd2);
  printf("open success, fd3:%d\n", fd3);
  printf("open success, fd4:%d\n", fd4);
    
  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  return 0;
}

从上面的的代码可以看出fd都是从3开始的,那么前面的0,1,2去哪里了呢

在上面复习C语言文件接口时,提到过C语言中会默认打开三个标准输入输出流:stdin、stdout、stderr,这三个标准的输入输出流对应的就是0,1,2。

不信的话我们可以验证一下

stdout 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  fprintf(stdout, "hello stdout\n");
  const char *s1 = "hello 1\n";
  write(1, s1, strlen(s1));
  return 0;
}

stdin 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  char buf[64];
  ssize_t s = read(0, buf, sizeof(buf));
  if(s > 0)
  {
    buf[s] = '\0';//在读取到的有效数据的末尾添加\0
    printf("%s\n",buf);
  }

  return 0;
}

我们再回到C语言中,我们在使用C语言的文件函数时都见过FILE*,这个FILE*是一个指针,那么这个FILE是什么呢?

FILE其实是一个结构体,是C语言对底层做出的一个封装,里面包含了很多成员其中就有这个fd(文件描述符)。

#include <stdio.h>

int main()
{
  printf("stdin:%d\n", stdin->_fileno);
  printf("stdout:%d\n", stdout->_fileno);
  printf("stderr:%d\n", stderr->_fileno);

  return 0;
}

理解文件描述符

进程要访问文件,必须先要打开文件,并且一个进程可以打开多个文件,那么如果多个进程都打开自己的文件,系统中就会存在大量被打开的文件,面对如此之多的文件,操作系统肯定要管理起来,而管理的本质就是先描述,再组织。所以在操作系统的内部为了管理每一个被打开的文件,会构建一个结构体,这个结构体中包含了一个被打开的文件的所有内容,然后再将这一个个的结构体用双链表组织起来。

当我们调用open时,必须让进程和文件关联起来,才能打开对应文件,那么我们的进程又是如何和文件关联起来的呢?

在每个进程的PCB中都有一个指针*files,指向一张表files_struct,该表中会包含一个指针数组,数组中的每个元素都是一个指向打开文件的指针,这就实现了和文件进行关联。

那么既然是数组就会有下标,并且是从0开始的,所以文件描述符的本质就是该指针数组的下标。所以只要拿着文件描述符,就可找到对应的文件。

文件描述符的分配规则

从上面的演示中我们就知道了0,1,2已经被三个标准输入输出流给占了,所以我们调用open打开文件时文件描述符(后面就简称fd了)只能从3以后开始,那么我们尝试close关闭0或者2看一下会发生什么现象?

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
  //close(0);
  close(2);
  int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  close(fd);
  return 0;
}

所以fd的分配规则是:当前没有被使用的最小的一个下标

重定向的本质

当我们close关闭1又会发生什么现象呢?

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
  close(1);
  int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd); 
  printf("fd: %d\n", fd);

  fflush(stdout);
  close(fd);
  return 0;
}

本该往显示器打印的内容结果打印到文件中去了。

这种现象是不是就和输出重定向是一样的了

本质

输入重定向

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
  close(0);
  int fd = open("log.txt", O_RDONLY, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("fd: %d\n", fd); 
  char buffer[64];
  fgets(buffer, sizeof buffer, stdin);
  printf("%s\n", buffer);

  fflush(stdout);
  close(fd);
  return 0;
}

追加重定向

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
  close(1);
  int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  fprintf(stdout, "hello hjx\n");

  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

但事实上重定向并不是这样实现的,我们上面的实现只是利用了fd的分配规则,而在操作系统中早就给我们准备了实现重定向的接口。

dup2

 

dup2的使用描述​​​​​​​

这里的意思就是将旧的文件描述符所对应的内容拷贝的新的文件描述符中,最后两个文件描述符是和旧的文件描述符保持一致,如果必要时把新的文件描述符关掉。

dup2的使用

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
  int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }
  dup2(fd, 1);
  fprintf(stdout, "hello dup2\n");

  close(fd);
  return 0;
}

理解Linux下一切皆文件

 Linux下一切皆文件时Linux的一个设计哲学,它是体现在操作系统的软件设计层面的。

我们都知道Linux是用C语言写的,那么你知道怎么用C语言来实现面向对象,甚至是运行时多态吗?

在C++或者其它语言中实现面向对象我们都知道是要用类,类中包含了成员属性和成员方法,但是C语言中只有结构体的概念,而结构体中只能包含成员属性,包含不了成员方法,那该怎么办呢?

没事,我们可以用到一个函数指针来实现其效果。

我们知道操作系统再往下就是硬件(比如磁盘、网卡、键盘、显示器等等)了,这些不同的硬件对应的一定是不同的驱动方法(你访问磁盘和访问键盘是不一样的),但是这些硬件都是外设,所以这每一个设备的核心驱动程序都可以是read/write——>I/O,根据冯诺依曼所有的外设无非都是I/O,所以所有的外设都可以有自己的read和write,但是代码实现一定是不一样的。

这种设计方案就叫做虚拟文件系统(VFS)。 

在Linux内核中也确实是这么干的。 

内核源码

 

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 (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	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 (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go 单元测试基本介绍

文章目录 引入一、单元测试基本介绍1.1 什么是单元测试&#xff1f;1.2 如何写好单元测试1.3 单元测试的优点1.4 单元测试的设计原则 二、Go语言测试2.1 Go单元测试概要2.2 Go单元测试基本规范2.3 一个简单例子2.3.1 使用Goland 生成测试文件2.3.2 运行单元测试2.3.3 完善测试用…

存储过程的使用(一)

目录 不带参数的存储过程 创建一个存储过程&#xff0c;向数据表 dept 中插入一条记录 带 IN 参数的存储过程 在存储过程中接受来自外部的数值&#xff0c;在存储过程中判断该数值是否大于零并显示 输入一个编号&#xff0c;查询数据表emp中是否有这个编号&#xff0c;如果…

博客系统项目测试(selenium+Junit5)

在做完博客系统项目之后&#xff0c;需要对项目的功能、接口进行测试&#xff0c;利用测试的工具&#xff1a;selenium以及Java的单元测试工具Junit进行测试&#xff0c;下面式测试的思维导图&#xff0c;列出该项目需要测试的所有测试用例&#xff1a; 测试结果&#xff08;全…

【Tesla T4为例】GPU安装最新版本NVIDIA Driver、CUDA、cuDNN、Anaconda、Pytorch

NVIDIA Driver 进入英伟达官网下载页面 按照以上方式选择即可得到>535.113.01版本的驱动&#xff0c;可以实现多卡推理&#xff0c;小于这个版本会导致多卡训练以及推理报错 虽然最新版本为550.54.15&#xff0c;但是535版本更加稳定&#xff0c;并且pytorch目前只支持到1…

云渲染采用了哪些核心技术来实现其高效的计算的?

云渲染运用云计算技术&#xff0c;将3D渲染任务分配至远程服务器集群以实现高速高效渲染&#xff0c;释放本地资源&#xff0c;缩短了影视动画、效果图等的制作周期&#xff0c;在影视、建筑、游戏等行业发挥关键作用。哪云渲染都用了哪些技术呢&#xff1f;我们一起来看看。 …

2024新版淘宝客PHP网站源码

源码介绍 2024超好看的淘客PHP网站源码&#xff0c;可以做优惠券网站&#xff0c;上传服务器&#xff0c;访问首页进行安装 安装好了之后就可以使用了&#xff0c;将里面的信息配置成自己的就行 喜欢的朋友们拿去使用把 效果截图 源码下载 2024新版淘宝客网站源码

UE5下载与安装

官方网站&#xff1a;https://www.unrealengine.com/zh-CN 1、下载启动程序安装包。 登录官网后&#xff0c;点击首页右侧下载按钮下载Epic Games启动程序的安装包&#xff0c;如下图&#xff1a; 2、安装启动程序。 双击步骤1所下载安装软件&#xff0c;如下图&#xff1a;…

一个开箱即用的物联网项目,开源免费可商用

一、平台简介 今天给大家推荐一款开源的物联网项目&#xff0c;简单易用&#xff0c;非常适合中小团队和个人使用&#xff0c;项目代码和文档完全开源&#xff0c;个人和公司都可以应用于商业项目&#xff0c;只需要保留开源协议文件即可。 本项目可应用于智能家居、农业监测…

Jmeter测试学习笔记

第一章 jmeter基础知识 一.Jmeter工具中的组件 1.测试计划&#xff1a;Jmeter测试的起点。容器。 2.线程组&#xff1a;代表一定的用户 3.取样器&#xff1a;发送请求的最小单元 4.逻辑控制器&#xff1a;处理请求逻辑 5.前置处理器&#xff1a;请求之前的操作 6.后置处…

算法课程笔记——pair的使用

先思考&#xff0c;为什么 STL 中的容器和算法都是用的左闭右开区间&#xff1f; | | | 这样迭代器只需要支持和!(或者<或者)操作就可以方便的进行区间遍历了。 其它区间设置的话&#xff0c;要么得支持<操作&#xff0c;要么得在循环体内&#xff0c;操作之前进行!判定。…

Proxmox VE 实现批量增加多网络

前言 实现批量创建多网络&#xff0c;更改主机名称&#xff0c;hosts解析 初始化网卡&#xff0c;主机名称&#xff0c;hosts解析&#xff0c;重启网卡 我的主机六个网卡&#xff0c;使用的有四个网卡&#xff0c;以下一键创建和初始化主机名称我是以硬件的SN号最为主机的名…

【InternLM 实战营第二期作业04】XTuner微调LLM:1.8B、多模态、Agent

基础作业 训练自己的小助手认知 1.环境安装 安装XTuner 源码 # 如果你是在 InternStudio 平台&#xff0c;则从本地 clone 一个已有 pytorch 的环境&#xff1a; # pytorch 2.0.1 py3.10_cuda11.7_cudnn8.5.0_0studio-conda xtuner0.1.17 # 如果你是在其他平台&#x…

[NISACTF 2022]huaji?

注意要加--run-asroot

第四百六十七回

文章目录 1. 知识回顾2. 使用方法3. 示例代码4. 内容总结 我们在上一章回中介绍了"OverlayEntry组件简介"相关的内容&#xff0c;本章回中将介绍OverlayEntry组件的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中介绍了Overlay…

高通 Android 12 源码编译aidl接口

最近在封装系统sdk接口 于是每次需要更新aidl接口 &#xff0c;传统方式一般使用make update-api或者修改Android.mk文件&#xff0c;今天我尝试使用Android.bp修改 &#xff0c;Android 10之前在Android.mk文件修改&#xff0c;这里不做赘述。下面开始尝试修改&#xff0c;其实…

CTFHub(web sql注入)(二)

布尔盲注 盲注原理&#xff1a; 将自己的注入语句使用and与?id1并列&#xff0c;完成注入 手工注入&#xff1a; 爆库名长度 首先通过折半查找的方法&#xff0c;通过界面的回显结果找出数据库名字的长度&#xff0c;并通过相同的方法依次找到数据库名字的每个字符、列名…

ROS 2边学边练(29)-- 使用替换机制

前言 启动文件用于启动节点、服务和执行流程。这组操作可能有影响其行为的参数。替换机制可以在参数中使用&#xff0c;以便在描述可重复使用的启动文件时提供更大的灵活性。替换是仅在执行启动描述期间评估的变量&#xff0c;可用于获取特定信息&#xff0c;如启动配置、环境变…

2024年哪一款洗地机好用?四大热门主流机型分享

传统的拖地方式必须是拖一会就得清洗一遍拖把&#xff0c;如果房屋面积大&#xff0c;中途得经历无数次换清水的过程&#xff0c;而且拖地是得频繁得弯腰用力气&#xff0c;顽固的污渍还需要来回反复拖几遍&#xff0c;甚至要蹲下身子手动抹布清洁&#xff0c;真的是费时费力。…

【科研入门】评价指标AUC原理及实践

评价指标AUC原理及实践 目录 评价指标AUC原理及实践一、二分类评估指标1.1 混淆矩阵1.2 准确率 Accuracy定义公式局限性 1.3 精确率 Precision 和 召回率 Recall定义公式 1.4 阈值定义阈值的调整 1.5 ROC与AUC引入定义公式理解AUC算法 一、二分类评估指标 1.1 混淆矩阵 对于二…

脾虚百病生,出现这3种情况,说明是脾虚了,简单2步养出好脾胃~

中医认为脾胃为后天之本&#xff0c;人体通过脾胃来消化吸收营养物质&#xff0c;脾主运化水谷精微、运化水湿&#xff0c;脾主肌肉&#xff0c;脾主生血、统血&#xff0c;为气血生化之源&#xff0c;是人体气机升降的枢纽。 脾虚百病生 李东垣在《脾胃论》说&#xff1a;“内…