[Linux-文件I/O] 文件函数系统文件接口缓冲区文件描述符dup2inode软硬链接动静态库

news/2024/5/1 4:44:41/文章来源:https://blog.csdn.net/Allen9012/article/details/127169093

BingWallpaper

[Linux-文件I/O] 文件函数&系统文件接口&缓冲区&文件描述符&dup2&inode&软硬链接&动静态库

    • 文件IO
    • C语言文件操作
    • 系统接口文件操作
      • 文件描述符
        • 文件描述符分配
      • 进程和文件之间的对应关系是如何建立的?
      • 打开用open
        • mode
      • 关闭用close
      • 读用read写用write
        • read
        • write
      • FILE*文件指针
    • 重定向
      • 输入输出追加重定向
      • dup2
        • 简易shell功能增加
      • 系统接口&重定向小结
    • 缓冲区
      • 缓冲
    • inode
      • 文件系统
        • 目录也是文件
      • 软硬链接
        • 硬链接
          • 什么是硬链接
          • 硬链接快速入门
        • 软链接
          • 什么是软链接
          • 软链接快速入门
    • 动静态库
      • 动静态库快速入门
      • 动静态库的特征
      • 动静态库的创建
        • 生成静态库
        • 使用静态库
        • 生成动态库
        • 使用动态库
        • 动静态库小结
    • 总结

image-20220706101950933

文件IO

我们说文件IO主要分为下面几个重点

  1. 文件描述符和重定向的本质
  2. 软连接和硬连接和inode
  3. 动静态库的知识

我们说一个文件的描述符,一个进程可以打开多个文件,一个进程启动时就会打开0,1,2,也就是输入输出和err,系统中会存在多个打开的文件,如何管理文件,先描述后组织,用一个数据结构就可以描述和组织struct_file

文件描述符的本质是数组的下标,他和FILE*的关系

C语言文件操作

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

💎 w和a一样吗?

a是追加:也是写入,前者是从头开始写,后者是从结尾开始写(追加),我们恰恰可以想到重定向’>‘和追加重定向’>>’

r 		Open text file for reading.The stream is positioned at the beginning of the file.r+ Open for reading and writing.The stream is positioned at the beginning of the file.
w 		Truncate(缩短) file to zero length or create text file for writing.The stream is positioned at the beginning of the file.
w+ 		Open for reading and writing.The file is created if it does not exist, otherwise it is truncated.The stream is positioned at the beginning of the file.
a 		Open for appending (writing at end of file).The file is created if it does not exist.The stream is positioned at the end of the file.
a+ 		Open for reading and appending (writing at end of file).The file is created if it does not exist. The initial file positionfor reading is at the beginning of the file,but output is always appended to the end of the file.

🥝 打开文件之后我们创建的文件路径在哪里?

注意,创建文件一定是当前路径,这和可执行程序的位置没有任何关系,和启动程序的位置有关

🥝 Linux中一切皆文件,键盘和显示器是文件吗?那为什么C语言中没有打开文件,我们也可以直接printf输出,scanf输入?

任何进程在运行的时候OS默认都会打开三个输入输出流,分别是stdin,stdout,stderror,仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

演示示例:利用stdout输出到显示屏

#include <stdio.h>
#include <string.h>
int main()
{const char *msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

💛 fopen究竟在干什么?

  1. 给调用的用户申请struct FILE结构体变量,并返回地址(FILE*)
  2. 在底层通过open打开文件,并返回fd,把fd填充到file变量的fileno

类似的fread,fwite,fputs,fgets等都是通过FILE* 指针找到fd

系统接口文件操作

文件描述符

在Linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针,已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。

标准输入(stdin)的文件描述符是 0

标准输出(stdout)的文件描述符是 1

标准错误(stderr)的文件描述符是 2

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

文件描述符分配

文件描述符的分配规则:从当前未被分配的最小整数处分匹配。

运行下面的代码我们可以看到一个现象

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd1 = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("fd1: %d\n", fd1);int fd2 = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("fd2: %d\n", fd2);int fd3 = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("fd3: %d\n", fd3);int fd4 = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("fd4: %d\n", fd4);int fd5 = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("fd5: %d\n", fd5);
}

image-20220701202440303

🍂 我们发现文件描述符是从3开始标记的,那么0,1,2在哪里呢?

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器

另外文件描述符的分配规则是从小到大分配

🗡一个进程可以打开多少个文件描述符?

系统当中一个进程打开文件描述符的数量是由限制的,可以通过"ulimit -a"指令查看

image-20220703104229045

这里的"open files"的大小就是一个进程可以打开文件描述符的数量

"open files"的大小可以通过ulimit -n [num]修改,eg:ulimit -n 10000,将一个进程可以打开的文件描述符数量限制为10000

进程和文件之间的对应关系是如何建立的?

我们知道,当一个程序运行起来时,操作系统会将该程序的代码和数据加载到内存,然后为其创建对应的task_struct、mm_struct、页表等相关的数据结构,并通过页表建立虚拟内存和物理内存之间的映射关系。

image-20221004190158816

而task_struct当中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。

当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。

image-20221004190349868

🎅 磁盘文件 V.S. 内存文件

我们说上一问的文件是内存文件,下面思考一下磁盘文件,我们在磁盘中存的时候就是存的是文件的内容+属性

磁盘文件由两部分构成,分别是文件内容和文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息。

内存的文件的打开主要是文件的属性信息,把属性信息写到文件中,而主要的读写的内容会延后式的慢慢加载数据(缓冲区)

打开用open

我们可以尝试用系统接口打开文件试试看

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

🐙什么是系统函数参数传参标志位?

其实他是一个通过定义一组宏引用传参时传宏,内部进行宏判断可以通过bit位的方式来传递多种选项给函数

