当前位置: 首页 > news >正文

基础I/O -> 如何谈文件与文件系统?

文件的基础理解

  1. 空文件也要在磁盘上占据空间。
  2. 文件 = 文件内容+文件属性。
  3. 文件操作 = 对内容的操作 + 对属性的操作或者是对内容和属性的操作。
  4. 标定一个文件,必须使用:文件路径 + 文件名(具有唯一性)。
  5. 如果没有指明对应的文件路径,默认是在当前路径(进程当前的路径)进行文件访问。
  6. 当我们把fopen、fclose、fwrite等接口写完之后,代码编译之后,形成二进制可执行程序后,但是没有运行,文件对应的操作有没有被执行呢?没有。对文件操作的本质是:进程对文件的操作!
  7. 一个文件要被访问,必须先被打开(用户+进程+OS)。用户调用相关函数接口、进程执行函数、OS访问磁盘文件。
  8. 所以文件操作的本质是:进程和被打开文件之间的关系。

C语言有文件操作的接口,C++、JAVA、Python、php、go等其他语言呢?

同样有,但是操作接口都不一样。

文件存在磁盘上,磁盘又是一个硬件,要访问硬件,只有操作系统能访问,所以要想访问磁盘就不能绕过OS,OS也必定要提供文件级别的系统调用接口。所以,无论上层语言如何变化:

  1. 库函数可以千变万化,但是底层不变。
  2. 库函数底层都必须调用系统调用接口。

C文件I/O

FILE * fopen ( const char * filename, const char * mode );
//成功返回指向FILE对象的指针,失败返回NULL。int fclose ( FILE * stream );
//成功返回0,失败返回EOF。size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );size_t fread ( void * ptr, size_t size, size_t count, FILE * stream ); int puts ( const char * str );
int fputs ( const char * str, FILE * stream );
//fputs不会写入其他字符、puts会在自动在末尾附加换行符。char * fgets ( char * str, int num, FILE * stream );
char * gets ( char * str );
//fgets在结果字符串中包含任何结束换行符、gets结果字符串中不包含任何字符。 ...fseek、ftell、rewind......

写段简单的对文件操作的C语言代码:

int main()
{FILE* fp = fopen("log.txt","w");     //w 只写方式打开,不存在创建。存在将内容自动清空// FILE* fp = fopen("log.txt","r");   //r 只读方式打开// FILE* fp = fopen("log.txt","a");   //a 以追加方式打开if(NULL == fp){perror("fopen");return 1;}// char buffer[64];// while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)// {//     buffer[strlen(buffer)-1] = 0;//    // fputs(buffer,stdout);//     puts(buffer);//会在末尾自动添加\n// }int cnt = 5;while(cnt){fprintf(fp,"%s:%d\n","hello world!",cnt--);}fclose(fp);return 0;
}

打开文件的方式:

系统文件I/O 

操作文件,除了上述C接口(其他语言也有),当然还可以采用系统接口来进行文件的访问。

打开文件:
#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);
int creat(const char *pathname, mode_t mode);pathname:要打开或创建的目标文件
flags:打开文件时,可以传入多个参数选项,用一个或者多个常量进行“或”运算,构成flags。参数:O_RDONLY: 只读打开。O_WRONLY: 只写打开。O_RDWR:   读、写打开。这三个常量,必须指定一个且只能指定一个。O_APPEND: 追加写。O_CREAT:  若文件不存在,则创建它。需要使用mode选项,指明新文件的访问权限。 O_TRUNC:  若文件存在,则清空文件内容。返回值:成功:返回新打开的文件描述符。失败:返回-1。
关闭文件:
#include <unistd.h>
int close(int fd);写入文件:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count)
//成功返回成功写入的字节个数,失败返回-1。读取文件:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//成功返回成功读取的字节个数,失败返回-1。//注意:"const void *buf",返回值为void*。在其他语言中,写入文件的类型包括文本类、二进制类,是语言提供的文件读取的分类,但是在OS看来,不管写入的是什么,都是二进制。读也是一样,没有要读入数据的具体类型,就是要读几个字节,具体读到的是什么由自己决定。

