【Linux】内存文件系统的I/O、重定向
文章目录
- 1. 系统中的文件
- 2. 回顾C中的文件接口
- 3. 文件类的系统调用
- 3.1 open
- 3.2 文件描述符
- 4. IO的基本过程
- 5.重定向
- 5.1 引入重定向
- 5.2 系统中的重定向接口
- 6. 缓冲区问题
- 7. 简单版shell的实现

1. 系统中的文件
在学习完Linux权限后,我们清楚的知道:文件 = 文件内容 + 文件属性
,这和进程就很像。所以我们文件的所有操作无非就是对文件内容或属性进行的。
无论何种语言,在访问文件之前,我们都必须打开文件,例如C语言中的fopen。但是我们访问前为什么要打开它呢?
文件在没有被访问的时候,它存储在磁盘上。学习完进程后,我们清楚的知道,访问文件其实是进程在访问。
在下面的代码中,当程序跑起来,进程执行到fopen时,文件才被打开。
进程是在内存中的,最终要被CPU执行;当进程执行文件操作时,但文件在磁盘上,根据冯诺依曼体系,CPU不能访问磁盘。
所以,文件也必须加载到内存中,否则进程访问不了(因为CPU访问不了),所以,打开文件的本质是:将文件加载到内存中。
因为文件 = 内容 + 属性,所以加载时,加载的就是文件的内容或属性。
我们知道一个进程可以打开多个文件,多个进程就可以打开更多的文件。
既然操作系统要管理进程,那么操作系统也要管理加载到内存中的文件(文件是谁加载的?什么时候加载的?要不要释放?…),操作系统管理加载到内存中文件的方式:先描述,再组织!
因此,我们研究打开的文件,是在研究:进程和文件的关系!
文件就可以分为:
- 被打开的文件 - -内存中
- 未被打开的文件 - - 磁盘中
2. 回顾C中的文件接口
在C语言中,我们学习了很多的文件相关接口,例如:
在系统文件中,我们重点关注 w 与 a
执行任何一个程序,进程默认会打开三个输入输出流,分别是stdin, stdout, stderr。
仔细观察发现,这三个流的类型都是FILE*,而且fopen的返回值也是FILE*。
在任何一个语言中,都会提供类似的三个流,既然语言都支持,那这本身并不属于语言的特性,而是属于操作系统所做的工作。
所以各种语言提供的访问文件(键盘、显示器)的接口,本质都是封装的文件类的系统调用接口。
3. 文件类的系统调用
3.1 open
- 参数
第二个参数是标记位,上方列出了常用的选项,它们本质上都是宏,可以组合使用。
但是以前使用多个宏时,我们需要传递多个参数,为什么这里一个int flag就能解决呢?- - - 位图,32个比特位,可以存储32个选项
。如果是这样的话,那上方列出的宏选项,它们应该只有一个比特位为1。
举个栗子:
上方代码就通过检查flag对应位置上是0还是1,就可以实现传递多个选项的效果。
下面我们使用一下系统调用open
运行后发现,文件不存在时确实创建了,但是文件的权限为什么是错乱的呢?
因为创建文件和起始权限是操作系统的两个分支功能,操作系统不会在系统调用上让你创建文件时就按照默认权限来,它也没有这个权力。你要告诉系统调用,文件的默认起始权限是什么。
我们需要通过第三个参数mode指定,这不就是权限那里的chmod吗?
权限不是666是因为有 umask,去除umask可在函数中调用 umask(0);
每个进程都有自己的umask,继承自系统,如果你设置了umask,它采用就近原则使用用户设置的,不使用系统的了。
有了open,我是不是就可以自己实现一个touch命令了。
如果是touch命令,则获取命令行中的第二个参数,调用open(argv[1],O_WRONLY | O_CREAT,0666)
不重新设置umask时,文件权限就是664
- 返回值
打开并新建一个文件后,会返回新文件的文件描述符descriptor
上方的代码返回的文件描述符为什么是3呢?- - 后面讲
它有什么用呢?
使用文件描述符可对文件进行操作
修改要写入的内容后
为什么此时写到文件中的内容,没有清空之前的内容呢? - - (因为你只告诉open系统调用,只写,没有就创建,没告诉我要清空。)
清空/截断
的选项:O_TRUNC
,带上该选项,写入前就会清空。
新增
式的写,需要带上选项:O_APPEND
有了上面的知识后,我们就可以清楚的知道:fopen -> open,fclose -> close,fwrite->write,fread -> read;w、a、r就对应相应的宏,所以语言层的文件库函数,本质上就是封装了文件的系统调用接口。
3.2 文件描述符
open的返回值(文件描述符)为什么从3开始呢?
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2
。 0,1,2对应的物理设备一般是:键盘,显示器,显示器
那么此时就可以直接使用三个默认打开的文件描述符了
#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));//输入到标准输出中}return 0;}
所以这个文件描述符到底是什么呢?- - 连续的数字,是数组下标吗?
下面我们从内核的角度看一下
内核源代码中确实存在文件描述符表
所以,每次我们在open时,当前进程会去自己的文件描述符表中的 结构体指针数组 中从前向后找空位置。
那么,在系统层面,文件描述符就是访问文件的唯一方式。
但是我们平常在使用C标准库提供的文件访问函数时,并没有看到相应的fd呀?- - 这是因为库中进行了封装,全部封装到FILE中去了!
上面所说的我都能理解,可是键盘、显示器也能被当作文件来看吗?Linux下一切皆文件又如何理解呢?
对于硬件来说,它们都有一个特点,它们统一叫做外设。操作系统是通过类似于链表的方式将各个外设管理起来的。
但对于很多外设(struct device)来说,它们的属性可以相同,但是属性的内容可以不同;可是键盘就是键盘、显示器就是显示器,对这两个外设进行操作的方法只有两个:读和写(IO),所以它们的方法一定不同,因为不同的设备访问对应硬件的方式是不同的。那是怎么做到统一以文件的视角来访问的呢?
- 对于每一种设备,都要给其定义读写方法。
例如:键盘定义了读和写的方法,但是只需要实现读方法;显示器定义了读和写的方法,但是只需要实现写方法。对于未实现的方法,就让它为空。- 在对设备进行管理时,只需要在其struct file中设置两个函数指针,让其指向对应的方法(不关心方法的实现),屏蔽了底层的差异。
- 从struct file向上开始,所有访问硬件的方法,统一叫做读和写,struct file就相当于做了一次封装。
- Linux系统对外部只需要提供struct file对象,这叫做虚拟文件系统vfs。
- 那这个struct file是怎么让用户看到的呢?通过文件描述符,让进程看到。
- 因为所有用户的行为,都会被转化为进程
- 站在进程角度,它只需要通过fd,找到对应的struct file,执行struct file中对应的方法即可完成对设备的操作。
所以Linux中一切皆文件是对进程而言的
- 在一个结构体当中,一切皆文件,但是底层却有不同硬件,这种模式在C++ 中叫做多态。(struct file就是父类,因为struct file都一样;硬件的struct device就是子类,子类实现了不同的读写方法;继承就是产生了上下层关系),这就是C语言中实现多态的一种形式。
内核源代码
4. IO的基本过程
- 写文件
其实write做的工作就是将要写的内容,拷贝到文件的内核缓冲区即可。
- 读文件
先从磁盘读到文件的内核缓冲区,在调用read函数拷贝到指定位置
- 修改文件
修改的本质:也是先读取,在写入
(先将文件加载到文件的内核缓冲区,在内核中修改,然后再将修改后的内容从缓冲区加载到外设中)
存在缓冲区的原因:内存的操作快,外设的操作慢
5.重定向
5.1 引入重定向
先看一个现象
为什么关掉0以后,我自己打开的文件就是0了呢?
因为进程打开文件,需要给进程分配新的fd,fd的分配规则:最小的,没有被使用的fd!
会从上往下扫描文件描述符表,找未被使用的,最小的。
如果我把1号关了,此时fd1就应该为1;由于printf底层封装了fprintf,fprintf中有FILE*参数,默认fd为1,所以只会向1号描述符中打印,不管1指向哪里,那么就应该将内容打印到log1.txt中!
但是log1.txt中怎么什么都没有呢?
fflush(stdout)后为什么又有了呢? - - 跟缓冲区相关,后面讲。
为什么本来应该向显示器中打印的内容,最终却写到了文件中呢?
因为在上层的调用中,fprintf、fwrite等向stdout中打印的所有调用,它们只认文件描述符1。 1号描述符表并没有变,我们只改变了1中的内容,这就叫做重定向
所以,重定向的原理就是:更改文件描述符表中特定下标中的内容。重定向的过程中,上层代码毫不知情!
5.2 系统中的重定向接口
dup2:系统重定向接口
本质就是用新的fd覆盖到指定位置! 被覆盖的将被关掉。
- 输出/追加重定向
那追加重定向不就是只需要将选项 O_TRUNC换成O_APPEND了吗?
- 输入重定向
在写我们自己的shell时,思考一下,程序替换会影响重定向的结果吗?
- 不会,因为程序替换仅仅是替换进程所对应的代码和数据,必要时修改mm_struct中页表的映射关系。对于一个进程“上层”的东西(task_struct、file_struct、mm_struct等)都不会修改,依旧使用重定向以后的内容。
6. 缓冲区问题
在上面的内容中,我们遗留了一个问题。
那就是为什么我重定向以后它并没有直接给我写到指定文件中,而fflush(stdout)后就写进去了呢?
- 首先我们要知道,在C语言中我们使用的printf、scanf、fprintf、fscanf、fwrite、fread等都要求有一个FILE*的指针。
- 所以,在调用这些函数进行操作时,它并没有直接调用系统调用read、write直接拷贝到文件的内核缓冲区,因为频繁的
调用系统调用的成本太高了
,效率低。- 所以怎么能提高效率呢?
- 通过用户级缓冲区!! 你printf、fprintf等只需要将内容拷贝到用户级缓冲区中任务就完成了,无非就是在拷贝的过程中进行一下格式化;等用户级缓冲区攒了足够多的数量,在统一调用系统调用写入到文件的内核缓冲区,提高了效率。
- 该缓冲区在FILE结构体中,刷新的本质就是从用户级缓冲区拷贝到内核的文件缓冲区。
用户级缓冲区有以下几种刷新方案:
- 显示器文件:行刷新
- 普通文件:缓冲区写满再刷新
- 不缓冲(语言级无需刷新)
我们将最开始的代码修改一下会发现,如果我不调用任何的close或者调用fclose,内容可以正常打印出来,这是为什么呢?
因为,当一个进程退出的时候,会自动刷新自己的缓冲区(所有的FILE对象内部,包括stdin、stdout、stderr);fclose是C语言级的,调用它关闭FILE时,也会自动刷新。
那close(fd)后,为什么不会刷新呢?
此时尽管“表面上”是向显示器中打,应该是行刷新,那么我不自己刷新应该也可以显示出来呀? - - 此时不是行刷新,因为显示器文件早就关闭了,1中放的是普通文件,应执行写满刷新的策略。
- 那操作系统是什么时候将文件内核缓冲区的内容刷新到外设中的呢?我能不能控制呢?
- 通过系统调用fsync
- 一个简单的题目
如果在调用函数时不加 \n,即使不重定向,也是上图所示的打印效果。
7. 简单版shell的实现
#include<cstdio>
#include<stdlib.h>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
//存储命令行参数的两个全局变量
char* g_argv[argvnum];
int g_argc;
//存shell自己的环境变量
char* g_env[envnum];//如果这两个设置为局部变量,则会写入一个空白
//局部变量销毁,环境变量表中存的是这两个变量的地址,所以就是空
char pwd[basesize];
char pwdenv[basesize];//存储之前的退出码
int lastcode = 0;//重定向相关全局变量
#define NonRedir 0
#define InputRedir 1
#define OutputRedir 2
#define AppendRedir 3
int redir = NonRedir;
char* filename = nullptr;
//去除空格
#define TrimSpace(pos)\do {\while(isspace(*pos)){\pos++;\}\}while(0)\
string GetName()
{string username = getenv("USER");return username.empty() ? "None" : username;
}string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None": hostname;
}bool repalcePwd()
{for(int i = 0; g_env[i]; i++){if(strncmp(g_env[i],pwdenv,3) == 0){g_env[i] = pwdenv;return true;}}return false;
}string GetPwd()
{if(getcwd(pwd,sizeof(pwd)) == nullptr)return "Node";//将当前的工作路径,保存至环境变量中snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);//putenv(pwdenv); //将新的pwd,添加到系统的环境变量表中repalcePwd();//将新的pwd,添加到自己的环境变量表中return pwd;// string pwd = getenv("PWD");
}string LastDir()
{string pwd = GetPwd();if(pwd == "/" || pwd == "None")return pwd;//寻找最后一个文件夹int pos = pwd.rfind("/");return pwd.substr(pos+1);
}//1.显示命令行提示符
void ShowCommandLine()
{char command_line[basesize];snprintf(command_line,basesize,"[%s@%s %s]#",\GetName().c_str(),GetHostName().c_str(),LastDir().c_str());printf("%s",command_line); fflush(stdout);
}//2.读取命令行参数
bool GetCommandLine(char command_buffer[],int size)
{//读取一行的用户输入char* ret = fgets(command_buffer,size,stdin);if(ret == NULL){return false; //获取输入失败}//处理回车键command_buffer[strlen(command_buffer) - 1] = '\0';if(strlen(command_buffer) == 0)return false;return true;
}void debug()
{printf("argc:%d\n",g_argc);for(int i=0; g_argv[i]; i++){printf("[%d] = %s\n",i,g_argv[i]);}
}void CheckRedir(char command_line[],int len)
{int end = len - 1;while(end >= 0){if(command_line[end] == '<'){redir = InputRedir;command_line[end] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}else if(command_line[end] == '>'){if(command_line[end - 1] == '>'){redir = AppendRedir;command_line[end] = '\0';command_line[end-1] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}else {redir = OutputRedir;command_line[end] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}}else {end--;}}
}void InitCommand()
{//命令行清空memset(g_argv,0,sizeof(g_argv));g_argc = 0;//每次检查是否有重定向前先清空redir = NonRedir;filename = nullptr;
}void AnalyCommand(char command_line[])
{const char sep[10] = " ";//指定分隔符g_argv[g_argc++] = strtok(command_line,sep);//先提取第一个//strtok读取失败返回nullwhile((g_argv[g_argc++] = strtok(nullptr,sep)));//依次提取后面的g_argc--;//个数要-1
}void AnalyzeCommandLine(char command_line[])
{InitCommand();//printf("redir before:%s\n",command_line);//检查重定向CheckRedir(command_line,strlen(command_line));// printf("redir:%d\n",redir);// printf("filename:%s\n",filename);// printf("redir after:%s\n",command_line);AnalyCommand(command_line);
}void Redir()
{//程序替换不会影响重定向,因为内核数据结构中的file_struct没变//程序替换,替换的是代码和数据。int fd = -1;if(redir == InputRedir){if(filename){fd = open(filename,O_RDONLY);if(fd < 0){exit(3);}dup2(fd,0);}else {exit(2);}}else if(redir == OutputRedir){if(filename){fd = open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd < 0){exit(5);}dup2(fd,1);}else {exit(4);}}else if(redir == AppendRedir){if(filename){fd = open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);if(fd < 0){exit(7);}dup2(fd,1);}else {exit(6);}}else {//没有重定向//do nothing}
}
bool ExecCommandLine()
{//shell创建子进程执行任务pid_t id = fork();if(id < 0)return false;if(id == 0){//child执行命令//重定向应由子进程做Redir();execvpe(g_argv[0],g_argv,g_env);exit(1);//执行失败,退出码为1}int status = 0;pid_t rid = waitpid(id,&status,0); //阻塞式等待if(rid > 0){//等待成功//1.子进程正常结束if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else {lastcode = 99;}return true;}return false;
}void AddEnv(const char* str)
{int index = 0;while(g_env[index]){index++;}g_env[index] = (char*)malloc(strlen(str) + 1);strncpy(g_env[index++],str,strlen(str)+1);g_env[index] = nullptr;
}bool CheckAndExecBuildCommand() //判断是否式内建命令
{//枚举几个内建命令if(strcmp(g_argv[0],"cd") == 0){if(g_argc == 2){chdir(g_argv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if(strcmp(g_argv[0],"export") == 0){if(g_argc == 2){AddEnv(g_argv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if(strcmp(g_argv[0], "env") == 0){for(int i = 0; g_env[i]; i++){printf("%s\n",g_env[i]);}lastcode = 0;return true;}else if(strcmp(g_argv[0],"echo") == 0){if(g_argc == 2){if(g_argv[1][0] == '$'){if(g_argv[1][1] == '?'){printf("lastcdoe:%d\n",lastcode);lastcode = 0;}}else{printf("%s\n",g_argv[1]);lastcode = 0;}}else{lastcode = 3;}return true;}return false;
}void InitEnv()
{extern char** environ;//从系统shell中获取环境变量int index = 0;while(environ[index]){int len = strlen(environ[index]);g_env[index] = (char*)malloc(len + 1);strncpy(g_env[index],environ[index],len + 1);index++;}g_env[index] = nullptr;
}int main()
{InitEnv();char command_buffer[basesize];while(true){ //1.显示命令行提示符ShowCommandLine();//slsleep(2);//printf("\n");//2.读取命令行参数if( !GetCommandLine(command_buffer,basesize) ){continue; }//3.解析命令行参数AnalyzeCommandLine(command_buffer);//debug();if(CheckAndExecBuildCommand()) //判断是否式内建命令{continue;}//4.执行命令ExecCommandLine();}return 0;
}
相关文章:

【Linux】内存文件系统的I/O、重定向
文章目录 1. 系统中的文件2. 回顾C中的文件接口3. 文件类的系统调用3.1 open3.2 文件描述符 4. IO的基本过程5.重定向5.1 引入重定向5.2 系统中的重定向接口 6. 缓冲区问题7. 简单版shell的实现 1. 系统中的文件 在学习完Linux权限后,我们清楚的知道:文…...
力扣10.18
1463. 摘樱桃 II 给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。 grid 中每个格子的数字表示你能获得的樱桃数目。 你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。 请你按照如下规则…...

cs木马图形化界面出现问题处理
一个月多月没用cs木马了,发现打开客户端之后显示不出图形化界面,且出现下面这样的报错。 、 最后发现是java版本的问题,kali的java自动更新了。把原来的openjdk11改到了openjdk23。 解决方法: 输入: sudo update-…...

数据结构与算法 - 树 #数的概念 #二叉树 #堆 - 堆的实现/堆排序/TOP-K问题
文章目录 前言 一、树 (一)、概念 1、树的定义 (二)、树的定义 1、树为什么是递归定义的? 2、如何定义树(如何表达一棵树) 解决方案一:假设我们得知该树的度 解决方案二:顺序表 解决方案三:左孩子右兄弟表示法 二、二叉…...

Git推送被拒
今天开发完成一个新的需求,将自己的分支合并到test分支后,推送到远程仓库,结果显示推送被拒: 原因是因为有人更新了test分支的代码,我在合并之前没有拉取最新的test分支代码,所以他提示我“推送前需要合并…...

Jmeter进行http接口测试
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 本文主要针对http接口进行测试,使用jmeter工具实现。 Jmeter工具设计之初是用于做性能测试的,它在实现对各种接口的调用方面已经做的比较…...

工业相机详解及选型
工业相机相对于传统的民用相机而言,具有搞图像稳定性,传输能力和高抗干扰能力等,目前市面上的工业相机大多数是基于CCD(Charge Coupled Device)或CMOS(Complementary Metal Oxide Semiconductor)芯片的相机。 一,工业相机的分类 …...
RAID 矩阵
在架构设计中,RAID矩阵(RAID Log)是一个用于项目管理和风险管理的工具,帮助团队有效管理和跟踪项目中可能影响成功交付的关键因素。与存储技术中的 RAID 不同,这里的 RAID 是一个缩写,代表: R:…...

详细分析Redisson分布式锁中的renewExpiration()方法
目录 一、Redisson分布式锁的续期 整体分析 具体步骤和逻辑分析 为什么需要递归调用? 定时任务的生命周期? 一、Redisson分布式锁的续期 Redisson是一个基于Redis的Java分布式锁实现。它允许多个进程或线程之间安全地共享资源。为了实现这一点&…...

实验3,网络地址转换
实验3:网络地址转换 实验目的及要求: 通过实验,掌握NAT技术的工作原理,了解三种不同类型NAT技术的主要作用以及各自的主要应用环境。能够完成静态NAT和复用NAT技术的应用,并熟练掌握NAT技术相关的配置命令。 实验设…...
Java 中的 String 字符串是不可变的
文章目录 什么是不可变字符串?举个例子直观理解 不可变的原理1. 内部实现2. 字符串常量池3. 线程安全 为什么要设计成不可变?什么时候用可变字符串?示例 总结推荐阅读文章 在 Java 编程中,字符串(String)是…...
计算机网络架构实例
小型企业网络 1. 终端设备: - 员工的台式电脑和笔记本电脑,用于日常办公,如文档处理、邮件收发、业务软件使用等。 - 智能手机和平板电脑,方便员工在外出或移动办公时也能接入公司网络,查看邮件和处理紧急事务。 2.…...

Chrome与Firefox浏览器HTTP自动跳转HTTPS的解决方案
一、背景介绍 随着网络安全意识的不断提高,越来越多的网站开始采用HTTPS协议,以确保数据传输的安全性。然而,有时用户在浏览网页时,可能会遇到HTTP请求被自动跳转至HTTPS的情况导致网站打不开,提示安全问题࿰…...

众数信科荣登“2024 CHINA AIGC 100”榜单
2024年10月17日,由非凡产研推出的「2024 CHINA AIGC 100」榜单隆重发布,众数信科凭借领先的企业AI智能体解决方案能力荣登榜单。 非凡产研AIGC 100 评选旨在挖掘国内具有高潜力的AI应用,为AI产业的高质量发展注入新动力。榜单覆盖了教育、医疗…...

【AI知识】距离度量和相似性度量的常见算法
本文介绍一些AI中常见的距离度量和相似性度量算法: 1. 欧几里得距离(Euclidean Distance) 欧几里得距离是最常见的距离度量方法,用来计算两个向量之间的“直线距离”,也被成为L2范数。 公式如下,其中 x…...

LeetCode1004.最大连续1的个数
题目链接:1004. 最大连续1的个数 III - 力扣(LeetCode) 1.常规解法(会超时) 遍历数组,当元素是1时个数加一,当元素是0时且已有的0的个数不超过题目限制时,个数加一,若上…...

Parallels Desktop20虚拟机软件能让你在Mac上无缝运行Windows
Code 生成器:Parallels Desktop 20最新版本虚拟机的奇妙世界 🌟【轻松跨越操作系统界限】🌟 你是否常常感到在Mac和Windows之间切换太麻烦?Parallels Desktop 20最新版,让你不再为跨系统操作而烦恼。这款虚拟机软件能让…...

Golang | Leetcode Golang题解之第476题数字的补数
题目: 题解: func findComplement(num int) int {highBit : 0for i : 1; i < 30; i {if num < 1<<i {break}highBit i}mask : 1<<(highBit1) - 1return num ^ mask }...

Spring 实现 3 种异步流式接口,干掉接口超时烦恼
大家好,我是小富~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某…...

字节 HLLM 论文阅读
github连接:https://github.com/bytedance/HLLM 探讨问题: 推荐LLM的三个关键问题: LLM预训练权重通常被认为是对世界知识的概括,其对于推荐系统的价值?对推荐任务进行微调的必要性?LLM是否可以在推荐系统…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...