int是32bit,所以说理论上可以传递32种标志位,其中这些有关的宏中32个bit位只有一个是1

我们可以先来查看一个open函数,open中的flags就是一个标志位,它是一个int类型的

image-20220701192203401

#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: **要打开或创建的目标文件
若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。(注意当前路径的含义)
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1

实际上这里所谓的文件描述符本质上是一个指针数组的下标,指针数组当中的每一个指针都指向一个被打开文件的文件信息,通过对应文件的文件描述符就可以找到对应的文件信息。

当使用open函数打开文件成功时数组当中的指针个数增加,然后将该指针在数组当中的下标进行返回,而当文件打开失败时直接返回-1,因此,成功打开多个文件时所获得的文件描述符就是连续且递增的。

而Linux进程默认情况下会有3个缺省打开的文件描述符,分别就是标准输入0、标准输出1、标准错误2,这就是为什么成功打开文件时所得到的文件描述符是从3开始进程分配的。

要查看这些参数标志位我们可以查找这个路径下的fcntl-linux.h文件

/usr/include/bits/fcntl-linux.h

假如我们想要满足同时两个标志位的话,只要按位或起来就可以,比如下面这样

 int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);

mode

open函数的第三个参数是mode,表示创建文件的默认权限。

例如,将mode设置为0666,则文件创建出来的权限如下:

在这里插入图片描述

但实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664。

在这里插入图片描述

若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。

关闭用close

打开获取fd之后可以关闭

close(fd);

读用read写用write

read

NAMEread - read from a file descriptor
SYNOPSIS#include <unistd.h>//参数[你从哪里读,读到哪里去,要读几个字节]ssize_t read(int fd, void *buf, size_t count);

理论上count是期望读的字节数,返回值是实际读的字节

write

NAMEwrite - write to a file descriptor
SYNOPSIS#include <unistd.h>//参数[你写到哪里去,你从哪里读,要读几个字节]ssize_t write(int fd, const void *buf, size_t count);

理论上也是一样的返回值

🍸 使用示例:

   int countt = 5;const char *msg = "hello there\n";while(count){write(fd1, msg, strlen(msg));count--;}

🌃strlen这里要不要+1,留一个’\0’?

不需要,多一个字符的话会出现乱码,因为结尾是’\0’的规则是C语言的,C++中是string就没有’\0’,而这里的文件不认你C的规则

🌴 我们发现这个系统接口和C接口好像啊,所以他们之间是不是有什么关系呢?

其实在Linux系统中C语言的fopen调用的就是open接口,同理fclose调用的就是close接口,类似的windows也会提供它自己的接口供语言调用

而正是C语言这些的接口完成了这些个封装,相较于系统级接口,其实实质上1. 更加方便调用 2. 而且满足了跨平台性

🌈 一个进程可以打开多个文件吗?文件管理 | 内存文件

当然可以,那么多个进程也能打开多个文件,系统中,事实上,在任何时刻都可能存在大量的已经打开的文件,这就是是操作系统的文件管理,回到管理,我们对文件的管理也是先描述再组织,利用进程,再内存中描述数据结构,一个文件用一个struct,一堆文件就用链表把多个struct链接起来

🎉 文件有几部分,是不是只保存文件的内容?

文件除了它的内容以外还是有文件的属性(元信息),文件实质上=内容+属性,所以不是说我文件里面有256KB最后就显示该磁盘文件的大小是256KB,还有属性所代表大小

image-20220702122118651

FILE*文件指针

文件指针:C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括缓冲区和文件描述符。而文件描述符是文件描述符表的一个索引,也就是说c语言的文件指针是Linux系统中对文件描述符的一种封装。

/usr/include/libio.h中是这么定义_IO_FILE结构体的

在这里插入图片描述

struct _IO_FILE {int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags/* The following pointers correspond to the C++ streambuf protocol. *//* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. *///读缓冲区char* _IO_read_ptr;   /* Current read pointer */char* _IO_read_end;   /* End of get area. */char* _IO_read_base;  /* Start of putback+get area. *///写缓冲区char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr;  /* Current put pointer. */char* _IO_write_end;  /* End of put area. */char* _IO_buf_base;   /* Start of reserve area. */char* _IO_buf_end;    /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base;  /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno;   //文件描述符的数值
...

文件指针指向的结构体,可以发现文件指针结构体里包含有文件描述符,说明文件指针是对文件描述符的一种封装

文件指针是C语言库里的提供的一个结构体,文件描述符是系统调用接口

💢 为什么系统已经有了文件描述符,库里面还要对其做一层封装呢?

  1. 方便程序员使用
  2. 可以提高程序的移植性

🎍 FILE结构体里面还有缓冲区:

  • 将数据写入硬盘文件中时,缓冲区的刷新方式默认为全缓冲,
  • 将数据写入显示文件中,缓冲区的刷新方式默认为行缓冲;
  • 而系统调用的函数write()写入时,是没有缓冲的,是因缓冲区是C库提供的,在FILE结构体里。文件描述符和缓冲区都是FILE结构体的成员,所以文件描述符指向的file结构体里是没有缓冲区的。

重定向

输入输出追加重定向

输出重定向

在明确了文件描述符的概念及其分配规则后,现在我们已经具备理解重定向原理的能力了。你会发现重定向的本质就是修改文件描述符下标对应的struct file*的内容。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{umask(0);close(1);int fd = open("myfile", O_WRONLY|O_CREAT, 0666);if(fd < 0){perror("open");return 1;}printf("hello there\n,printf");fprintf(stdout,"hello there,fprintf\n")fputs("hello there\n,fputs", stdout);fflush(stdout);//需要刷新输出缓冲区close(fd);return 0;
}

我们发现,本来应该输出到显示器上的内容,输出到了文件myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

printf是C库当中的IO函数,一般往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

输出重定向原理:

image-20221004190629841

追加重定向

衍生来说,所谓的追加重定向就是打开文件的时候或上一个追加的宏就可以了

输入重定向

输入重定向就是,将我们本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。

输入重定向的原理:

如果我们想让本应该从“键盘文件”读取数据的scanf函数,改为从log.txt文件当中读取数据,那么我们可以在打开log.txt文件之前将文件描述符为0的文件关闭,也就是将“键盘文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是0。

image-20221004191056219

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(0);int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}char str[40];while (scanf("%s", str) != EOF){printf("%s\n", str);}close(fd);return 0;
}

