【Linux】基础 IO(文件描述符、重定向、缓冲区)
Linux
- 1.理解文件
- 2.C文件接口
- 1.打开 + 写文件
- 2.读文件 + 简单实现cat命令
- 3.输出信息到显示器的方式
- 4.stdin、stdout、stderr
- 5.打开文件的方式
- 3.系统接口 IO
- 1.传递标志位
- 2.open、close
- 3.write、read
- 4.文件描述符
- 1.是什么?
- 2.分配规则
- 3.重定向原理
- 4.通过dup2系统调用重定向
- 5.标准错误重定向
- 6.自定义shell添加重定向功能
- 5.理解一切皆"文件"
- 6.缓冲区
- 1.什么是缓冲区?
- 2.FILE
- 3.缓冲类型
- 4.为什么要引入缓冲区机制
- 5.设计文件libc库
1.理解文件
狭义理解:
- 文件在磁盘中。
- 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的。
- 磁盘是外设(输出设备/输入设备)
- 磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出,简称 “IO”。
广义理解:
Linux 下一切皆文件(键盘、显示器、网卡、磁盘……)
文件操作:
- 对于 0KB 的空文件是占用磁盘空间的。
- 文件 = 文件属性(元数据) + 文件内容。
- 所有的文件操作本质是文件内容操作和文件属性的操作。
系统角度:
- 访问文件的前提是先打开文件,谁打开文件呢?答案是 “进程打开文件”。对文件的操作本质就是 “进程对文件的操作”。
- 磁盘的管理者是操作系统,访问文件本质就是 “访问磁盘”,只有操作系统才能访问磁盘文件。
- 文件的读写本质不是通过C语言/C++的库函数来操作的,这些库函数只是为用户提供方便,而是通过文件相关的系统调用接口来实现的。fopen 和 fclose 封装了操作系统对文件的系统调用。
- 操作系统通过 “先描述,再组织” 的方式对文件进行管理。在操作系统内部对被打开的文件创建 struct 结构体(包含被打开文件的相关属性),这些结构体通过链表的形式组织起来,对被文件的管理转化成对链表的 “增删查改”。
2.C文件接口
1.打开 + 写文件
#include<stdio.h>
#include<string.h>int main()
{FILE* fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen:");return 1; }int cnt = 1;const char* msg = "Hello Linux:"; while(cnt <= 10) { char buffer[1024]; snprintf(buffer, sizeof(buffer), "%s%d\n", msg, cnt++);fwrite(buffer, strlen(buffer), 1, fp);} fclose(fp);return 0;
}
2.读文件 + 简单实现cat命令
#include<stdio.h>
#include<string.h>int main(int argc, char* argv[])
{if(argc != 2){printf("Usage:%s filename\n", argv[0]); return 1;}FILE* fp = fopen(argv[1], "r");if(fp == NULL){perror("fopen");return 2;}while(1){char buffer[128];memset(buffer, 0, sizeof(buffer));int n = fread(buffer, sizeof(buffer) - 1, 1, fp);if(n > 0)printf("%s", buffer);if(feof(fp)) //当到了文件的结尾,退出循环break;} fclose(fp);return 0;
}
3.输出信息到显示器的方式
Linux 中一切皆文件,显示七也是一种文件
#include<stdio.h>
#include<string.h> int main()
{ printf("Hello printf\n"); fprintf(stdout, "Hello fprintf\n");const char* msg = "Hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);return 0;
}
4.stdin、stdout、stderr
- C 默认会打开三个输入输出流,分别是stdin、stdout、stderr
- 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型是文件指针。
#include <stdio.h>extern FILE *stdin; //标准输入:键盘文件
extern FILE *stdout; //标准输出:显示器文件
extern FILE *stderr; //标准错误:显示器文件
5.打开文件的方式
3.系统接口 IO
- 打开文件的方式不仅仅是fopen,ifstream等语言层的方案,其实系统才是打开文件最底层的方案。
- 不过,在学习系统文件 IO 之前,先要了解下如何给函数传递标志位,该方法在系统文件 IO 接口中会使用到:
1.传递标志位
- 当存在多个标记位时,一般的做法是传递多个参数,用起来非常麻烦。
- 操作系统采用 “位图” 来 “传递标志位” 的方式,32个比特位,每一个比特位的 0/1 代表是否被设置。
传递标志位的代码案例:
#include<stdio.h>#define ONE_FLAG 1<<0 //0000 0000 ... 0000 0001
#define TWO_FLAG 1<<1 //0000 0000 ... 0000 0010
#define THREE_FLAG 1<<2 //0000 0000 ... 0000 0100
#define FOUR_FLAG 1<<3 //0000 0000 ... 0000 1000void fun(int flags)
{if(flags & ONE_FLAG) printf("one\n");if(flags & TWO_FLAG) printf("two\n");if(flags & THREE_FLAG) printf("three\n");if(flags & FOUR_FLAG) printf("four\n");
}int main()
{fun(ONE_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG | THREE_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG); printf("\n");return 0;
}
2.open、close
#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:若文件不存在,则创建它。O_APPEND:追加写。
- mode:当文件不存在时,以O_WRONLY打开文件,指明创建新文件的访问权限。
- 返回值:成功时返回新打开的文件描述符。失败时返回-1
#include <unistd.h>int close(int fd);
- fd:文件描述符,open 函数的返回值。
- 返回值:成功时返回0,失败时返回-1
#include <sys/types.h>
#include <sys/stat.h>mode_t umask(mode_t mask); //修改权限掩码
3.write、read
#include <unistd.h>ssize_t write(int fd, const void* buf, size_t count);
- fd:文件描述符,open 函数的返回值。
- buf:指向要写入的数据的指针。
- count:指定了要写入的字节数。
- 返回值:成功时返回真实写入文件的字节数,失败时返回-1
FILE* fp = fopen("log.txt", "w"); //低层就是下面的系统调用
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);FILE* fp = fopen("log.txt", "a"); //低层就是下面的系统调用
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
文本写入 VS 二进制写入
#include<unistd.h>ssize_t read(int fd, void* buf, size_t count);
- fd:文件描述符。
- buf:将数据读入到该指针 buffer 指向的字符串中。
- count:需要读取的字节数。
- 返回值:成功时返回读取的字节数,失败时返回-1
4.文件描述符
1.是什么?
文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了 file 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针 *files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
- 文件描述符:进程对应的文件描述符表的数组下标。
- 当用户层进行 open(“log.txt”, “w”) 调用时:操作系统创建新的 struct file,在文件描述符表中找到未被使用的下标,将 struct file 的地址填写进去,此时进程与文件就关联了。
- 当用户层进行 read(fd, buffer, sizeof(buffer)) 调用时:操作系统拿着 fd 索引文件描述符表找到对应的 struct file,每一个 struct file 都对应内存中的一个 “文件缓冲区”,操作系统先将磁盘文件中的内容预加载到文件缓冲区中,再将文件缓冲区中的内容拷贝到 buffer 中。read 函数本质:内核到用户空间的拷贝函数。
- 当用户层进行 write(fd, buffer, strlen(buffer)) 调用时:先将 buffer 指向的内容拷贝到文件缓冲区中,再将缓冲区中的内容定期刷新到磁盘文件中。
- 对文件做任何操作,都必须先将文件加载(磁盘->内存的拷贝)到内核对应的文件缓冲区中。
内核代码如下:
通过 open 系统调用的返回值,得知文件描述符(fd)是一个整数。 如下代码所示:
思考:文件描述符为什么从3开始,值为0、1、2 的文件描述符是什么?
- 文件描述符值为0、1、2 分别是:标准输入、标准输出、标准错误。
- C语言中的 fopen 返回值 FILE* 中的 FILE 是一个结构体。
- 在操作系统接口层面上,只认文件描述符 fd
- 结构体 FILE 一定封装了文件描述符 fd
封装:
- 在 Windows、Linux 等不同的平台下的系统调用不同,使用系统调用不具备可移植性。
- C/C++封装各个平台关于文件操作的系统调用,在不同的平台下通过条件编译进行裁剪,成为语言级接口,具备可移植性。
- 语言增加可移植性的原因:为了让更多人使用,防止被淘汰。
2.分配规则
文件描述符的分配规则:在 struct file* array[] 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
3.重定向原理
解释图如下:
- printf 函数就是往 stdout 文件中打印内容,也就是文件描述符值为 1 的文件,但修改了 fd_array[1] 的指向时,便打印到了 log.txt 文件中。
- 更改文件描述符表中 fd_array[] 数组某个下标内容
指针的指向
(数组下标不变),叫做 “重定向”
4.通过dup2系统调用重定向
#include <unistd.h>int dup2(int oldfd, int newfd);
作用:makes newfd be the copy of oldfd, closing newfd first if necessary
5.标准错误重定向
- 思考:都是输出到显示器中,为什么要区分标准输出printf、cout和标准错误perror、cerr / 为什么存在标准错误?
- 答案:标准输出和标准错误占用不同的文件描述符。虽然都是显示器,但是可以通过重定向,将常规消息和错误消息分离。
将标准输出和标准错误都重定向到一个文件中该如何做呢?
6.自定义shell添加重定向功能
- 如果内建命令做重定向,需要更改 shell 的标准输入、输出、错误。此时需要创建临时文件,类似两个整数交换的过程,进行一次重定向后需要恢复。
- 一个文件可以被多个进程打开。若一个进程将其中一个文件关闭,就会影响其它正在读取该文件的进程。所以 struct file 中有一个 ref_count(引用计数)的整形变量,操作系统打开时引用计数为0,指针指向该文件时引用计数++,关闭文件时引用计数–,当引用计数为0时,struct file 被释放。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unordered_map>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "//命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//环境变量表
#define MAX_ENVS 128
char* g_env[MAX_ENVS];
int g_envs = 0;//别名映射表
std::unordered_map<std::string, std::string> alias_list;//重定向,关心的内容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
std::string filename;//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;//1.获取环境变量for(int i = 0; environ[i]; i++){//申请空间g_env[i] = (char*)malloc(strlen(environ[i] + 1));//拷贝环境变量strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"XZY=123456";g_env[g_envs] = NULL;//2.导入环境变量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}//获取用户名
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}//获取主机名
const char* GetHostName()
{const char* name = getenv("HOSTNAME");return name == NULL ? "None" : name;
}//获取当前路径
const char* GetPwd()
{//const char* pwd = getenv("PWD"); 根据环境变量PWD获得当前路径(当cd修改路径时:环境变量不会被修改)const char* ret = getcwd(cwd, sizeof(cwd)); //通过系统调用getcwd:获取当前路径cwdif(ret != NULL) //获取成功时:ret也是当前路径{snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd); //格式化环境变量"PWD=cwd"putenv(cwdenv); //更新环境变量"PWD=cwd"}return ret == NULL ? "None" : cwd;
}//获取家目录
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "NULL" : home;
}//根据当前绝对路径修改为相对路径
std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos + 1);
}//制作命令行提示符
void MakeCommandLinePrompt(char prompt[], int size)
{//snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str()); snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}//打印命令行提示符
void PrintCommandLinePrompt()
{char prompt[COMMAND_SIZE];MakeCommandLinePrompt(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}//获取命令
bool GetCommand(char* command, int size)
{char* ret = fgets(command, size, stdin);if(ret == NULL) return false;command[strlen(command) - 1] = '\0'; //清理\nif(strlen(command) == 0) return false;return true;
}//命令解析
bool CommandPrase(char* command)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(command, SEP);while((bool)(g_argv[g_argc++] = strtok(NULL, SEP)));g_argc--;return g_argc > 0 ? true : false;
}//打印命令行参数
void PrintArgv()
{for(int i = 0; g_argv[i]; i++){printf("argv[%d]:%s\n", i, g_argv[i]);}printf("argc:%d\n", g_argc);
}//父进程执行cd命令
bool Cd()
{if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else {std::string where = g_argv[1];if(where == "~"){}else if(where == "-"){}else {chdir(where.c_str());}}return true;
}bool Echo()
{if(g_argc == 2){std::string opt = g_argv[1];if(opt == "$?") //echo $?{std::cout << lastcode << std::endl;lastcode = 0;}else if(opt[0] == '$'){std::string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value)std::cout << env_value << std::endl;}else {std::cout << opt << std::endl;}}return true;
}//检测并执行内建命令:由父进程执行
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export") {//1.在环境变量表中查找环境变量名:是否存在//2.存在修改,不存在新增}else if(cmd == "alias"){//std::string nickname = g_argv[1];//alias_list.insert(k, v)}return false;
}//执行普通命令:由子进程执行
void ExecuteCommand()
{ pid_t id = fork();if(id == 0){//子进程//子进程检查重定向情况,父进程不能重定向int fd = -1;if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0) exit(1);dup2(fd, 0);close(fd);}else if(redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else {}//进程替换不影响,重定向的结果execvp(g_argv[0], g_argv);exit(1);}//父进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}
}void TrimSpace(char command[], int& end)
{while(isspace(command[end])){end++;}
}void RedirCheck(char command[])
{ redir = NONE_REDIR;filename.clear();int start = 0, end = strlen(command) - 1;while(end > start){if(command[end] == '<'){command[end++] = '\0';TrimSpace(command, end);redir = INPUT_REDIR;filename = command + end;break;}else if(command[end] == '>'){if(command[end - 1] == '>'){command[end - 1] = '\0';end++;TrimSpace(command, end);redir = APPEND_REDIR;filename = command + end;break;}else {command[end++] = '\0';TrimSpace(command, end);redir = OUTPUT_REDIR;filename = command + end;break;}}else {end--;}}
}int main()
{//shell启动的时候,需用从系统中获取环境变量//我们的环境变量信息应该从父shell中获取InitEnv();while(true){ //1.输出命令行提示符PrintCommandLinePrompt();//2.获取用户输入的命令char command[COMMAND_SIZE];if(!GetCommand(command, sizeof(command)))continue;//3.重定向分析:"ls -a -l > file.txt" -> "ls -a -l" "file.txt" -> 判定重定向方式RedirCheck(command); //printf("redir = %d, filename = %s\n", redir, filename.c_str());//4.命令解析:"ls -a -l" -> "ls"、"-a"、"-l" if(!CommandPrase(command))continue;//PrintArgv();//检测别名//5.检测并处理内建命令if(CheckAndExecBuiltin())continue;//6.执行命令ExecuteCommand();}return 0;
}
5.理解一切皆"文件"
- 首先在windows中是文件的东西,它们在linux中也是文件。其次一些在windows中不是文件的东西(进程、磁盘、显示器、键盘)这样硬件设备也被抽象成了文件,你可以使用访问文件的方法访问它们获得信息。甚至管道也是文件,网络编程中的socket(套接字)这样的东西,使用的接口跟文件接口也是一致的。
- 这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 PIPE)的操作都可以用 read 函数来进行。几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。
- 当打开一个文件时,操作系统为了管理所打开的文件,都会为这个文件创建一个 file 结构体,该结构体定义在 /usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h 下,以下展示了该结构部分我们关系的内容:
struct file
{//...struct inode* f_inode; /* cached value */const struct file_operations* f_op;//...atomic_long_t f_count; //表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值unsigned int f_flags; //表示打开文件的权限fmode_t f_mode; //设置对文件的访问模式,例如:只读,只写等。loff_t f_pos; //表示当前读写文件的位置 //...
};
值得关注的是 struct file 中的 f_op 指针指向了一个 file_operations 结构体,这个结构体中的成员中存在
read 和 write 等函数指针。如下:
struct file_operations
{//...ssize_t(*read) (struct file*, char __user*, size_t, loff_t*);ssize_t(*write) (struct file*, const char __user*, size_t, loff_t*);//...
};
file_operation 就是把系统调用和驱动程序关联起来的关键数据结构,这个结构的每一个成员都对应着一个系统调用。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。一张图总结如下:
上图中的外设,每个设备都可以有自己的 read 和 write,但对应着不同的操作方法!通过 struct file 下 file_operation 中的各种函数回调,让我们开发者只用 file 便可调取 Linux 系统中绝大部分的资源!这便是 “linux下一切皆文件” 的核心理解。
6.缓冲区
1.什么是缓冲区?
缓冲区:内存中预留了一段存储空间,这些空间用来缓冲输入或输出的数据,该空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
以上是什么导致的呢?
- 通过C语言中的库函数(printf/fprintf/fputs/fwrite)输出数据,并不是直接写到文件内核缓冲区中(若是直接写到文件内核缓冲区中,那么进程关闭该文件时,会将缓冲区中的内容刷新到外设中,上面的代码并未出现该结果)。而是在C语言的标准库中,它为每一个打开的文件创建一个用户层,语言级缓冲区。通过系统调用(write)输出数据,直接刷新到文件内核缓冲区中。
- 当用户
强制刷新 / 筛选条件满足 / 进程退出
时:由C标准库根据文件描述符fd + 系统调用(write),将语言级缓冲区中的内容刷新到文件内核缓冲区中。- 在调用 close 之前,进程还未退出,即没有强制刷新 / 筛选条件满足 / 进程退出,数据会一直在C标准库中的语言级缓冲区。当调用 close 时,文件描述符 fd 被关闭,然后进程退出了,此时打算将语言级缓冲区中的内容刷新到文件内核缓冲区,但是调系统调用时 fd 被关了,无法刷新到文件内核缓冲区,就无法看见内容。
2.FILE
- 问题:每一个文件都有自己对应的语言级缓冲区,那么语言级缓冲区在哪里呢?
- 答案:FILE 是 C 语言中的结构体,其中封装了
文件描述符fd
和语言级缓冲区
如下是FILE的部分内容:
typedef struct _IO_FILE FILE;struct _IO_FILE
{int _fileno; //封装的文件描述符//缓冲区相关//...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. *///...
};
3.缓冲类型
- 全缓冲:要求填满整个缓冲区后才进行 I/O 系统调用操作。对于磁盘文件的操作通常使用全缓冲。
- 行缓冲:当在输入和输出中遇到换行符时,标准 I/O 库函数将会执行系统调用操作。对于显示器通常使用行缓冲。
- 无缓冲:标准 I/O 库不对字符进行缓存,直接调用系统调用。标准出错流 stderr 通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。
数据交给系统,交给硬件的本质全是拷贝。计算机流动的本质:一切皆拷贝!
4.为什么要引入缓冲区机制
- 读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行读、写等操作,那么每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作,即需要执行一次系统调用,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率造成很大的影响。
- 为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
- 又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,⽤来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
- 多次执行 printf 函数可能内部只执行一次系统调用 write,可以减少系统调用的次数,提高效率。
5.设计文件libc库
相关文章:
【Linux】基础 IO(文件描述符、重定向、缓冲区)
Linux 1.理解文件2.C文件接口1.打开 写文件2.读文件 简单实现cat命令3.输出信息到显示器的方式4.stdin、stdout、stderr5.打开文件的方式 3.系统接口 IO1.传递标志位2.open、close3.write、read 4.文件描述符1.是什么?2.分配规则3.重定向原理4.通过dup2系统调用重…...
记录一下远程调试 备忘
在进行远程调试时,目标主机不需要安装完整的编程环境(舍去重复安装)。可以使用Visual Studio的远程调试功能,或者使用gdb和gdbserver进行远程调试。 Visual Studio远程调试 复制远程调试器文件夹:将Visual Studio安装目录下的remot…...
libevent服务器附带qt界面开发(附带源码)
本章是入门章节,讲解如何实现一个附带界面的服务器,后续会完善与优化 使用qt编译libevent源码演示视频qt的一些知识 1.主要功能有登录界面 2.基于libevent实现的服务器的业务功能 使用qt编译libevent 下载这个,其他版本也可以 主要是github上…...
MyISAM索引方案
在InnoDB中索引即数据,也就是聚簇索引的B树叶子节点已经包含了所有完整的用户记录,MyISAM的索引方案虽然也是树形结构,但是将索引和数据分开存储 将表中的记录按记录的插入顺序单独存储在一个文件中【数据文件】,这个文件不划分数…...
Windows 图形显示驱动开发-WDDM 1.2功能—WDDM 1.2 中的 Direct3D 功能和要求
一、架构演进与驱动模型 1.1 WDDM驱动模型的革命性升级 Windows 8引入的WDDM 1.2驱动模型在以下方面实现突破: 内存管理:采用统一虚拟地址空间(UVA)架构,使CPU和GPU可共享相同的指针地址空间。具体实现通过DXGK_DRI…...
深度解析 Vue 项目 Webpack 分包与合包 一文读懂
深度解析 Vue 项目 Webpack 分包与合包 一文读懂 文章目录 深度解析 Vue 项目 Webpack 分包与合包 一文读懂一、Webpack 打包机制深度解析1.1 模块化系统的本质1.2 Webpack 构建流程解析1.3 默认打包的问题分析 二、分包策略深度配置2.1 SplitChunksPlugin 核心配置2.2 精细化分…...
【ROS】map_server 地图的保存和加载
【ROS】map_server 地图的保存和加载 前言地图的保存地图的加载 前言 在 ROS 中,想要实现导航功能,首先需要一张已建好的地图。导航系统依赖这张地图进行路径规划、定位和障碍物避让等操作。本文将讲解在使用 gmapping 或 hector_mapping 建图后&#x…...
【计网】SSL/TLS核心原理
序言 在HTTP协议中,信息是明文传输的,因此为了通信安全就有了HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)协议。HTTPS也是一种超文本传送协议,在HTTP的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务端的…...
sqli-labs靶场 less 11
文章目录 sqli-labs靶场less 11 POS联合注入 sqli-labs靶场 每道题都从以下模板讲解,并且每个步骤都有图片,清晰明了,便于复盘。 sql注入的基本步骤 注入点注入类型 字符型:判断闭合方式 (‘、"、’、“”&…...
陕化之光(原创)
当城市在和周公化合 陕化的工装已与朝霞发生反应 工人先锋号已然吹响 陕化工人游走在工作的床层 钢铁森林间穿梭的身影 是沉默的催化剂 让冰冷的方程式 绽放出最活跃的分子温度 扳手与阀门对话时 塔林正在记录 关于电流与压力的学习笔记 每一次精确的调控 都是舞台上…...
【刷题2025】高级数据结构(并查集+优先队列+图论)
1.并查集 (1)基础理论 并查集是一种树形的数据结构,用于处理一些不相交集合的 合并 及 查询 问题。比如,可以用并查集判断一个森林中有几棵树、某个节点是否属于某棵树。 并查集由一个整形数组 pre[] 和两个函数 find() 、 join() 构成。 数组 pre[] 记录了每个点的前驱…...
数据库性能优化(sql优化)_分布式优化思路01_yxy
数据库性能优化_分布式优化思路01 1 分布式数据库的独特挑战2 分布式新增操作符介绍2.1 数据交换操作符(ESEND/ERECV):2.2 数据迭代操作符GI:3 核心优化策略(一)_分区裁剪优化3.1 普通分区裁剪3.2 动态分区裁剪1 分布式数据库的独特挑战 在分布式数据库系统中,核心为数据被…...
云服务器和物理服务器有什么区别
云服务器与物理服务器的核心区别在于资源分配方式、性能稳定性、成本结构、运维管理及 适用场景。以下是具体分析: 一、资源分配与架构差异 云服务器:基于虚拟化技术,将物理服务器集群分割为多个虚拟实例,资源由多个用户 共享,可根据需求弹性调整配置…...
FPGA-DDS技术的波形发生器
1.实验目的 1.1掌握直接数字频率合成(DDS)的基本原理及其实现方法。 1.2在DE2-115 FPGA开发板上设计一个可调频率的正弦波和方波发生器,频率范围10Hz~5MHz,最小分辨率小于1kHz。 1.3使用Quartus II进行仿真,并通过S…...
晶晨线刷工具下载及易错点说明:Key文件配置错误/mac剩余数为0解决方法
晶晨线刷工具下载及易错点说明:Key文件配置错误/mac剩余数为0解决方法 各种版本晶晨线刷工具下载: 晶晨线刷工具易出错点故障解决方法: 1、晶晨线刷工具加载固件的时候提示mac红字且剩余数为0的解决办法 很多同学可能会与遇到加…...
idea如何使用git
在 IntelliJ IDEA 中使用 Git 的详细步骤如下,分为配置、基础操作和高级功能,适合新手快速上手: 一、配置 Git 安装 Git 下载并安装 Git,安装时勾选“Add to PATH”。验证安装:终端输入 git --version 显示版本…...
python——学生管理系统
学生管理系统主要分为以下三个大类: 一、用户类(User): 属性:用户名(username)、密码(password) 功能:注册(register)、登录&#…...
快速幂+公共父节点
# 快速幂 求:23的10000次幂,那么就是求23的5000次幂,因为2350*235023^100;所以可以遍历log(n)次 int res1; int tmp23; for(int i1;i<logn;i) {tmp*tmp; }显然,我们无法通过logn计算次数; 比如是非偶数的怎么计算呢…...
【差分隐私相关概念】瑞丽差分隐私(RDP)命题4
命题4的证明详解(分情况讨论) 背景与设定 机制: f : D → R f: \mathcal{D} \to \mathcal{R} f:D→R 是由 n n n 个 ϵ \epsilon ϵ-差分隐私机制自适应组合而成。相邻输入: D D D 和 D ′ D D′ 是相邻数据集。目标…...
Vue 人看 React useRef:它不只是替代 ref
如果你是从 Vue 转到 React 的开发者,初见 useRef 可能会想:这不就是 React 版的 ref 吗?但真相是 —— 它能做的,比你想象得多得多。 👀 Vue 人初见 useRef 在 Vue 中,ref 是我们访问 DOM 或响应式数据的…...
C++第三方库【JSON】nlohman/json
文章目录 优势使用API从文件中读取json从json文本创建json对象直接创建并操作json对象字符串 <> json对象文件流 <> json对象从迭代器读取像使用STL一样的访问STL容器转化为 json数组STL容器 转 json对象自定义类型转化为 json对象 限制 优势 直观的语法ÿ…...
从源码到实战:深度解析`rsync`增量同步机制与高级应用
从源码到实战:深度解析rsync增量同步机制与高级应用 #mermaid-svg-C1ZMwvhtq4iP4E8m {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-C1ZMwvhtq4iP4E8m .error-icon{fill:#552222;}#mermaid-svg-C1ZMwvht…...
数据库表设计五层分类系统表设计
文章目录 数据库表设计五层分类系统表设计代码思路详解类概述核心方法详解1. processString(String input) 方法2. createNo(String input, boolean peerNode) 方法3. isParent(String parentNo, String sonNo) 方法 编号系统设计使用场景推测代码特点可能的使用示例 NoProcess…...
Centos/RedHat 7.x服务器挂载ISCSI存储示例(无多路径非LVM)
客户让帮忙挂载个ISCSI存储,大概结构如下图所示: ISCSI存储为一台安装了truenas的X86服务器,提供存储服务的IP地址为10.16.0.1 服务器的ETH1网卡配置与10.16.0.1同段网络。 为了给客户做个简单培训,整理了一下操作步骤。下面是配…...
【android bluetooth 协议分析 21】【ble 介绍 2】【什么是IRK,是如何生成和传递的】
1. 什么是 IRK? IRK,全称 Identity Resolving Key(身份解析密钥),是 BLE 设备用于生成和解析 Resolvable Private Address(RPA) 的密钥。 2. IRK 的生成和传递过程 IRK 是在 BLE 配对…...
4.14-4.15学习总结 IO流:缓冲流+转换流+序列化流+打印流+压缩流+Commons—io工具包+Hutool工具包
图片加密操作: import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class test {public static void main(String[] args) throws IOException {FileInputStream fisnull;FileOutputStream fosnull;try{fisnew…...
Linux入门学习笔记
一、文件路径相关 相对路径与绝对路径 相对路径:是从当前工作目录开始表示文件或目录位置的路径。例如,当前在 /home/user 目录下,若要访问 user 目录下 test 文件夹中的 file.txt 文件,相对路径就是 test/file.txt 。它依赖于当…...
Redis适用场景
Redis适用场景 一、加速缓存二、会话管理三、排行榜和计数器四、消息队列五、实时分析六、分布式锁七、地理位置数据八、限流九、数据共享十、签到 一、加速缓存 Redis最常见的应用之一是作为缓存层,用于存储频繁访问的数据,从而减轻数据库的负载。 通过…...
# WPS打开新文档,“工具”菜单下是空白
WPS打开新文档,“工具”菜单下是空白 在 WPS 中打开新文档后 “工具” 菜单下空白,可能由多种原因导致,如下图: 下面分析并给出对应的解决办法: 一、 功能区显示设置问题 1、原因: WPS 的功能区显示可能…...
【软考-架构】13.3、架构复用-DSSA-ABSD
✨资料&文章更新✨ GitHub地址:https://github.com/tyronczt/system_architect 文章目录 1、软件架构复用2、特定领域软件架构DSSADSSA的三个基本活动参与DSSA的四种角色人员建立DSSA的过程三层次模型 考试真题第一题第二题 3、基于架构的软件开发ABSD的软件开发…...
K8S_ResourceQuota与LimitRange的作用
ResourceQuota 作用详解 资源总量控制:ResourceQuota能对命名空间内的资源使用总量进行限制。在一个Kubernetes集群中,存在多个命名空间,每个命名空间可看作一个独立的工作单元。通过设置ResourceQuota,可以防止某个命名空间过度…...
T101D加固平板电脑:无人机地面站的高效智能控制核心
随着无人机技术在应急救援、农业监测、军事侦察等领域的广泛应用,对地面控制设备的要求也日益提高。鲁成伟业推出的T101D加固平板电脑凭借其高性能、强防护和专业化设计,成为无人机地面站的核心控制终端,为复杂环境下的作业提供了可靠支持。 …...
LLM中的N-Gram、TF-IDF和Word embedding
文章目录 1. N-Gram和TF-IDF:通俗易懂的解析1.1 N-Gram:让AI学会"猜词"的技术1.1.1 基本概念1.1.2 工作原理1.1.3 常见类型1.1.4 应用场景1.1.5 优缺点 1.2 TF-IDF:衡量词语重要性的尺子1.2.1 基本概念1.2.2 计算公式1.2.3 为什么需…...
【基于Servlet技术处理表单】
文章目录 一、实验背景与目的二、实验设计与实现思路1. 功能架构2. 核心代码实现3. 测试用例 总结 一、实验背景与目的 本次实验旨在深入理解Servlet工作原理,掌握JSP与Servlet的协同开发,实现前端表单与后端数据处理的交互。具体目标包括:设…...
【差分隐私相关概念】瑞丽差分隐私(RDP)-瑞丽散度约束了贝叶斯因子后验变化
分步解释和答案: 在Rnyi差分隐私(RDP)框架中,通过贝叶斯因子和Rnyi散度的关系可以推导出关于后验变化的概率保证。以下是关键步骤的详细解释: 1. 贝叶斯因子的定义与分解 设相邻数据集 D D D 和 D ′ D D′&#x…...
Oracle查询大表的全部数据
2000w的大表 表结构如下,其中id是索引 查询处理慢的写法 List<String> queryLoidForPage(Integer startNum,Integer endNum){try {Connection oracleConnection initBean.oracleConnection;Statement stmt oracleConnection.createStatement();// 4.执行查…...
linux 内核 static-key机制分析
1、static key是什么 Linux内核的 Static Keys机制是一种高效的条件分支优化技术,主要用于在运行时动态启用或禁用特定代码路径,同时避免常规条件判断(如 if 语句)的性能开销。它通过结合编译时优化和运行时代码修补(如 Jump Label 技术)实现近乎零成本的开关切换,广泛应用…...
【Java学习】Knife4j使用流程
手动添加依赖,并刷新Maven <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.3.0</version> </dependency>在配置文件application.…...
Java 基本操作快速入门:理解与实践
在软件开发的世界里,Java 作为一种广泛使用的编程语言,已经成为构建企业级应用、移动应用甚至大型系统的主力军。对于任何一位初学者来说,理解 Java 的基本操作是学习编程的第一步。从变量声明到控制流的结构,每一个基础知识点都是…...
jetson orin nano 开发板conda 的 base 环境在 shell 启动时自动激活
使用MobaXterm_Personal_23.0.exe 连接jetson开发板时默认是不进入base环境的 1.输入此命令nano ~/.bashrc看到图1后把conda activate 你的环境名 放到图中标记位置 然后保存退出: Ctrl O 回车保存 Ctrl X 退出编辑器 输入此命令后,source ~/.bas…...
【高中数学/指数/对数】同构六君子之 x/e^x/lnx组合曲线
yx*e^x ye^x/x yx/e^x yx*lnx ylnx/x yx/lnx END...
Golang|在线排查协程泄漏
根据我们的代码,前5毫秒内,每隔1毫秒就会来一个请求,5毫秒之后由于前面的协程执行完,后面又会来新的协程,所以协程数目会保持稳定但是代码一运行,协程数量一直增长,发生了协程泄漏 我们可以list…...
健康养生指南
在快节奏的现代生活中,健康养生愈发重要,它是我们享受美好生活的基石。 饮食是养生的关键一环。秉持均衡原则,每日保证谷类、蔬果、优质蛋白等各类食物合理摄入。多吃富含膳食纤维的粗粮,像燕麦、糙米,可促进肠道蠕…...
实验二 两个多位十进制数相加实验
一、实验目的 1.掌握汇编子程序的编写方法。 2.掌握循环程序的设计方法。 二、实验内容 将键盘输入的两个5位十进制数相加,在屏幕上显示相加的结果。 三、实验要求 1.显示格式:被加数加数相加的结…...
多模态大模型MLLM基础训练范式 Pre-train + Instruction FineTuning
多模态大模型Pre-train 为了在图文嵌入空间中更好地对齐视觉和文本信息。为此,使用图像-文本对(image-caption style data),表示为 ( X , Y a ) (\mathbf{X}, Y_a) (X,Ya),其中: X \mathbf{X} X&#x…...
2025.4.15六年之约day11
六年之约已经断更好几个月了,当初六年之约是当做日记来写的,然后被同事刷到了,被谈及的时候挺尴尬的,毕竟里面记录的是我的所思所想。在互联网下,是不适合发布日记的,但我又爱记录所思所想所做。 不知道距…...
Rust学习之实现命令行小工具minigrep(二)
Rust学习之实现命令行小工具minigrep(二) Rust学习之实现命令行小工具minigrep(一) 前言 继续记录一下Rust 语言学习过程,上次写了一个命令行查找字符串的小项目minigrep。学习完了闭包(Closures&#x…...
Access Token 和 Refresh Token 的双令牌机制,维持登陆状态
目录 1. 双令牌机制2. 工作流程3. 客户端实现4. 服务器端实现5. 注意事项拓展:Token在客户端安全存储的几种方式 为了实现客户端在 JWT Token 过期后自动更新 Token,通常会采用 Access Token 和 Refresh Token 的双令牌机制。以下是实现自动更新 Token 的…...
前端 -- uni-app 的 splitChunks 分包详解与实战!
全文目录: 开篇语📝 前言📖 目录🌟 什么是 splitChunks?🛠 splitChunks 的核心原理📂 文件拆分的机制⚙️ 配置选项✨ splitChunks 实战案例1️⃣ 项目初始化2️⃣ 编写页面逻辑3️⃣ 配置 splitChunks4️⃣ 查看效果🧩 splitChunks 的高级用法与优化🔍 优化一…...
【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 目录 检查硬件和驱动状态 测试RDMA通信 报错修复 对于交换机的配置,可以看这篇: 【教程】详解配置多台主机通过交换机实现互…...