写文件:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main()
{umask(0);//系统默认文件mask为0002,也可以更改(更改的为子进程的mask,父进程shell不受影响)int fd = open("log.txt",O_WRONLY|O_CREAT,0666);if(fd<0){perror("open");return 1;}//"hello world:5" hello world是字符串,5、4、3、2、1是整数//所以要将"hello world:5"... 格式化为字符串int cnt = 5;char outBuffer[64];while(cnt){sprintf(outBuffer,"%s:%d\n","hello world!",cnt--); //将格式化数据转化为字符串存储在outBuffer中write(fd,outBuffer,strlen(outBuffer));//向文件写入string时,要不要+1,即strlen(outBuffer)+1,将字符串末尾的"\0"写入 ?//不需要,以"\0"作为字符串结尾,是c语言规定的,和文件没有关系。文件要的是字符串的有效内容。}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()
{int fd = open("log.txt",O_RDONLY);if(fd<0){perror("open");return 1;}char buffer[1024];ssize_t num = read(fd,buffer,sizeof(buffer)-1);if(num > 0){buffer[num] = 0; //将读取结果最后一个字符置为\0,证明读取到的为字符串。}printf("%s",buffer);close(fd);return 0;
}

总结:

C库函数接口:fopen、fclose、fwrite、fread、fseek...

系统调用接口:open、close、write、read、lseek...

可以认为库函数接口底层都是封装了系统调用接口,方便二次开发。

文件描述符

文件操作的本质:进程和被打开文件的关系。

进程可以打开多个文件,所以系统中一定会存在大量的被打开的文件,OS要将这些被打开的文件管理起来(以先描述,再组织的方式),OS为了管理对应的打开的文件,必定要为文件创建对应的内核数据结构来标识文件(也就是struct file{ },其中包含了文件的大部分属性)。

所以在操作系统内部就可以把每一个struct file{ }文件用链式结构链接起来,OS只需要找到struct file{ }的起始地址,对文件的管理就变成了对链表的增删查改。

那么,进程和被打开文件之间的关系是怎样维护的呢?

通过对open函数的了解,打开成功返回的是文件的文件描述符,是一个整数。

看下面这段代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>#define FILE_NAME(number) "log.txt"#number //字符串拼接int main()
{umask(0);int fd0 = open(FILE_NAME(1),O_WRONLY|O_CREAT,0666);int fd1 = open(FILE_NAME(2),O_WRONLY|O_CREAT,0666);int fd2 = open(FILE_NAME(3),O_WRONLY|O_CREAT,0666);int fd3 = open(FILE_NAME(4),O_WRONLY|O_CREAT,0666);int fd4 = open(FILE_NAME(5),O_WRONLY|O_CREAT,0666);printf("fd:%d\n",fd0); printf("fd:%d\n",fd1);printf("fd:%d\n",fd2);printf("fd:%d\n",fd3);printf("fd:%d\n",fd4);close(fd0);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}

运行结果: 

fd为什么从3开始呢?0、1、2呢?

C程序默认会打开三个标准输入输出流:

  • stdin --> 键盘
  • stdout --> 显示器
  • stderr --> 显示器

所以输入输出还可以采用下面这种方式:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{char buf[1024];ssize_t s = read(0, buf, sizeof(buf));if(s > 0){buf[s] = 0;write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}

打开文件C语言使用的函数接口为FILE* fp = fopen( ),系统调用接口 int fd = open( )。

FILE其实就是一个结构体。上层库函数要访问文件必须调用底层系统调用接口,而系统调用要访问文件必须要通过文件描述符。那么,自然而然,FILE结构体中必定包含文件描述符这样的字段。C语言不仅封装了系统接口,而且也封装了数据类型。

FILE是一个结构体。stdin、stdout、stderr也是FILE类型的结构体,C程序又默认会打开这三个标准输入输出流,所以我们可以预测,文件描述符0、1、2就对应着默认打开的三个输入输出流。

printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
//...

总结:

  • Linux进程默认会打开三个文件描述符fd,标准输入0、标准输出1、标准错误2。
  • C语言不仅在访问文件接口方面封装了系统调用接口,FILE*类型的指针还封装了OS内的文件描述符。

那么文件描述符又为什么是0、1、2、3、4 ... 这样连续的小整数呢?

当我们打开文件时,OS要在内存中创建相应的数据结构来描述目标文件。所以就有了struct file{ }结构体,表示一个已经打开的文件对象,而进程执行open系统调用,必须让进程和文件关联起来。每个进程都有一个指针struct files_struct* files,指向一张表stuct files_struct{ }(文件描述符表),该表最重要的部分就是包含一个struct file* fd_arrar[ ]的指针数组,每个元素都是指向被打开文件的指针。

所以,文件描述符,本质就是该数组的下标,只要拿着文件描述符表,就可以找到对应的文件。

文件描述符分配规则

直接看代码:

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

输出结果fd:3(0、1、2默认被占用)。

如果0或2号文件:

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

关闭0: 

关闭2:

输出结果分别为fd:0、fd:2。

文件描述符的分配规则为:在struct files_struct结构体的数组(也就是文件描述符表struct file * fd_arry[ ])中,从小到大按顺序找到当前没有被使用的最小的下标,作为新文件的文件描述符。

重定向

如果关闭1号文件呢?

int main()
{//close(0);//close(2);close(1);umask(0);int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd < 0){perror("open");return 1;}printf("fd: %d\n",fd); //默认向stdout打印//fprintf(stdout,"fd: %d\n",fd); //向stdout打印close(fd);return 0;
}

运行结果:

本来应该输出到显示器上的fd:1,却写到了文件log.txt中,这种现象叫输出重定向

重定向的本质:上层使用的fd不变,在内核中更改fd对应的struct file*对应指向的地址。

系统调用dup2

系统也提供了支持重定向的接口:

#include<unistd.h>
int dup2 (int oldfd , int newfd);
//makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following。
//注意:是把oldfd文件描述符里的内容,拷贝到newfd文件描述符中。并不是拷贝文件描述符!

常见重定向

>    :输出重定向

>>  :追加重定向

<    :输入重定向

下面使用系统调用dup2分别实现输出重定向、追加重定向、输入重定向。

实现输出重定向:

int main()
{//close(0);//close(2);//close(1);umask(0);int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd < 0){perror("open");return 1;}dup2(fd,1);//输出重定向printf("fd: %d\n",fd);//fprintf(stdout,"fd: %d\n",fd);return 0;
}

可以看到,本来应该输出到显示器上的,此时写到了log.txt中。

实现追加重定向:

int main()
{umask(0);//int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);if(fd < 0){perror("open");return 1;}dup2(fd,1);printf("fd: %d\n",fd);const char* msg = "hello world\n";write(1,msg,strlen(msg));write(1,msg,strlen(msg));close(fd);        return 0;
}

实现输入重定向:

int main()
{umask(0);//int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);int fd = open("log.txt",O_RDONLY);if(fd < 0){perror("open");return 1;}char line[64];dup2(fd,0);//输入重定向while(1){printf("> ");if(fgets(line,sizeof(line)-1,stdin)==NULL) break;//stdin -> 0printf("%s",line);}//dup2(fd,1);//printf("fd: %d\n",fd);//const char* msg = "hello world\n";//write(1,msg,strlen(msg));//write(1,msg,strlen(msg));close(fd);        return 0;
}

没有使用系统调用dup2()之前,从键盘输入:

dup2()输入重定向之后,从log.txt文件输入:

例:myshell中添加重定向功能 

【Linux】C语言实现简易的Linux shell命令行解释器-CSDN博客

在shell命令行上,我们可以这样使用重定向:

那么shell是怎么做到的呢?我们可以在自己实现的myshell中添加重定向功能来模拟实现一下。

关于如何实现简易的Linux shell命令行解释器可以看上篇文章:

【Linux】C语言实现简易的Linux shell命令行解释器-CSDN博客

完整代码: 

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#include<ctype.h>
#include<errno.h>#define NUM 1024
#define OPT_NUM 64#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define trimSpace(start) do{ while(isspace(*start)) start++;   }while(0)char lineCommand[NUM];
char* myargv[OPT_NUM];//指针数组
int lastCode = 0;
int lastSig = 0;int redirType = NONE_REDIR;//重定向类型
char* redirFile = NULL;//重定向目标文件//eg: "ls -a -l > myfile.txt" -> "ls -a -l" "myfile.txt"
//将得到的字符串解析为两部分。顺序扫描/倒序扫描
void commandCheck(char* commands)
{assert(commands);char* start = commands;char* end = commands + strlen(commands);while(start < end){if(*start == '>'){*start = '\0';start++;if(*start == '>') //追加重定向{//"ls -a >>  log.txt"redirType = APPEND_REDIR;start++;}else{//"ls -a >  log.txt"redirType = OUTPUT_REDIR;}trimSpace(start);redirFile = start;break;}else if(*start == '<') //输入重定向{//"ls -a <    log.txt"*start = '\0';start++;//跳过空格trimSpace(start);//填写重定向信息//redirType = INPUT_REDIR;redirFile = start;break;}else{start++;}}
}int main()
{while(1){redirType = NONE_REDIR;redirFile = NULL;errno = 0;//输出提示符printf("用户名@主机号 当前路径# ");fflush(stdout);//获取用户输入,输入的时候会输入\nchar* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);assert(s!=NULL);//清除最后一个\nlineCommand[strlen(lineCommand)-1]=0;//解析字符串//"ls -a -l > myfile" ->  命令:"ls -a -l" 重定向目标文件:"myfile"commandCheck(lineCommand);//printf("test : %s\n",lineCommand);//切割字符串//"ls -a -l" -> "ls" "-a" "-l"myargv[0] = strtok(lineCommand," ");int i  = 1;//给ls加颜色if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0){myargv[i++] = "--color=auto";}while(myargv[i++] = strtok(NULL," "));//如果没有子字符串要切割,strtok返回NULL,而恰好myargv[end]也一定要= NULLif(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0){if(myargv[1] != NULL){chdir(myargv[1]);}continue;}if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0){if(strcmp(myargv[1],"$?") == 0){printf("%d\n",lastCode);}else if(strcmp(myargv[1],"$?") != 0){for(int i = 1; myargv[i]; i++){printf("%s ",myargv[i]);}printf("\n");}else{printf("%s\n",myargv[1]);}continue;//重新循环,不执行fork创建子进程}//测试切割是否成功,条件编译
#ifdef DEBUG for(int i = 0; myargv[i]; i++){printf("myargv[%d]: %s\n",i,myargv[i]);}
#endif//执行命令pid_t id = fork();assert(id != -1);if(id  == 0){switch(redirType){case NONE_REDIR://nothing to dobreak;case INPUT_REDIR:{//打开输入重定向目标文件int fd = open(redirFile,O_RDONLY);if(fd < 0){perror("open");exit(errno);}//输入重定向dup2(fd,0);}break;case OUTPUT_REDIR:case APPEND_REDIR:{umask(0);int flags = O_WRONLY|O_CREAT;if(redirType == OUTPUT_REDIR) flags |= O_TRUNC;else flags |= O_APPEND;int fd = open(redirFile,flags,0666);if(fd < 0){perror("open");exit(errno);}//输入、追加重定向dup2(fd,1);}break;default:printf("bug?\n");break;}execvp(myargv[0],myargv);exit(1);}int status = 0;pid_t ret =  waitpid(id,&status,0);assert(ret > 0);lastCode = ((status>>8) & 0xFF);lastSig = (status & 0x7F);}
}

测试: 

Linux下一切皆文件 

当一个硬件设备或文件被访问时,OS内核会为其分配一个struct file结构体,并将对应的文件操作函数指针指向该设备或文件系统的具体实现。这样,当上层应用程序调用如read()、write()等系统调用时,内核会根据当前的文件描述符找到对应的struct file结构体,并调用其指向的具体函数来直接指向具体方法来实现读写操作。

总结:

通过虚拟的文件系统,上层访问硬件时,就摒弃了底层硬件设备的差异,而统一使用文件接口来进行所有的文件操作。

理解缓冲区问题

看下面这段代码:

#include<stdio.h>
#include<unistd.h>
int main()
{//C接口printf("hello printf\n");fprintf(stdout,"%s","hello fprintf\n");const char* fputsString = "hello fputs\n";fputs(fputsString,stdout);//系统接口const char* writeString = "hello write\n";write(1,writeString,strlen(writeString));//fork();return 0;
}

测试运行: 

当把fork函数删除之后:

可以看到,当fork函数存在时,当向显示器输出的时候,C语言函数接口和系统函数接口都只打印了一次,而当输入重定向到log.txt文件中,C语言函数接口打印了两次,系统函数接口打印了一次。这种现象跟缓冲区有关。

缓冲区存在意义

缓冲区本质就是一段内存!

所以,缓冲区存在的意义就是:节省进程进行数据IO的时间!

缓冲区刷新策略

缓冲区什么时候将数据发送到磁盘呢?

如果有一块数据,一次写入到外设,还是多次少量的写入到外设效率高呢?

实际上,在进行IO的时候,数据从内存拷贝到外设花费时间的时间很少,大部分的时间都是在等外设准备好。所以,将数据一次写入到外设的效率高,因为只进行了一次IO。

缓冲区会结合具体的设备,制定自己的刷新策略:

  • 立即刷新          - - - -   无缓冲
  • 行刷新              - - - -   行缓冲   如:显示器
  • 缓冲区满刷新   - - - -   全缓冲   如:磁盘文件

两种特殊情况:

  1. 用户强制刷新
  2. 进程退出(一搬都要进行缓冲区刷新)

显示器同样是外设,为什么要行刷新呢?

虽然,全缓冲,缓冲区满了之后,再将缓冲区里的数据一次刷新到外设中的效率是最高的,但是,显示器是用给人来看的,按行显示,更符合人的阅读习惯。

缓冲区存放区域

那么缓冲区在哪里呢?由谁提供的呢?

删除fork之后:

根据此现象,可以得知,缓冲区一定不在OS内核中!因为,如果在内核中,write也要被打印两次!(因为库函数fwitre底层调用的是系统调用write)

所以,我们上面所谈论的缓冲区,都指的是用户级语言层面(这里C语言)给我们提供的缓冲区。(当然。OS也会提供内核级缓冲区,不过,我们不讨论)

当我们进行C语言输入输出的时候,默认会打开stdin、stdout、stderr,他们的类型都是FILE * 或者打开文件fopen,进行文件读写fwrite、fread时,都必须要传入FILE*,FILE是一个结构体,FILE中就封装了文件描述符和缓冲区!!!也就是说,我们在C程序上进行所谓的输入输出等操作,最终都会先将数据写进FILE*指向的FILE结构体内的缓冲区中!

所以,当我们要强制刷新缓冲区,fflush(文件指针FILE*),或者关闭文件fclose(文件指针FILE*),都要传入FILE*,原因就是FILE结构体中包含了缓冲区!

如果有兴趣,也可以看一下FILE结构体:

/usr/include/stdio.h

/usr/include/libio.h

基于上述对缓冲区的理解,我们现在再来解释一下,一开始代码fork之后出现的现象。

int main()
{//C接口printf("hello printf\n");fprintf(stdout,"%s","hello fprintf\n");const char* fputsString = "hello fputs\n";fputs(fputsString,stdout);//系统接口const char* writeString = "hello write\n";write(1,writeString,strlen(writeString));//代码结束之前,进行创建子进程//1、如果我们没有进行输出重定向> ,看到了4条消息//stdout 默认使用的是行刷新,在进程fork之前,3条函数已经将数据进行打印输出到显示器上(外设),FILE内部,进程内部不存在对应的数据了。//2、如果我们进程了输出重定向> ,写入文件不再是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条C显示函数,虽然带了\n,但是不足以//将stdout缓冲区写满!数据并没有被刷新。//执行fork的时候,stdout属于父进程,创建子进程时,紧接着就是进程退出,谁先退出,一定要进行缓冲区刷新(就是修改)//所以,此时,会发生写时拷贝!数据最终会显示两份。//3、write为什么没有呢?上面的过程都和write无关,write是系统调用,write访问文件没有FILE结构体,而用的是fd,也就没有C提供的缓冲区。fork();return 0;
}

下面我们写一部分代码使用系统调用来封装一下C库的文件操作接口,再理解一下缓冲区以及它的刷新策略是怎样的。

DEMO

myStdio.h:

#pragma once#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 1024
#define SYNC_NOW    1
#define SYNC_LINE   2
#define SYNC_FULL   4typedef struct _FILE{int flags; //刷新方式int fileno;int cap; //buffer的总容量int size; //buffer当前的使用量char buffer[SIZE];
}FILE_;FILE_ *fopen_(const char *path_name, const char *mode);
void fwrite_(const void *ptr, int num, FILE_ *fp);
void fclose_(FILE_ * fp);
void fflush_(FILE_ *fp);

myStdio.c:

#include "myStdio.h"FILE_ *fopen_(const char *path_name, const char *mode)
{int flags = 0;int defaultMode=0666;if(strcmp(mode, "r") == 0){flags |= O_RDONLY;}else if(strcmp(mode, "w") == 0){flags |= (O_WRONLY | O_CREAT |O_TRUNC);}else if(strcmp(mode, "a") == 0){flags |= (O_WRONLY | O_CREAT |O_APPEND);}else{//TODO//...}int fd = 0;if(flags & O_RDONLY) fd = open(path_name, flags);else fd = open(path_name, flags, defaultMode);if(fd < 0){const char *err = strerror(errno);write(2, err, strlen(err));return NULL; // 也就是C为什么打开文件失败会返回NULL}FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));assert(fp);fp->flags = SYNC_LINE; //演示。默认设置成为行刷新fp->fileno = fd;fp->cap = SIZE;fp->size = 0;memset(fp->buffer, 0 , SIZE);return fp; // 也就是为什么C打开一个文件,就会返回一个FILE *指针
}void fwrite_(const void *ptr, int num, FILE_ *fp)
{// 1. 写入到缓冲区中memcpy(fp->buffer+fp->size, ptr, num); //这里我们不考虑缓冲区溢出的问题fp->size += num;// 2. 判断是否刷新if(fp->flags & SYNC_NOW){write(fp->fileno, fp->buffer, fp->size);fp->size = 0; //清空缓冲区}else if(fp->flags & SYNC_FULL){if(fp->size == fp->cap){write(fp->fileno, fp->buffer, fp->size);fp->size = 0;}}else if(fp->flags & SYNC_LINE){if(fp->buffer[fp->size-1] == '\n') // abcd\nefg , 不考虑{write(fp->fileno, fp->buffer, fp->size);fp->size = 0;}}else{//....}
}void fflush_(FILE_ *fp)
{if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);//TODO...//fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!//fp->size = 0;
}void fclose_(FILE_ * fp)
{fflush_(fp);close(fp->fileno);
}

 main.c:

#include "myStdio.h"
#include <stdio.h>int main()
{FILE_ *fp = fopen_("./log.txt", "w");if(fp == NULL){return 1;}int cnt = 10;    const char *msg = "hello world ";//const char *msg = "hello world ";while(1){fwrite_(msg, strlen(msg), fp);//fflush_(fp);//将数据强制刷新到外设sleep(1);printf("count: %d\n", cnt);if(cnt == 5) fflush_(fp);cnt--;if(cnt == 0) break;}fclose_(fp);return 0;
}

写一个监控脚本监视运行一下,看到的现象如下: 

缓冲区与OS的关系

缓冲区(用户级)和操作系统OS有什么关系呢?

如果OS将数据从内核缓冲区刷新到外设的时候,OS宕机了呢?

会发生数据丢失。避免方法是,用户层可以直接强制OS将对应的文件内核缓冲区中的数据同步到磁盘。

了解磁盘

上面我们所谈论的文件是被打开的文件,如果一个文件没有被打开呢?该如何被OS管理?

没有被打开的文件,只能静静的在磁盘上放着。磁盘上有大量的文件,也是必须要被“静态”管理的,方便我们随时打开。

磁盘的物理结构

磁盘是计算中唯一的一个机械结构!硬盘是一个机械结构,同时也是外设,所以硬盘访问速度会很慢。但是在企业端,磁盘依旧是存储的主流。

磁盘的存储结构

磁盘在寻址的时候,基本单位不是bit,也不是byte,而是扇区(512byte)

如何在单面上,定位一个扇区?

如何在磁盘中,定位任何一个扇区?

磁盘的逻辑结构

为什么OS要进行将磁盘逻辑抽象,使用LAB线性地址访问扇区呢?直接使用磁盘的物理地址CHS不可以吗?

  • 便于管理。
  • 不想让OS的代码和硬件强耦合。

虽然磁盘访问的基本单位为扇区(512byte),但是依旧很小!磁头摆动,盘片旋转找对应的扇区的效率略低,这也就是为什么进程要访问磁盘,大部分的时间都是在等磁盘“准备好”。

所以OS系统内的文件系统会定制的进行多个扇区的读取,一次读取1KB或者2KB或者4KB(常用)为基本单位。哪怕只想读取或修改1个bit位,也必须将4KB加载到内存,进行读取或修改,必要的时候,再写回磁盘。以空间换时间的做法!

理解文件系统 

一个磁盘空间大小约几百个GB,例如500GB,那么如何管理这个磁盘呢?

500GB太大了,不好管理,所以先将磁盘进行分区,例如分为100GB、100GB、150GB、150GB四个分区。只需要将100GB的分区管理好,其余分区同样的方法也可以管理好。但是100GB同样很大,相对来说也不好管理,所以再将每个分区进行分组,例如100GB的分区分为20个5GB的分组。这时候只需要将5GB的空间管理好,同样的方法就可以管理好其余分组,同样可以管理好每个分区,进而能够管理好一个次磁盘。(分治的思想)

文件系统是如何管理一个分组的呢?

Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs-b选项可以设定block大小为102420484096字节。而上图中启动块(Boot Block)的大小是确定的。

  • Super Block:保存的是整个文件系统的信息。
文件 = 文件属性 + 文件内容。
Linux下文件属性和文件内容是分批存储的。
  • 文件属性存在inode块中,inode是固定大小。几乎一个文件,一个inode。这些inode为了区分彼此,每一个inode都有自己的ID编号!inode块中包含了几乎这个文件的所有属性,文件名并不在inode中存储。
  • data block里存放文件内容,data block随着应用类型的变化,大小也在变化。

如何在一个分组里面保存众多的文件呢?

  • inode Table:保存了分组内部所有的可用(已经使用+没有使用)inode。

当创建文件的时候,首先就需要区inode table中查找找没有使用的inode,再将文件属性填到inode中。

  • Data Blocks:保存的是分组内部所有文件的数据块(4kb大小为单位),哪些块是属于哪些文件的,也有办法来标识。

当创建文件的时候,然后需要在data block中查找没有被使用的数据块,进而申请数据块,就可以将文件内容写进数据块中。

在创建文件的时候无论是查找没有使用的inode,还是查找没有使用的data block。都需要进行查找操作。

  • inode Bitmap:inode对应的位图。每个bit位表示一个inode是否空闲可用(0/1)。
  • block Bitmap:数据块对应的位图,记录着data blocks中哪个数据块被占用或没有被占用。
  • Group Descriptor Table:块组描述表,对应分组的宏观属性。

例如:一共有多少个分组,每个分组是多大,一共多少个inode/data block,使用了多少个inode/data block,哪些没有被使用等等。

查找一个文件的时候,统一使用的是inode编号。(inode可以跨分组,但是不能跨分区)

首先在inode bitmap中查找inode编号对应的bit位是否为1,为1证明文件是存在的,再去inode table中查找对应的inode,就可以将文件属性查找出来,那么文件内容呢?去data block中查找,data block中有大量的数据块,怎么知道哪个数据块和我要查找的文件有关呢?

inode块中不仅包含着文件的属性,还包含了一个存放着数据块编号的数组。

创建文件,首先在inode map中将inode编号对应的bit位置为1,再去inode table找到对应的inode中将文件属性填写进去,然后将文件数据写到未使用的data block中,再将inode和data block之间建立联系,文件就创建成功了。

删除文件,只需要在inode map中将文件对应的inode编号映射的bit位置为0即可。删除之后,如果不做其他操作,如创建新文件,只要知道删除文件的inode编号,在inode map中将删除文件的indoe编号映射的bit位置1,还可以恢复文件。

在Linux下查找文件,我们也并没有使用inode编号啊!使用的是文件名!

普通文件是一个文件,任何一个文件一定在某一个目录里面。目录也是一个文件,也有自己的属性和内容,即同样有inode和数据块,那么目录的数据块放的是什么呢?

目录的数据块放的就是,当前目录下的文件名和inode的映射关系!

像ls查文件的时候,第一步一定是查找当前目录对应的数据块,将文件的文件名和inode提取出来...所以同一个目录下不可以存在文件名相同的文件!

  • 这也就是为什么创建文件的时候为什么一定要有写权限,因为一定要在当前目录的数据块里去写文件名和inode的映射关系!
  • 这也就是查看文件的时候为什么一定要有读权限,因为拿到的是文件名,必须得访问目录的数据块,根据文件名去找对应文件的inode!

软硬链接

一个文件对应一个inode,一个inode对应一个文件。

软硬链接的区别

  • 是否是具有独立的inode!
  • 软链接具有独立的inode,可以被当作独立的文件来看待。
  • 硬链接没有独立的inode。

硬链接

可以看到硬链接,与原文件有同样的inode以及属性和内容。

所以建立硬链接,根本就没有创建新文件!因为没有给硬链接分配独立的inode。既然没有创建文件,一定没有自己的属性集合和内容集合,用的一定是别人的inode以及属性和内容!

建立硬链接本质就是在指定的路径下,新增文件名和inode编号的映射关系。

所以当删除文件的时候,其实做了两件事情,1、在目录中将对应的记录删除。2、将硬链接数-1,如果为0,则将文件对应的磁盘空间释放,就彻底的删除了文件。

软链接

在这种情况下,当把软链接链接的目标文件删除之后,软链接就失效了,但是事实上,这个链接的目标文件还是存在的。

所以软链接链接文件并没有使用目标文件的inode来链接文件的,而使用的是目标文件的文件名!

硬链接是通过inode引用另外一个文件,软链接是通过文件名引用另外一个文件。

当把目标文件删除之后,当前目录也就没有对应的目标文件的文件名了,也就是说,软链接有查找这个目标文件的方式。我们在查找文件的时候,使用的是路径。软链接是一个独立的文件,具有独立的inode,也有数据块,它的数据块里面保存的是所指向目标文件的路径。

当把软链接删除之后,并不会影响目标文件。当把软链接链接的目标文件删除之后,软链接就失效了。

所以,在Linux下的软链接就相当于在Windows下的快捷方式!

软链接的应用

快速的访问某个文件或目录。

此时,我要执行mytest应用程序。

这样执行要带很长的一段路径,是不是太麻烦了。我们就可以给mytest建立一个软链接。

硬链接的应用

为什么普通文件的硬链接数是1呢?

因为一个普通文件,本身自己的文件名和自己的inode具有一个映射关系!

为什么这个空目录的硬链接数是2呢?

因为任何一个目录里都有两个隐藏目录,分别为“.”目录和“..”目录。

我们发现,empty目录里隐藏的“.”目录的inode和empty目录的inode相同!“.”目录也叫做当前目录。他们的inode相同,也就意味着,“.”目录就是当前目录empty的一个硬链接。

我们再在空目录empty里建立一个dir目录,再回到上级路径,发现empty的硬链接数变成了3:

原因就是empty目录里的dir目录里隐藏的还有一个“..”目录,它的inode和上级目录empty目录的inode相同,也叫做上级目录。

这也就是为什么“cd ..”能回到上级目录的原因:“..”目录,是上级目录的一个硬链接!

Linux为什么不允许普通用户给目录创建硬链接呢?

  • 防止目录循环引用:

  1. 硬链接与原文件共享同一个inode号。如果对目录进行硬链接,就可能有多个等效的入口点指向同一个目录,这样会导致目录树出现环形结构。
  2. 在这种环形结构中,像文件遍历这样的操作可能会陷入无限循环,进一步可能导致文件系统的损坏。例如,如果有两个目录A和B,它们之间存在循环链接,那么当尝试遍历A目录时,就可能导致无限循环。
  • 保护文件系统的结构完整性:

  1. 允许目录硬链接可能会使inode系统遇到复杂的父子关系以及所有权问题,因为多个父目录可能会指向同一个inode号。
  2. 这将导致文件系统的维护人员在处理删除目录、重命名目录等操作时,无法有效地处理这种inode的多父问题。
  • 简化文件系统的设计

  1. 允许硬链接到目录将引入复杂的处理逻辑,以保证文件系统的一致性,例如更新目录下文件的链接数、处理异常情况等。
  2. 简单的设计有助于减少bug,提高文件系统的稳定性。

促进了文件系统的健康性和易于维护性。

但是,“.”目录、“..”目录,不就是分别是当前目录和上级目录的硬链接吗?

这两个是特殊情况,是Linux操作系统创建的,但是不允许用户给目录创建硬链接。

文件的三个时间

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

注意:

由于我们大多数操作文件的时候都是在读文件,如果每一次读,都要更新文件的访问时间,那么访问就太频繁了,数据在磁盘上,数据更新频繁,就会影响Linux的效率。所以Acess时间不会实时更新。修改文件的内容,同时也会影响文件的属性,比如修改数据、文件大小同时也会发生改变。

相关文章:

基础I/O -> 如何谈文件与文件系统?

文件的基础理解 空文件也要在磁盘上占据空间。文件 文件内容文件属性。文件操作 对内容的操作 对属性的操作或者是对内容和属性的操作。标定一个文件&#xff0c;必须使用&#xff1a;文件路径 文件名&#xff08;具有唯一性&#xff09;。如果没有指明对应的文件路径&…...

安装配置git

1、下载git:https://git-scm.com 2、配置git,进入git bash #配置用户名 git config-global user.name "petter7226" #配置邮箱 git config-global user.email "894266014qq.com" 3、配置ssh免密登录 可参考这个文档 https://gitee.com/help/article…...

cad学习 day4 day5

平面布置 客厅平面布置 端景柜: 一般玄关区域、走廊、过道尽头做造型端景柜,可以组展示、柜体、艺术品陈设窗帘盒 200mm 电动窗帘盒 250mm, 镜像命令: MI 做对称使用沙发: 归类FF - 移动家私木门: 归类FF - 平面内门地台床: 使用bo快速生成, 绘制后外围偏移O 50mm pl连接作为灯…...

Java的基础概念(一)

一、注释 **注意&#xff01;&#xff1a;**注释内容不会参与编译和运行&#xff0c;仅仅是对代码的解释说明。 Java支持单行注释、多行注释、文档注释。 单行注释 以//开头 &#xff0c;格式【 //注释内容 】 例子如下&#xff1a; 多行注释 格式【 / 注释内容 / 】 例…...

Qt5 cmake引用private头文件

Qt5 cmake引用private头文件 如何引用Qt的qzipreader_p.h头文件 、xlsxzipreader_p.h头文件 使用 target_include_directories target_include_directories(TestQtXlsx PRIVATE${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS}${Qt${QT_VERSION_MAJOR}Xlsx_PRIVATE_INCLUD…...

重温设计模式--代理、中介者、适配器模式的异同

文章目录 1、相同点2、不同点 1、相同点 目的都是为了更好地处理对象之间的关系&#xff1a;这三种模式都是在软件设计中用于处理对象之间的关联和交互&#xff0c;以达到优化系统结构、增强可维护性等目的。它们都在一定程度上隐藏了对象之间的某些细节或者复杂性&#xff0c…...

拦截器魔法:Spring MVC中的防重放守护者

目录 简介HandlerInterceptorAdapter vs HandlerInterceptor创建一个防重放拦截器注册拦截器路径模式匹配适配器模式的魅力总结 简介 在构建安全可靠的Web应用程序时&#xff0c;防止请求重放攻击是一项关键任务。当用户或系统发出的请求被恶意第三方捕获并重复发送给服务器…...

MVC 发布

关于MVC发布&#xff0c;我为您整理了以下信息&#xff1a; SpringMVC发布&#xff1a;SpringMVC是Spring框架的一部分&#xff0c;它基于MVC架构&#xff0c;具有解耦合、轻量级和对注解的广泛支持等优点。发布SpringMVC项目通常涉及配置中央调度器、编写控制器类和设置视图解…...

AI在传统周公解梦中的技术实践与应用

本文深入探讨了人工智能在传统周公解梦领域的技术实践与应用。首先介绍了传统周公解梦的背景与局限性&#xff0c;随后详细阐述了 AI 技术如何应用于梦境数据的采集、整理与分析&#xff0c;包括自然语言处理技术对梦境描述的理解&#xff0c;机器学习算法构建解梦模型以及深度…...

Go怎么做性能优化工具篇之基准测试

一、什么是基准测试&#xff08;Benchmark&#xff09; 在 Go 中&#xff0c;基准测试是通过创建以 Benchmark 开头的函数&#xff0c;并接收一个 *testing.B 类型的参数来实现的。testing.B 提供了控制基准测试执行的接口&#xff0c;比如设置测试执行的次数、记录每次执行的…...

社区管理系统:实现社区信息数字化管理的实践

3.1可行性分析 开发者在进行开发系统之前&#xff0c;都需要进行可行性分析&#xff0c;保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该社区管理系统所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识&#xff0c;同…...

Java设计模式 —— 【结构型模式】外观模式详解

文章目录 概述结构案例实现优缺点 概述 外观模式又名门面模式&#xff0c;是一种通过为多个复杂的子系统提供一个一致的接口&#xff0c;而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口&#xff0c;外部应用程序不用关心内部子系统的具体的细节&#xff0c;这…...

基于 Python 的二手电子设备交易平台

标题:基于 Python 的二手电子设备交易平台 内容:1.摘要 基于 Python 的二手电子设备交易平台的摘要&#xff1a;本文介绍了一个基于 Python 的二手电子设备交易平台。该平台旨在为用户提供一个便捷、安全的交易环境&#xff0c;促进二手电子设备的流通和再利用。文章首先介绍了…...

Vue.js组件开发-插槽(Slots)的使用

插槽&#xff08;Slots&#xff09;是 Vue.js 中一个非常强大的特性&#xff0c;允许在组件内部指定可重用的内容片段&#xff0c;这些内容片段可以由父组件动态地填充。它能够让父组件决定组件内部应该渲染什么内容。 默认插槽 默认插槽是最简单的插槽类型。在子组件的模板中…...

python:面向对象简单示例

编写 se2ball.py 如下 # -*- coding: utf-8 -*- """ python 面向对象简单示例 """ import randomclass Random_ball(object):""" 随机选双色球 """def __init__(self, reds33, blues16):""" 初始…...

Stealthy Attack on Large Language Model based Recommendation

传统RS依赖id信息进行推荐&#xff0c;攻击&#xff1a;生成虚假用户&#xff0c;这些用户对特定目标物体给于高评价&#xff0c;从而影响模型的训练。 基于llm的RS&#xff1a;llm利用语义理解&#xff0c;将用户兴趣转化为语义向量&#xff0c;通过计算用户兴趣向量与物品向…...

云原生周刊:利用 eBPF 增强 K8s

开源项目推荐 Slurm-operator Slurm-operator 是一个高效可扩展的框架&#xff0c;用于在 K8s 环境中部署和运行 Slurm 工作负载。 它结合了 Slurm 的可靠性和 Kubernetes 的灵活性&#xff0c;支持快速部署 Slurm 集群、动态扩展 HPC 工作负载&#xff0c;并提供高度灵活的定…...

Ubuntu20.04安装openMVS<成功>.colmap<成功>和openMVG<失败(已成功)>

一、安装openMVS 官方文档&#xff1a;https://github.com/cdcseacave/openMVS/wiki/Building sudo apt-get -y install git mercurial cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev eigen git clone https://gitlab.com/libeigen/eigen --branch 3.4 mkdi…...

第22天:信息收集-Web应用各语言框架安全组件联动系统数据特征人工分析识别项目

#知识点 1、信息收集-Web应用-开发框架-识别安全 2、信息收集-Web应用-安全组件-特征分析 一、ICO图标&#xff1a; 1、某个应用系统的标示&#xff0c;如若依系统有自己特点的图标&#xff1b;一旦该系统出问题&#xff0c;使用该系统的网站都会受到影响&#xff1b; 2、某个公…...

Sourcegraph 概述

Sourcegraph 报告 Sourcegraph 是一款强大的代码搜索和智能导航工具&#xff0c;专为大型代码库、分布式系统和跨多个仓库的开发环境设计。它能显著提高开发者对复杂系统的理解和维护效率&#xff0c;帮助团队在庞大的代码库中快速找到关键信息。本文将详细讲解 Sourcegraph 的…...

Redis常见阻塞原因总结

O(n) 命令 Redis 中的大部分命令都是 O(1)时间复杂度&#xff0c;但也有少部分 O(n) 时间复杂度的命令&#xff0c;例如&#xff1a; KEYS *&#xff1a;会返回所有符合规则的 key。HGETALL&#xff1a;会返回一个 Hash 中所有的键值对。LRANGE&#xff1a;会返回 List 中指定…...

MyBatis执行完sql后,返回的数值代表的意思

在 MyBatis 中&#xff0c;常见的数据库操作方法返回的数值&#xff08;如 insert、update 和 delete&#xff09;代表了 受影响的行数&#xff0c;即数据库操作成功后&#xff0c;实际修改&#xff08;插入、更新或删除&#xff09;的记录数量。每个方法返回的数值有不同的含义…...

MySQL超详细安装配置教程(亲测有效)

目录 1.下载mysql 2.环境配置 3.安装mysql ​4.navicat工具下载与连接 ​5总结 1.下载mysql mysql下载--MySQL &#xff1a;&#xff1a; 下载 MySQL 社区服务器 下载的时候这里直接逃过就行 我这里的版本是最新的mysql8.0.37 下载完成之后,将压缩包进行解压 这里我建议大…...

MacroSan 2500_24A配置

双控制器电源同时按下,切记/切记/切记 默认信息 默认地址:192.168.0.210 输入ODSP授权后设置密码## 配置端口 物理资源–>设备–>网口–>eth-1:0:0或eth-2:0:0 创建存储池 存储资源–>存储池 介质类型:混合(支持机械及SSD)全闪(仅支持SSD) RAID类型:CRAID-P(基于磁…...

vue3+vite一个IP对站点名称的前端curd更新-会议系统优化

vue3-tailwind-todo https://github.com/kgrg/vue3-tailwind-todo 基于这个项目,把ip到sta的映射做了前端管理. 核心代码是存储和获得的接口,需要flask提供. def redis2ipdic():global ipdicipdic.clear()tmdiccl.hgetall(IPDIC_KEY)for k in tmdic.keys():ipdic[k.decode() …...

GraalVM完全指南:云原生时代下使用GraalVM将Spring Boot 3应用转换为高效Linux可执行文件

一、前言 在现代软件开发中,启动速度和资源利用率常常是衡量应用性能的关键指标。对于基于Spring Boot的应用来说,虽然它们易于开发和部署,但JVM的启动时间有时会成为一个瓶颈。本文介绍如何使用GraalVM将Spring Boot 3应用编译成原生Linux可执行文件,从而显著提高启动速度…...

《Swift 字面量》

《Swift 字面量》 介绍 在 Swift 编程语言中&#xff0c;字面量是一种表示源代码中固定值的表达方式。字面量可以直接表示数字、字符串、布尔值等基本数据类型&#xff0c;为编程提供了简洁和直观的方式。Swift 支持多种类型的字面量&#xff0c;包括整数字面量、浮点数字面量…...

国标GB28181平台EasyGBS在安防视频监控中的信号传输(电源/视频/音频)特性及差异

在现代安防视频监控系统中&#xff0c;国标GB28181协议作为公共安全视频监控联网系统的国家标准&#xff0c;该协议不仅规范了视频监控系统的信息传输、交换和控制技术要求&#xff0c;还为不同厂商设备之间的互联互通提供了统一的框架。EasyGBS平台基于GB28181协议&#xff0c…...

AlipayHK支付宝HK接入-商户收款(PHP)

一打开支付宝国际版 二、点开商户服务 三、下载源码...

CS!GO

CS&#xff08;computer science&#xff09;计算机科学&#xff0c;说实话&#xff0c;不是找工作面试&#xff0c;这些题谁会背啊&#xff0c;反正我不行&#xff0c;一问三不知。 咱也不管这些&#xff0c;这个系列&#xff0c;可能会时不时的给出一些计网和操作系统相关的东…...

全栈开发中的技术选型决策:快速上线与扩展的平衡

文章目录 摘要引言技术选型的重要性技术选型的关键考虑点项目需求团队技能技术生态性能与扩展性成本与复杂性 基于 Spring Boot 和 Vue.js 的全栈架构后端代码&#xff1a;Spring Boot 示例代码详解&#xff1a;运行原理&#xff1a; 前端代码&#xff1a;Vue.js 示例代码详解&…...

软件著作权申请教程(超详细)(2024新版)软著申请

目录 一、注册账号与实名登记 二、材料准备 三、申请步骤 1.办理身份 2.软件申请信息 3.软件开发信息 4.软件功能与特点 5.填报完成 一、注册账号与实名登记 首先我们需要在官网里面注册一个账号&#xff0c;并且完成实名认证&#xff0c;一般是注册【个人】的身份。中…...

【强化学习】Stable-Baselines3学习笔记

【强化学习】Stable-Baselines3学习笔记 Stable-Baselines3是什么安装ExampleReinforcement Learning Tips and TricksVecEnv相关 Stable-Baselines3是什么 Stable Baselines3&#xff08;简称SB3&#xff09;是一套基于PyTorch实现的强化学习算法的可靠工具集旨在为研究社区和…...

sqoop的参数有哪些?

Sqoop 是一款用于在 Hadoop 与关系型数据库之间进行数据传输的工具&#xff0c;它有很多参数&#xff0c;可分为通用参数、导入参数和导出参数等&#xff0c;以下是一些常见的参数介绍&#xff1a; 通用参数 --connect 说明&#xff1a;指定要连接的关系型数据库的 JDBC URL。…...

16×16LED点阵字符滚动显示-基于译码器与移位寄存器(设计报告+仿真+单片机源程序)

资料下载地址&#xff1a;​1616LED点阵字符滚动显示-基于译码器与移位寄存器(设计报告仿真单片机源程序)​ 1、功能介绍 设计1616点阵LED显示器的驱动电路&#xff0c;并编写程序实现在1616点阵LED显示器上的字符滚动显示。1616点阵LED显示器可由4块88点阵LED显示器构成。可采…...

后门移除方法和后门检测

1、后门移除方法 1.1、Fine-Pruning方法 [48]利用了这样一个观察结果&#xff1a;后门攻击会利用神经网络中的空闲容量。该方法通过消除在干净输入下处于休眠状态的神经元来减小网络的规模&#xff0c;然后对网络进行微调(使用干净数据继续训练)&#xff0c;以增强对抗修剪感…...

网络安全检测

实验目的与要求 (1) 帮助学生掌握木马和入侵的防护和检测方法、提高学习能力、应用能力和解决实际问题的能力。 (2) 要求学生掌握方法, 学会应用软件的安装和使用方法, 并能将应用结果展示出来。 实验原理与内容 入侵检测是通过对计算机网络或计算机系统中若干关键点收集信…...

FPGA(一)verilog语句基础

Verilog 是一种硬件描述语言&#xff08;HDL&#xff09;&#xff0c;常用于数字电路的设计、模拟和验证&#xff0c;特别是用于 FPGA 和 ASIC 的设计。Verilog 让设计者能够描述和模拟硬件系统的行为和结构&#xff0c;最终将其转化为硬件电路。 一、模块结构 Verilog 中的设计…...

istio配置重复的svc报错

现象&#xff1a; 两个vs中配置了同一个svc地址&#xff0c;导致其中的一个vs路由配置不生效&#xff0c;看到istiod服务的报错duplicate domain from service 解决&#xff1a; istiod服务报错日志 2024-11-13T14:54:50.39418167508:00 "pilot_vservice_dup_doma…...

springboot473基于web的物流管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统物流管理系统信息管理难度大&#xff0c;容错率低&#x…...

面试题整理7----Nginx的access.log被删除在不影响应用的情况下恢复日志的写入

面试题整理7----Nginx的access.log被删除在不影响应用的情况下恢复日志的写入 1. 问题2. 问题复现2. 释放空间2.1 确定nginx的pid2.2 确定文件描述符号2.3 清空文件 3. 恢复access.log的写入4. 后续改善 1. 问题 这是一个非常常见的故障处理. 应用负责人发现服务器磁盘满了,经…...

ip_forward函数

ip_forward 函数是 Linux 内核中用于处理 IP 数据包转发的重要函数。它负责将数据包从一个网络接口转发到另一个网络接口。以下是这个函数的一些关键点和工作流程的概述: 1. **数据包接收**:当一个数据包到达网络设备(如以太网卡)时,内核会首先接收到这个数据包。 2. **路…...

华院计算参与项目再次被《新闻联播》报道

12月17日&#xff0c;央视《新闻联播》播出我国推进乡村振兴取得积极进展。其中&#xff0c;华院计算参与的江西省防止返贫监测帮扶大数据系统被报道&#xff0c;该系统实现了由原来的“人找人”向“数据找人”的转变&#xff0c;有效提升监测帮扶及时性和有效性&#xff0c;守…...

postman关联接口用于登录(验证码会变情况)

目录 一、介绍 二、操作步骤 (一)Fiddler抓取到登录信息 (二)postman发送请求 新建请求一&#xff1a;登录值请求 (三)易变值赋值固定住 新建请求二&#xff1a;易变值验证码(uuid)请求 切换到请求一里面进行赋值绑定 一、介绍 接口有两种形式&#xff0c;一种是单…...

《探秘 Qt Creator Manual 4.11.1》

《探秘 Qt Creator Manual 4.11.1》 一、Qt Creator 4.11.1 概述二、功能特性全解析&#xff08;一&#xff09;跨平台能力展示&#xff08;二&#xff09;代码编辑优势&#xff08;三&#xff09;版本控制集成&#xff08;四&#xff09;特定 Qt 功能呈现&#xff08;五&#…...

linux(ubuntu )卡死怎么强制重启

&#xff08;公司的 ubuntu 跑个用例经常卡死&#xff09; 如果其他快捷键都没有反应&#xff0c;且不想按电源键进行硬重启&#xff0c;可以尝试以下方法&#xff1a; 1. 使用 Magic SysRq 键 Magic SysRq 键可以在系统完全卡死的情况下&#xff0c;执行一些强制操作来重启…...

优化程序中的数据:从数组到代数

前言 我们往往都希望优化我们的程序&#xff0c;使之达到一个更好的效果&#xff0c;程序优化的一个重点就是速度&#xff0c;加快速度的一个好办法就是使用并行技术&#xff0c;但是&#xff0c;并行时我们要考虑必须串行执行的任务&#xff0c;也就是有依赖关系的任务&#…...

图像配准有哪些技术?

目录 图像配准技术 1.基于特征的图像配准 2.基于强度的图像配准 3.基于模型的图像配准 4.基于学习的图像配准 5.混合方法 图像配准的应用 图像配准技术入门 常见问题解答 图像配准是计算机视觉和医学成像中的一项关键技术&#xff0c;用于将多幅图像对齐到一个共同的坐…...

第五节:GLM-4v-9b模型model加载源码解读(模型相关参数方法解读)

文章目录 前言一、GLM-4v-9b模型model加载源码解读1、GLM-4v-9b模型model加载主函数源码2、GLM-4v-9b模型model加载源码源码解读3、GLM-4v-9b自定义模型类源码解读 二、基于GLM-4v-9b模型获取模型输入参数等内容源码解读(from_pretrained-->huggingface)1、from_pretrained函…...

Unity3D仿星露谷物语开发7之事件创建动画

1、目标 掌握事件通知的Publisher - Subscriber设计模式&#xff0c;并通过事件通知触发动画。 2、发布者/订阅者模式 首先&#xff0c;定义事件Event 然后&#xff0c;Publisher触发事件 最后&#xff0c;Subscriber订阅事件并进行处理 &#xff08;1&#xff09;创建动作…...