这是因为:scanf函数是默认从stdin读取数据的,而stdin指向的FILE结构体中存储的文件描述符是0,因此scanf实际上就是向文件描述符为0的文件读取数据。

dup2

上述操作的重定向显然是不太合理的,要先关闭文件然后再打开文件这样重定向不能完成很多情况

dup2就是可以一行命令完成重定向,也能够保证我们可以指定式重定向,意思是将oldfd重定向至newfd

NAMEdup, dup2, dup3 - duplicate a file descriptor
SYNOPSIS#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);#define _GNU_SOURCE             /* See feature_test_macros(7) */#include <fcntl.h>              /* Obtain O_* constant definitions */#include <unistd.h>int dup3(int oldfd, int newfd, int flags);
DESCRIPTIONThese system calls create a copy of the file descriptor oldfd.dup() uses the lowest-numbered unused descriptor for the new descriptor.dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:*  If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.*  If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

示例:

//int fd = open("./README.TXT",O_RDWR|O_APPEND,0777);
dup2(fd,STDIN_FILENO);

实际上通过调用dup2(fd,STDIN_FILENO);fd和STDIN_FILENO之间建立了一种关系。本来所有函数的输出都要往终端输出哪里走,但是通过调用dup2函数,终端输出关闭了(也就是若newfd原来已经打开了一个文件,则先关闭这个文件),那么终端输出关闭后这些输出往哪里去呢?当然是往我们新复制的文件描述符这里了。所有相当于是把newfd重定向至了oldfd。

把old的内容拷贝到new中,所以最终new和old中的内容都会是old的内容,注意dup完之后是很难恢复的

image-20221004191322592

测试实例:

我们将打开文件log.txt时获取到的文件描述符和1传入dup2函数,那么dup2将会把fd_arrya[fd]的内容拷贝到fd_array[1]中,在代码中我们向stdout输出数据,而stdout是向文件描述符为1的文件输出数据,因此,本应该输出到显示器的数据就会重定向输出到log.txt文件当中。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}close(1);dup2(fd, 1);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

追加重定向

只需在输出只写的基础上添加O_APPEND选项

  int fd = open("log.txt", O_WRONLY | O_APPEND); // aif (fd < 0){perror("open");return 1;}// fd, 1close(1);dup2(fd, 1);

输入重定向

//int fd = open("./README.TXT",O_RDWR|O_CREAT,0777);
dup2(fd,0);

简易shell功能增加

我们可以增加重定向功能给我们的shell,关键是找到大于符号,如果在命令行中找到了>的话,将他置为’\0’,字符串就拆成两块,处理完了之后就是前面的是指令后面的是指向的文件

首先我们需要知道
💎 子进程会继承父进程打开的文件的信息
💎 进程替换不会影响打开文件的信息

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后的最大个数
int main()
{int type = 0; //0 >, 1 >>, 2 <char cmd[LEN]; //存储命令char* myargv[NUM]; //存储命令拆分后的结果char hostname[32]; //主机名char pwd[128]; //当前目录while (1){//获取命令提示信息struct passwd* pass = getpwuid(getuid());gethostname(hostname, sizeof(hostname)-1);getcwd(pwd, sizeof(pwd)-1);int len = strlen(pwd);char* p = pwd + len - 1;while (*p != '/'){p--;}p++;//打印命令提示信息printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);//读取命令fgets(cmd, LEN, stdin);cmd[strlen(cmd) - 1] = '\0';//实现重定向功能char* start = cmd;while (*start != '\0'){if (*start == '>'){type = 0; //遇到一个'>',输出重定向*start = '\0';start++;if (*start == '>'){type = 1; //遇到第二个'>',追加重定向start++;}break;}if (*start == '<'){type = 2; //遇到'<',输入重定向*start = '\0';start++;break;}start++;}if (*start != '\0'){ //start位置不为'\0',说明命令包含重定向内容while (isspace(*start)) //跳过重定向符号后面的空格start++;}else{start = NULL; //start设置为NULL,标识命令当中不含重定向内容}//拆分命令myargv[0] = strtok(cmd, " ");int i = 1;while (myargv[i] = strtok(NULL, " ")){i++;}pid_t id = fork(); //创建子进程执行命令if (id == 0){//childif (start != NULL){if (type == 0){ //输出重定向int fd = open(start, O_WRONLY | O_CREAT | O_TRUNC, 0664); //以写的方式打开文件(清空原文件内容)if (fd < 0){error("open");exit(2);}close(1);dup2(fd, 1); //重定向}else if (type == 1){ //追加重定向int fd = open(start, O_WRONLY | O_APPEND | O_CREAT, 0664); //以追加的方式打开文件if (fd < 0){perror("open");exit(2);}close(1);dup2(fd, 1); //重定向}else{ //输入重定向int fd = open(start, O_RDONLY); //以读的方式打开文件if (fd < 0){perror("open");exit(2);}close(0);dup2(fd, 0); //重定向}}execvp(myargv[0], myargv); //child进行程序替换exit(1); //替换失败的退出码设置为1}//shellint status = 0;pid_t ret = waitpid(id, &status, 0); //shell等待child退出if (ret > 0){printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出码}}return 0;
}

系统接口&重定向小结

🏖 什么叫做一个进程在默认创建的时候就打开了0,1,2呢?

OS把键盘,输出显示器和错误显示器形成struct files,然后默认让files_struct中的0,1,2分别指向标准输出,标准输入和标准错误

🌋 1和stdout/stdin/stderr的关系

其实stdout/stdin/stderr是一个结构体,是struct FILE类型的结构体,它内部肯定有一个成员变量是fd

缓冲区

在之前的重定向中,我们发现如果我们close(1)之后只是输出到stdout仍是没有在显示器中看到有输出,因为此时我们没有刷新缓冲区,所以在执行fflush(stdout)之后我们才看到有输出,可是我们之前明明就已经close(fd)了呀,为什么还要fflush呢?这就涉及到了缓冲区知识

缓冲

缓存有三种:

  1. 无缓冲
  2. 行缓冲(常见的对显示器进程刷新数据时,效率和可用性的平衡)
  3. 全缓冲(对文件写入的时候采用全缓冲,效率最高)

⚓️ 为什么要有缓冲区?

我们都知道硬盘的速度要远低于 CPU,它们之间有好几个数量级的差距,当向硬盘写入数据时,程序需要等待,不能做任何事情,就好像卡顿了一样,用户体验非常差。计算机上绝大多数应用程序都需要和硬件打交道,例如读写硬盘、向显示器输出、从键盘输入等,如果每个程序都等待硬件,那么整台计算机也将变得卡顿。

但是有了缓冲区,就可以将数据先放入缓冲区中(内存的读写速度也远高于硬盘),然后程序可以继续往下执行,等所有的数据都准备好了,再将缓冲区中的所有数据一次性地写入硬盘,这样程序就减少了等待的次数,变得流畅起来

缓冲区的另外一个好处是可以减少硬件设备的读写次数。其实我们的程序并不能直接读写硬件,它必须告诉操作系统,让操作系统内核去调用驱动程序,只有驱动程序才能真正的操作硬件。

从用户程序到硬件设备要经过好几层的转换,每一层的转换都有时间和空间的开销,而且开销不一定小;一旦用户程序需要密集的输入输出操作,这种开销将变得非常大,会成为制约程序性能的瓶颈。

这个时候,分配缓冲区就是必不可少的。每次调用读写函数,先将数据放入缓冲区,等数据都准备好了再进行真正的读写操作,这就大大减少了转换的次数。实践证明,合理的缓冲区设置能成倍提高程序性能。

缓冲区其实就是一块内存空间,它用在硬件设备和用户程序之间,用来缓存数据,目的是让快速的 CPU 不必等待慢速的输入输出设备,同时减少操作硬件的次数。

🥖 缓冲区在哪里?是谁提供的?
我们用一个测试用例来引出:

#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{//C语言函数printf("hello printf\n");fprintf(stdout,"hello fprintf\n");//systemconst char *msg = "hello write\n";write(1, msg, strlen(msg));fork();return 0;
}

我们发现如果我们只是执行该文件的话,效果如下:

image-20220703102603073

但是一旦我们重定向到一个文件中时却变成了5行:

image-20220703102651779

这恰恰说明了:C接口打了两次,OS的API打印了一次

所以说重定向和非重定向会改变进程的缓冲方式

我们发现printf 和fwrite (库函数)都输出了2次,而write 只输出了一次(系统调用)。为什么呢?fork有关!

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。

而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

但是进程退出之后,会统一刷新,写入文件当中。

但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。

write 没有变化,说明没有所谓的缓冲。

综上: **printf fwrite 库函数会自带缓冲区,而write 系统调用没有带缓冲区。**另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是write 没有缓冲区,而printf fwrite有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供

在/usr/include/libio.h中有提到缓冲区

image-20220703105545906

🌍 解释为什么已经close(fd)了呀,为什么还要fflush呢?

首先需要下面的图理解关系:关键是用户缓冲区和内核缓冲区的区分

image-20220703122208889

我们说用户在用户缓冲区刷新,是不可能直接写到磁盘或者是输出到显示器上的,所以肯定是经过了内核缓冲区的一套刷新机制,然后再到磁盘和显示器,也就是fflush把进程缓冲区的数据刷新到内核缓冲区,fsync把内核缓冲区的数据刷新到物理媒介上

image-20220703124309969

那为什么没有fflush(stdout)就不会输出我printf在fd和fputs在stdout的内容呢?因为开始的close(1)导致了重定向,所以从行缓冲变为了全缓冲(上一个问题中提到的:一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲),假设我现在不fllush(stdout)或者是fclose(stdout)的话,我们调用的是close(fd)时,fd:1已经关了,所以没有办法通过fd的指针找到文件刷新了,数据无法刷新到内核缓存区中,就失败了

inode

inode是一个文件的属性集合,Linux中的几乎每一个文件都有一个inode

一般ls打印的是文件的属性,cat打印的是文件的内容

文件系统

before inode 先看一下文件系统:

要知道文件系统得先知道什么是磁盘?

磁盘是在我们的计算机中的几乎唯一一个机械设备,磁盘具有永久性,但是是掉电易失的存储介质,目前所有的普通文件都是存储在磁盘中的。这里我们可以理解磁盘的本质其实是线性结构,然而又因为整个磁盘是很大的,我们可以把它划分成多个扇区,代销也可以不同,好处就是便于管理,因为只要管理好一个区就可以管理好所有区

BootSector是一个启动块,后面的是一个分区中的不同分组

image-20220704153550400

👻 **Block Group:**ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。
👻 **超级块(Super Block):**存放文件系统本身的结构信息。记录的信息主要有:block 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载(分配磁盘符)的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
👻 GDT,Group Descriptor Table:块组描述符,描述块组属性信息
👻 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
👻 **inode位图(inode Bitmap):**每个bit表示一个inode是否空闲可用。
👻 **i节点表(Inode table):**存放文件属性,如: 文件大小,所有者/组,最近修改时间,权限等,注意inode中是没有文件名的,只有编号
👻 数据区(Data blocks):存放文件内容,数据区也分一个个块号,这些块号都应该写入inode,记录一个数组,通过数组,我就可以访问其中的不同块号

🥝 inode table里面有哪些inode已经被占用?哪些没有?Data blocks里面有哪些block已经被占用?哪些没有?

inode bitmap,通过极限提高查找效率,可以知道哪些inode是没有占用的,哪些是占用的,其中可以是为0的就是没有被占用,为1的就表示是被占用的

同样的也有block bitmap,思想是一样的

image-20220704184258779

注意点:

  1. 其他块组当中可能会存在冗余的Super Block,当某一Super Block被破坏后可以通过其他Super Block进行恢复。
  2. 磁盘分区并格式化后,每个分区的inode个数就确定了。

💲 创建一个新文件需要哪些操作?

  1. 存储属性
    内核先找到一个空闲的i节点(这里是889012)。然后把这里的比特位置为1,说明这里要被占用了,然后把这个inode table的空间申请给这个文件,内核把文件信息记录到其中。
  2. 存储数据
    该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
  3. 记录分配情况
    文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录

☢️ 新的文件名abc。linux如何在当前的目录中记录这个文件?

内核将入口(889012,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

🍃 写入1KB数据的过程?删除一个文件的过程?

扫描bitmap找到文件对应的inode,再通过inode找到对应的data block,发现文件没有申请过空间的话,就还需要再block中申请空间,扫描block bitmap,置为1,把block id写入block,再写入数据1KB

删除文件只需要,在inode bitmap置为0,把inode对应的block,把block的bitmap置为0

因此拷贝文件慢,删除文件快,因此说删除文件是可以恢复的,但是要注意删除的文件也是会被覆盖的

目录也是文件

🌛 如何理解目录?目录创建的过程

目录的inode的创建是一样的,目录属性是类似的,目录的内容放的是,当前目录下文件名和对应文件的inode指针(inode号),目录文件里面放的是文件名和inode的映射,这些内容算作数据文件

🍆 ls在做什么?ls -l做了什么?cat是在做什么?

ls就是找到当前目录的inode对应的数据内容位置,只需要文件名就可以了

ls -l则需要找到当前目录的inode,就找到了当前的数据块,就找到了当前目录下的文件名,再通过文件名找到每个文件的inode指针,再去跑到inode table找到文件的权限,所属组,所属成员,大小,创建时间和文件名,拼接字符串输出

cat一个文件,找出当前文件的inode属性,在inode中找到数据,然后把内容显示出

💎 进入一个目录需要什么权限?读取一个目录需要什么权限?在目录下创建文件需要什么权限?

进入一个目录需要执行权限x,读取一个目录需要读取数据区,所以需要读权限r,在目录下创建文件写权限w(需要向当前目录写入文件名和inode的映射关系)

软硬链接

硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,在shell中的做法

软连接和硬链接的区别是:

  • 软连接是一个独立的文件,有自己的inode
  • 硬链接没有独立的inode
  • 软链接文件的大小和创建时间和源文件不同
  • 硬链接文件和源文件的大小和创建时间一样

硬链接

什么是硬链接

例如有一个文件 a.txt,文件内容长度是 1024 个字节,存放在硬盘上的某个块(block)中,假设就是第 10000 个块吧。

那么这个文件对应的 inode 节点中,就会把 10000 这个块记录下来。

同时,它还有一个 links 字段,表示:当前这个 inode 对应一个文件,此时 inode.links 的值为 1。(引用计数)

此时,如果我们用另一个文件名 a_hard_link.txt,也来表示 a.txt 这个文件。

也就是说:虽然我们用了 2 个文件名称,但是本质上指向同一个文件,内容都指向第 10000 个块中存储的文件内容。

Linux 系统中提供了硬链接来支持这样的目的,它仅仅是把 inode 节点中的 links 字段的值加1即可,也就是 inode.links 的值变成了 2。

img

硬链接快速入门
ln [filename] [hard-link name]

真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode。

pokemon和pikachu的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 1063107 的硬连接数为2。

我们在删除文件时干了两件事情:

  1. 在目录中将对应的记录删除
  2. 将硬连接数-1,如果为0,则将对应的磁盘释放。

image-20220704205241081

🎉 关键在于硬链接有什么用?

硬链接的本质相当于取了一个别名

  1. 允许一个文件拥有多个有效路径名,这样用户就可以建立硬链接到重要的文件,以防止“误删”源数据。这时候就可以方便文件多人共享当很多人同时对同一个文件进行维护的时候,如果大家都直接操作这个文件,万一不小心把文件删除了,大家就都玩完了!此时,可以在每个人自己的私人目录中,创建一个硬链接。每次只需要对这个硬链接文件进行操作,所有的改动会自动同步到目标文件中。由于每个人都是操作硬链接文件,即使不小心删除了,也不会导致文件的丢失。因为删除硬链接文件,仅仅是把该文件的 inode 节点中的 links 值减 1 而已,只要不为 0,就不会真正的删除文件。
  2. 同时方便目录通过相对路径的方式进行跳转,其实我们在cd .cd ..的时候这个.就是硬链接,你创建了子文件夹会发现当前文件夹的硬链接数就变了
  3. 文件备份.在备份的时候,如果是实实在在的拷贝一份,那真的是太浪费磁盘空间,特别是对于我这种只有 256G 硬盘空间的笔记本。此时,就可以利用硬链接功能,既实现文件备份的目的,又节省了大量的硬盘空间.很多备份工具利用的就是硬链接的功能,包括 git 工具,当克隆本地的一个仓库时,执行 clone 指令,git 并不会把仓库中的所有文件拷贝到本地,而仅仅是创建文件的硬链接,几乎是零拷贝!

补充:硬链接存在 2 个限制:

  1. 不允许用户给目录创建硬链接,即:用户不可以,操作系统可以(想一下每个目录下的 . 和 …)
  2. 只有在同一个文件系统中的文件,才能创建硬链接,也就是说:不能跨文件系统

🎉不用进入目录我能不能知道目录里面又几个子目录?

可以,是当前目录的硬链接-2

软链接

什么是软链接

为了克服硬链接的 2 个限制,软链接被引入进来了。

软链接也叫符号链接,它是一个独立的文件。

软链接文件的内容是一个文本字符串,存储的是目标文件(即:链接到的文件)的路径名。

这个路径名可以指向任意一个文件系统的任意文件或者目录,甚至可以指向一个不存在的文件。

与创建硬链接不同的是:当我们创建了一个软链接之后,操作系统会创建一个新的 inode 来表示这个软链接文件。

如果我们把源文件删除掉之后,inode 节点会被删除掉,当真正的目标文件被删除之后,快捷方式也就没有存在的意义了。

img

软链接快速入门
ln -s [filename] [soft-link name]

image-20220704205739886

image-20220704204756901

软连接的本质有点像是快捷方式,这个文件也有自己的数据块,保存的是指向文件所在的路径和文件名

💴 软连接有什么用?

  1. 灵活切换不同版本的目标程序 :在开发的过程中,对于同一个工具软件,可能要安装多个不同的版本,例如:Python2Python3g++ 4.8g++ 7.2 等等。此时就可以通过软链接来指定当前使用哪个版本。
  2. 动态库版本管理 (后面有提到)
  3. 快捷方式

参考https://cloud.tencent.com/developer/article/1837822

###acm

acm指的是文件的三个时间:

  • Access 最后访问时间
  • Modify 文件内容最后修改时间
  • Change 属性最后修改时间

想要查看文件修改的时间的话可以用命令

stat [filename] 

image-20220705172237107

modify一般个change是联动的,modify改了change也会改,不过反之可能不会连带改,比如说我只修改文件的属性,比如权限,不修改内容,只有change变了

动静态库

动静态库快速入门

动静态库是一个可执行程序的半成品(本质是一堆.o的集合,不包含main,但是包含了大量的方法),静态库与动态库都是二进制的程序代码的集合。将程序编写成库提供给第三方使用,这样做的好处是不会造成源码泄漏,而且调用者不用关心内部实现。

image-20220705192024988

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码

☯️ windows中的动静态库?Linux动静态库的命名?

在windows中.dll是动态库,而.lib是静态库

库的名字,是去除lib和后缀之后名字:libc.so.6->c库

image-20220705190847797

查看可执行文件所依赖的库文件

ldd  [可执行文件]

image-20220705194039390

我们发现这里的动态库采用的是软连接方式,同时我们发现了软链接文件的属性是l

动静态库的特征

缺点优点特点
静态库自身比较大,占空间
静态链接浪费空间,多个C静态程序加载的时候一定会有在内存中的大量重复代码
与库无关了,不需要库N.A.
动态库必须依赖库,没有库没办法运行通过地址空间共享的基本都是代码,是不能被写入的

补充描述:在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

动静态库的创建

生成静态库

测试用例:add.c/add.h/sub.c/sub.h

  1. 先将所有的.c文件生成.o文件
[root@localhost linux]# ls
add.c add.h main.c sub.c sub.h
# 直接gcc
[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o
  1. 生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o
ar是gnu归档工具,rc表示(replace and create)
  1. 查看静态库中的目录列表:
  • t:列出静态库中的文件
  • v:verbose 详细信息
[root@localhost linux]# ar -tv libmymath.a
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o

Makefile操作生成静态库

🈵 补充Makefile:

mylib=libcal.a                                                                                                                                         
CC=gcc    $(mylib):add.o sub.o    ar -rc $(mylib) $^    
%.o:%.c    # 这里的%表示通配符,表示任意一个.o文件的依赖是.c$(CC) -c $<    # 这里的$<是把.c文件一个个写上.PHONY:clean    
clean:    rm -f $(mylib) *.o    .PHONY:output    
output:    mkdir -p mathlib/lib    mkdir -p mathlib/include    cp *.h mathlib/include    cp *.a mathlib/lib  

在这个Makefile的基础上我们进行make,会发现达到了结果

image-20220705202721732

使用静态库

这个时候我们需要使用静态库,我们怎么使用呢?

首先比如我们写了一个test.c,我们需要包上头文件,这里推荐使用尖括号

///
///test.c//
///
#include <stdio.h>
#include <add.h>int main()
{int a=10;int b=20;int c=my_add(a,b);return 0;
}

但是此时我们发现gcc还是找不到我们的add.h,那肯定,因为gcc不知道路径,不像系统库

gcc -o test test.c

image-20220705203304492

gcc test.c -I./mathlib/include

现在还是报错,但是我们可以看到gcc已经找到了我们的my_add函数,这是因为现在我们的main函数中,还是不知道函数my_add是来自于add.h的
image-20220705204740265

gcc test.c -I./mathlib/include -L./mathlib/lib

现在还是报错,因为有时候这个目录下会有很多库,但是具体要链接哪一个是不确定的,所以还得告诉它要链接哪一个库

image-20220705205025014

所以我们通过-lcal告诉我们的gcc是这个libcal.a的库,这样就过了

gcc test.c -I./mathlib/include -L./mathlib/lib -lcal

image-20220705205332253

生成动态库

🌿 shared: 表示生成共享库格式
🌿 fPIC:产生位置无关码(position independent code)
🌿 库名规则:libxxx.so

  1. 生成.o文件,需要带选项-fPIC
  2. 链接,需要带选项-shared
  3. 打包给别人用的时候只需要给他人.h头文件和.so动态库文件
  4. 可以手动创建目录分别放入include文件和lib文件

image-20220706094109240

 gcc -fPIC -c sub.c add.c gcc -shared -o libmymath.so *.o 

使用动态库

使用的时候直接使用需要还是需要先告诉路径,才能够编译

gcc test.c -I mlib/include/ # -I不够
gcc test.c -I mlib/include/ -L mlib/lib # -I和-L还不够
gcc test.c -I mlib/include/ -L mlib/lib lcal # -I -L -l

此时还没有结束,之前是编译阶段,不过这个程序当我们运行的时候,他会形成一个进程,但是由于运行的时候不知道动态库在哪里,所以说还是找不到动态库的数据位置以加载到进程中

image-20220706094850772

方法有三:

1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改LD_LIBRARY_PATH

 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[lib的绝对路径]

3、ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新

cat /etc/ld.so.conf.d/bit.conf/root/tools/linux # 输出
ldconfig

动静态库小结

🌊 如何制作动静态库?

都必须现状话为.o文件,然后打包

# 静态库:
gcc -c
ar -rc
# 动态库
gcc -fPIC -c
gcc -shared [name]

📦 使用库?

给别人的,include(头文件集合)+lib(库文件)

静态库 -I -L -l

动态库 -I -L -l + export LD_LIBRARY_PATH

给别人库的时候,本质是给别人一份头文件(库的使用说明书)+一份库文件(.a/.so,库的实现),目标文件生成后,静态库删掉,程序照样可以运行。

-I #头文件在哪里
-L #指定库路径
-l #指定库名

🍁 其实我们看上一顿操作太麻烦了,其实事实上我们可以把它放到/usr/include/系统库,这时安装好后还需要用-l选项不过可以省去-I和-L

🍁 也就是说安装库的过程就是拷贝到系统路径下,因此以后打好库的时候要给他人安装,只需要写一个脚本去安装库就可以了直接简单操作了

🍁 使用第三方库的时候一般要指明库名称

总结

🔥 为什么说Linux下一切皆文件?

我们说磁盘,显示器,网卡,键盘,怎么理解这些东西是文件呢?这些东西被抽象成了文件。你可以使用访问文件的方法访问它们获得信息

这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 socket,读PIPE)的操作都可以用read函数来进行;几乎所有更改(更改文件,更改系统参数,写 socket,写 PIPE)的操作都可以用write函数来进行。

虚拟文件系统(Virtual File System, 简称 VFS),是 Linux 内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个抽象功能,允许不同的文件系统共存。系统中所 有的文件系统不但依赖 VFS 共存,而且也依靠 VFS 协同工作。

image-20220703165921511

为了能够支持各种实际文件系统,VFS 定义了所有文件系统都支持的基本的、概念上的接口和数据 结构;同时实际文件系统也提供 VFS 所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式 上与VFS的定义保持一致。换句话说,一个实际的文件系统想要被 Linux 支持,就必须提供一个符合VFS标准的接口,才能与 VFS 协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS 层和内核的其他部分看来,所有文件系统都是相同的。

user space的应用程序与VFS的接口就是系统调用,VFS与驱动程序的接口就是file_operations。通过file_operations方法将 设备类型的差异化屏蔽了,这就是Linux能够将所有设备都理解为文件的缘由

image-20220703165632523

既然这样,那设备的差异化又该如何体现呢?在文件系统层定义了文件系统访问设备的方法,该方法就是 address_space_operations,文件系统通过该方法可以访问具体的设备。

在C语言中,可以通过函数指针,做到调用同一个方法,指向不同对象时可以执行不同的方法,从而实现多态的性质。我们在每个struct file当中包含一堆函数指针,这样,在struct file上层看来所有的文件都是调用统一的接口,不管底层具体是什么文件;在底层我们通过函数指针指向不同硬件的方法来实现我们的设备差异性的多态

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

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

相关文章

TEE OS中断篇(一):系统的中断处理

前面我学习了线程方面的东西&#xff0c;这个假期&#xff0c;空闲了来看看《手机安全和可信应用开发指南》这本书的中断篇。 中断处理一个完整的系统都会存在中断&#xff0c;ARMv7架构扩展出了Monitor模式而ARMv8使用EL的方式对ARM异常运行模式进行了重新定义&#xff0c;分为…

Spring 测试运行的时候提示 Unable to find a @SpringBootConfiguration 错误

Spring 进行测试的时候提示的错误信息如下: SEVERE: Caught exception while closing extension context: org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext@c63c11ed java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you n…

Flink学习笔记(4)——Flink运行架构

目录 一、Flink运行时架构 1.1 系统架构 1.1.1 整体构成 1.1.2 作业管理器&#xff08;JobManager&#xff09; 1.1.3 任务管理器&#xff08;TaskManager&#xff09; 1.2 作业提交流程 1.2.1 高层级抽象视角 1.2.2 独立模式&#xff08;Standalone&#xff09; 1.2.…

SpringCloud 使用 Turbine 聚合监控 Hystrix 健康状态

Hystrix 的降级熔断,只是被迫的折中方案,并不是我们所期望的结果,我们还是期望系统能够永远健康运行。绝大多数情况下,一个系统有很多微服务组成,在高峰期很可能个别微服务会发生降级熔断,我们必须能够通过监控才行,这样才能快速发现并解决问题。 Hystrix 是 Netflix 的…

soc的核间通信机制-->mailbox

对于mailbox&#xff0c;这个东西其实看到了很多次&#xff0c;但是一直不知道是啥。这里大概看了一下&#xff0c;知道了为甚有这个玩意儿&#xff0c;以及这个玩意相关的有啥&#xff0c;至于具体怎么使用&#xff0c;以及详细的工作原因等着以后再说吧。 正文 目前很多芯片…

微信小程序开发实战(SM周期及WXS脚本)

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; 微信小程序 &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f4…

webshell 提权

在我们使用cve或者其他方式获取shell 后 python -c import pty;pty.spawn("/bin/bash") 获取一个交互式的bash shell 使用id 命令可以查看当前的用户权限 查看当前的linux 系统版本 利用kali自带的漏洞检索库检索漏洞 searchsploit privilege | grep -i linux |…

【MySQL】数据库介绍以及MySQL数据库

目录 数据库介绍 数据库概述 数据表 MySql数据库 MySql安装 登录MySQL数据库 ​​​​​​​SQLyog&#xff08;MySQL图形化开发工具&#xff09; 数据库介绍 数据库概述 什么是数据库(DB:DataBase) 数据库就是存储数据的仓库&#xff0c;其本质是一个文件系统&#…

实训任务1:Linux基本操作

文章目录一、实训目的二、实训要求三、实训任务1、创建并配置三个虚拟机2、创建SSH连接3、实现IP地址与主机名的映射4、关闭和禁用防火墙5、创建目录结构6、压缩打包7、安装软件包8、创建脚本文件9、直接运行脚本10、虚拟机相互免密登录11、远程拷贝文件一、实训目的 通过实训…

代谢组学和宏基因组学研究不同添加剂对青贮品质的影响

​ 发表期刊&#xff1a;Bioresource Technology 影响因子&#xff1a;9.642 百趣生物提供服务&#xff1a;代谢组学宏基因组 研究背景 人口增长促进了全球肉类和牛奶消费量增加&#xff0c;养殖所需饲料用量也逐年上升&#xff0c;发酵后的饲料是进行农副产品处理更好的选…

大数据技术Spark3.0详解

一、Spark3.0 简介 Spark3.0版本包含了3400多个补丁程序&#xff0c;是开源社区做出巨大贡献的最高峰&#xff0c;带来了Python和SQL功能的重大进步&#xff0c;并着眼于探索和生产的易用性。 1、Spark3.0新功能 &#xff08;1&#xff09;通过自适应查询执行&#xff0c;动…

基于物联网的智能厨房安全监测系统-上位机程序

CSDN话题挑战赛第2期 参赛话题&#xff1a;学习笔记 博客写作背景----项目中解决的问题 最近遇到一个基于TCP/IP网络的远程智能物联网系统&#xff0c;采用Arduino Uno控制器作为下位机&#xff0c;采用LabVIEW作为远程监控软件&#xff0c;两者通过网络实现通信。初步定为使…

2022/10/4——基于stm32mp157a的M4核的中断实验

本次实验采用STM32CubeMX软件进行元器件的初始化 本次实验采用的中断源为三个按键和光电开关、火焰传感器、人体红外。其实验接口分别为&#xff1a; key1------>PF9 key2------>PF7 key3------>PF8&#xff08;检测方式&#xff1a;下降沿&#xff09; 光电开…

详解欧拉计划第107题:最小网络

下面这个无向网络包含有7个顶点和12条边,其总重量为243。 这个网络也可以用矩阵的形式表示如下。 ABCDEFGA-161221---B16--1720--C12--28-31-D211728-181923E-20-18--11F--3119--27G---231127-然而,我们其实可以优化这个网络,移除其中的一些边&#

windows幻灯片壁纸

幻灯片设置10秒设置为10秒 win+r输入regedit 查找路径 HKEY_CURRENT_USER\Control Panel\Personalization\Desktop Slideshow 修改interval文件没有就创建一个 修改10进制文件 60000(毫秒为60秒)改为10000(毫秒为10秒)即可 修改后点击幻灯片放映他自己设置为10分钟不用管他已经…

深入分析vhost-user网卡实现原理 —— VirtIO Features协商

文章目录前言数据结构设备模型deviceVirtIONetPCIVirtIONetNICStateNetClientStatenetdevNetVhostUserStatevhost_netvhost_devchardevchardevChardevClassSocketChardevFeaturesVirtIONetVirtIODevicevhost_devNetVhostUserStatefeature_bitsBackend流程详解启动过程网络连接网…

【Docker Desktop】Neo4j

1、下载neo4j并启动 docker run --rm --name testneo4j -p 7474:7474 -p 7687:7687 -d -v C:/Users/ASUS/Desktop/neo4j/data:/data -v C:/Users/ASUS/Desktop/neo4j/logs:/logs -v C:/Users/ASUS/Desktop/neo4j/import:/var/lib/neo4j/import -v C:/Users/ASUS/Desktop/neo4j…

Mysql出现问题:慢查询日志失效解决方案

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作者&#x1f3c6;&#xff0c;阿里云专家博主&#x1f3…

【C++入门】学习使用二维数组基本知识及用法详解

&#x1f9db;‍♂️iecne个人主页&#xff1a;&#xff1a;iecne的学习日志 &#x1f4a1;每天关注iecne的作品&#xff0c;一起进步 &#x1f4aa;一起学习&#xff0c;必看iecne &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录一.定义方式1.1…

深度学习进阶-自然语言处理-04-基于计数的方法(上)

语料库&#xff1a;大量的文本数据 语料库通常采用树的结构&#xff0c;我们这里的与料理假定没有添加任何标签&#xff0c;在实际项目中&#xff0c;一般都会给文本数据添加标签&#xff08;如&#xff0c;词性&#xff09; 1 语料库的预处理 步骤一:创建单词列表 #样本文章…