【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.理解文件

狭义理解:

  1. 文件在磁盘中。
  2. 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的。
  3. 磁盘是外设(输出设备/输入设备)
  4. 磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出,简称 “IO”。

广义理解:

Linux 下一切皆文件(键盘、显示器、网卡、磁盘……)

文件操作:

  1. 对于 0KB 的空文件是占用磁盘空间的。
  2. 文件 = 文件属性(元数据) + 文件内容。
  3. 所有的文件操作本质是文件内容操作和文件属性的操作。

系统角度:

  1. 访问文件的前提是先打开文件,谁打开文件呢?答案是 “进程打开文件”。对文件的操作本质就是 “进程对文件的操作”。
  2. 磁盘的管理者是操作系统,访问文件本质就是 “访问磁盘”,只有操作系统才能访问磁盘文件。
  3. 文件的读写本质不是通过C语言/C++的库函数来操作的,这些库函数只是为用户提供方便,而是通过文件相关的系统调用接口来实现的。fopen 和 fclose 封装了操作系统对文件的系统调用。
  4. 操作系统通过 “先描述,再组织” 的方式对文件进行管理。在操作系统内部对被打开的文件创建 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

  1. C 默认会打开三个输入输出流,分别是stdin、stdout、stderr
  2. 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型是文件指针。
#include <stdio.h>extern FILE *stdin;   //标准输入:键盘文件
extern FILE *stdout;  //标准输出:显示器文件
extern FILE *stderr;  //标准错误:显示器文件

5.打开文件的方式

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.系统接口 IO

  1. 打开文件的方式不仅仅是fopen,ifstream等语言层的方案,其实系统才是打开文件最底层的方案。
  2. 不过,在学习系统文件 IO 之前,先要了解下如何给函数传递标志位,该方法在系统文件 IO 接口中会使用到:

1.传递标志位

  1. 当存在多个标记位时,一般的做法是传递多个参数,用起来非常麻烦。
  2. 操作系统采用 “位图” 来 “传递标志位” 的方式,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);
  1. pathname:要打开或创建的目标文件。
  2. flags:文件描述符。打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行 “按位或” 运算,构成 flags。参数:O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读,写打开)。这三个常量,必须指定一个且只能指定一个。O_CREAT:若文件不存在,则创建它。O_APPEND:追加写。
  3. mode:当文件不存在时,以O_WRONLY打开文件,指明创建新文件的访问权限。
  4. 返回值:成功时返回新打开的文件描述符。失败时返回-1
#include <unistd.h>int close(int fd);
  1. fd:文件描述符,open 函数的返回值。
  2. 返回值:成功时返回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);
  1. fd:文件描述符,open 函数的返回值。
  2. buf:指向要写入的数据的指针。
  3. count:指定了要写入的字节数。
  4. 返回值:成功时返回真实写入文件的字节数,失败时返回-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);
  1. fd:文件描述符。
  2. buf:将数据读入到该指针 buffer 指向的字符串中。
  3. count:需要读取的字节数。
  4. 返回值:成功时返回读取的字节数,失败时返回-1

在这里插入图片描述
在这里插入图片描述

4.文件描述符

1.是什么?

文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了 file 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针 *files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

在这里插入图片描述

  1. 文件描述符:进程对应的文件描述符表的数组下标。
  2. 当用户层进行 open(“log.txt”, “w”) 调用时:操作系统创建新的 struct file,在文件描述符表中找到未被使用的下标,将 struct file 的地址填写进去,此时进程与文件就关联了。
  3. 当用户层进行 read(fd, buffer, sizeof(buffer)) 调用时:操作系统拿着 fd 索引文件描述符表找到对应的 struct file,每一个 struct file 都对应内存中的一个 “文件缓冲区”,操作系统先将磁盘文件中的内容预加载到文件缓冲区中,再将文件缓冲区中的内容拷贝到 buffer 中。read 函数本质:内核到用户空间的拷贝函数。
  4. 当用户层进行 write(fd, buffer, strlen(buffer)) 调用时:先将 buffer 指向的内容拷贝到文件缓冲区中,再将缓冲区中的内容定期刷新到磁盘文件中。
  5. 对文件做任何操作,都必须先将文件加载(磁盘->内存的拷贝)到内核对应的文件缓冲区中。

内核代码如下:
在这里插入图片描述

通过 open 系统调用的返回值,得知文件描述符(fd)是一个整数。 如下代码所示:

在这里插入图片描述
在这里插入图片描述
思考:文件描述符为什么从3开始,值为0、1、2 的文件描述符是什么?

  1. 文件描述符值为0、1、2 分别是:标准输入、标准输出、标准错误。
  2. C语言中的 fopen 返回值 FILE* 中的 FILE 是一个结构体。
  3. 在操作系统接口层面上,只认文件描述符 fd
  4. 结构体 FILE 一定封装了文件描述符 fd

在这里插入图片描述

封装:

  1. 在 Windows、Linux 等不同的平台下的系统调用不同,使用系统调用不具备可移植性。
  2. C/C++封装各个平台关于文件操作的系统调用,在不同的平台下通过条件编译进行裁剪,成为语言级接口,具备可移植性。
  3. 语言增加可移植性的原因:为了让更多人使用,防止被淘汰。

2.分配规则

文件描述符的分配规则:在 struct file* array[] 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

在这里插入图片描述

3.重定向原理

在这里插入图片描述

解释图如下:

在这里插入图片描述
在这里插入图片描述

  1. printf 函数就是往 stdout 文件中打印内容,也就是文件描述符值为 1 的文件,但修改了 fd_array[1] 的指向时,便打印到了 log.txt 文件中。
  2. 更改文件描述符表中 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.标准错误重定向

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 思考:都是输出到显示器中,为什么要区分标准输出printf、cout和标准错误perror、cerr / 为什么存在标准错误?
  2. 答案:标准输出和标准错误占用不同的文件描述符。虽然都是显示器,但是可以通过重定向,将常规消息和错误消息分离。

在这里插入图片描述

将标准输出和标准错误都重定向到一个文件中该如何做呢?

在这里插入图片描述

6.自定义shell添加重定向功能

  1. 如果内建命令做重定向,需要更改 shell 的标准输入、输出、错误。此时需要创建临时文件,类似两个整数交换的过程,进行一次重定向后需要恢复。
  2. 一个文件可以被多个进程打开。若一个进程将其中一个文件关闭,就会影响其它正在读取该文件的进程。所以 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.理解一切皆"文件"

在这里插入图片描述

  1. 首先在windows中是文件的东西,它们在linux中也是文件。其次一些在windows中不是文件的东西(进程、磁盘、显示器、键盘)这样硬件设备也被抽象成了文件,你可以使用访问文件的方法访问它们获得信息。甚至管道也是文件,网络编程中的socket(套接字)这样的东西,使用的接口跟文件接口也是一致的。
  2. 这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 PIPE)的操作都可以用 read 函数来进行。几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。
  3. 当打开一个文件时,操作系统为了管理所打开的文件,都会为这个文件创建一个 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.什么是缓冲区?

缓冲区:内存中预留了一段存储空间,这些空间用来缓冲输入或输出的数据,该空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上是什么导致的呢?

在这里插入图片描述

  1. 通过C语言中的库函数(printf/fprintf/fputs/fwrite)输出数据,并不是直接写到文件内核缓冲区中(若是直接写到文件内核缓冲区中,那么进程关闭该文件时,会将缓冲区中的内容刷新到外设中,上面的代码并未出现该结果)。而是在C语言的标准库中,它为每一个打开的文件创建一个用户层,语言级缓冲区。通过系统调用(write)输出数据,直接刷新到文件内核缓冲区中。
  2. 当用户强制刷新 / 筛选条件满足 / 进程退出时:由C标准库根据文件描述符fd + 系统调用(write),将语言级缓冲区中的内容刷新到文件内核缓冲区中。
  3. 在调用 close 之前,进程还未退出,即没有强制刷新 / 筛选条件满足 / 进程退出,数据会一直在C标准库中的语言级缓冲区。当调用 close 时,文件描述符 fd 被关闭,然后进程退出了,此时打算将语言级缓冲区中的内容刷新到文件内核缓冲区,但是调系统调用时 fd 被关了,无法刷新到文件内核缓冲区,就无法看见内容。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.FILE

  1. 问题:每一个文件都有自己对应的语言级缓冲区,那么语言级缓冲区在哪里呢?
  2. 答案: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.缓冲类型

  1. 全缓冲:要求填满整个缓冲区后才进行 I/O 系统调用操作。对于磁盘文件的操作通常使用全缓冲。
  2. 行缓冲:当在输入和输出中遇到换行符时,标准 I/O 库函数将会执行系统调用操作。对于显示器通常使用行缓冲。
  3. 无缓冲:标准 I/O 库不对字符进行缓存,直接调用系统调用。标准出错流 stderr 通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。

在这里插入图片描述

数据交给系统,交给硬件的本质全是拷贝。计算机流动的本质:一切皆拷贝!

4.为什么要引入缓冲区机制

  1. 读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行读、写等操作,那么每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作,即需要执行一次系统调用,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率造成很大的影响。
  2. 为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
  3. 又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,⽤来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
  4. 多次执行 printf 函数可能内部只执行一次系统调用 write,可以减少系统调用的次数,提高效率。

5.设计文件libc库

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

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

相关文章

Apache Doris SelectDB 技术能力全面解析

Apache Doris 是一款开源的 MPP 数据库&#xff0c;以其优异的分析性能著称&#xff0c;被各行各业广泛应用在实时数据分析、湖仓融合分析、日志与可观测性分析、湖仓构建等场景。Apache Doris 目前被 5000 多家中大型的企业深度应用在生产系统中&#xff0c;包含互联网、金融、…

交换机与路由器的默契配合:它们的联系与区别

交换机与路由器的默契配合&#xff1a;它们的联系与区别 一. 交换机与路由器的基本功能1.1 交换机的功能1.2 路由器的功能 二. 交换机和路由器的区别三. 交换机和路由器的联系3.1 数据转发的协作3.2 网络分段与分隔3.3 协同工作提供互联网接入 四. 交换机和路由器的联合应用场景…

【计算机系统结构】MIPSsim

目录 双击MIPSsim.exe 问题1&#xff1a;Microsoft Defender SmartScreen阻止了无法是被的应用启动&#xff0c;运行此应用可能会导致你的电脑存在风险 解决 出现下面的问题的话&#xff0c;建议直接在官网下载 问题2&#xff1a;.NET Framework 3.5安装错误代码0x80240438 …

map 中key 是否可以放置的自定义的对象?

在 Java 中,可以将自定义对象作为 Map 的 Key,但必须满足以下条件: 1. 必须正确重写 hashCode() 和 equals() 方法 原因:Map(如 HashMap)依赖这两个方法确定键的唯一性和存储位置。未正确重写的风险: 无法正确查找值:即使两个对象逻辑上相等,若 hashCode 不同,会被视…

【笔记ing】AI大模型-04逻辑回归模型

一个神经网络结构&#xff0c;其中的一个神经网络层&#xff0c;本质就是一个逻辑回归模型 深度神经网络的本质就是多层逻辑回归模型互相连接或采用一定的特殊连接的方式连接在一起构成的。其中每一个层本质就是一个逻辑回归模型。 逻辑回归模型基本原理 逻辑回归&#xff0…

Android学习总结之算法篇七(图和矩阵)

有向图的深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;的示例&#xff0c;以此来模拟遍历 GC Root 引用链这种有向图结构&#xff1a; 一、深度优先搜索&#xff08;DFS&#xff09; import java.util.*;public class GraphDFS {privat…

熟悉Linux下的编程

可能 目录 熟悉Linux下Python编程的含义及与非Linux环境编程的区别 一、核心含义解析 二、与非Linux环境的关键区别 三、典型应用场景对比 四、能力培养建议 openfoem的下载之路&#xff1a; 方法一&#xff1a;使用cd命令 方法二&#xff1a;使用快捷方式 方法三&am…

c++引入nacos,详细步骤

以下是将Nacos引入C项目的详细步骤&#xff0c;包括安装、配置和代码实现&#xff1a; 1. 安装Nacos服务器 下载Nacos服务器安装包&#xff0c;可以从Nacos官网获取最新版本。 解压安装包并启动Nacos服务器&#xff1a; cd nacos/bin sh startup.sh -m standalone 这将启动…

性能优化实践

4.1 大规模量子态处理的性能优化 背景与问题分析 量子计算中的大规模量子态处理(如量子模拟、量子态可视化)需要高效计算和实时渲染能力。传统图形API(如WebGL)在处理高维度量子态时可能面临性能瓶颈,甚至崩溃(如表格中14量子比特时WebGL的崩溃)。而现代API(如WebGPU…

课堂总结。

第三章第六节 Spark-SQL核心编程&#xff08;五&#xff09;自定义函数&#xff1a;UDF&#xff1a;val sparkConf new SparkConf().setMaster("local[*]").setAppName("SQLDemo")//创建SparkSession对象val spark :SparkSession SparkSession.builder()…

分库分表-除了hash分片还有别的吗?

在分库分表的设计中,除了常见的 Hash 分片,还有多种策略根据业务场景灵活选择。以下是几种主流的分库分表策略及其应用场景、技术实现和优缺点分析,结合项目经验(如标易行投标服务平台的高并发场景)进行说明: 一、常见分库分表策略 1. 范围分片(Range Sharding) 原理:…

AUTOSAR图解==>AUTOSAR_SWS_GPTDriver

AUTOSAR GPT驱动 (通用定时器驱动) 分析 AUTOSAR标准软件规范解析 目录 1. GPT驱动概述 1.1 GPT驱动在AUTOSAR架构中的位置1.2 GPT驱动主要功能 2. GPT驱动模块结构3. GPT驱动初始化流程4. GPT驱动状态机5. GPT驱动错误处理6. GPT预定义定时器7. 总结 1. GPT驱动概述 GPT驱动…

MyBatis持久层框架

MyBatis持久层框架 目录 一、Mybatis简介 1. 简介 2. 持久层框架对比 3. 快速入门&#xff08;基于Mybatis3方式&#xff09; 二、日志框架扩展 1. 用日志打印替代sout 2. Java日志体系演变 3. 最佳拍档用法 4. Lombok插件的使用 4.1 Lombok简介 4.2 Lombok安装 4.3 …

域控制器升级的先决条件验证失败,证书服务器已安装

出现“证书服务器已安装”导致域控制器升级失败时&#xff0c;核心解决方法是卸载已安装的证书服务‌。具体操作如下&#xff1a;‌ ‌卸载证书服务‌ 以管理员身份打开PowerShell&#xff0c;执行命令&#xff1a; Remove-WindowsFeature -Name AD-Certificate该命令会移除A…

VMware虚拟机常用Linux命令进阶指南(一)

摘要&#xff1a;本文涵盖多方面 Linux 命令的使用。包括用户与用户组管理&#xff0c;创建用户和组并设置权限&#xff1b;目录结构操作&#xff0c;涉及创建和更改目录结构&#xff1b;Vim 编辑器及文件归档&#xff0c;有文件创建、编译、合并、打包等任务。 更多优质文章 …

【AI News | 20250415】每日AI进展

AI News 1、字节跳动发布Seaweed-7B视频模型&#xff1a;70亿参数实现音视频同步生成与多镜头叙事 字节跳动推出新一代视频生成模型Seaweed-7B&#xff0c;该模型仅70亿参数却实现多项突破&#xff1a;支持音视频同步生成、多镜头叙事&#xff08;保持角色连贯性&#xff09;、…

如何实现动态请求地址(baseURL)

需求: 在项目中遇到了需要实时更换请求地址,后续使用修改后的请求地址(IP) 例如:原ip请求为http://192.168.1.1:80/xxx,现在需要你点击或其他操作将其修改为http://192.168.1.2:80/xxx,该如何操作 tips: 修改后需要跳转( 修改了IP之前的不可使用,需要访问修改后的地址来操作 …

Open AI 使用篇

一.function Calling 大模型中的 function calling 指的是在人工智能模型&#xff08;如 GPT-4&#xff09;中调用外部函数或API&#xff0c;以便模型能够执行更复杂的任务或获取外部数据。这种方式允许模型在生成回答时不仅仅依赖于内部的训练数据&#xff0c;还能够与外部系…

6.DJI-PSDK:psdk订阅无人机高度/速度/GPS/RTK/时间/经纬度等消息及问题解决

DJI-PSDK:psdk订阅无人机高度/速度/GPS/RTK/时间/经纬度等消息 消息订阅可以获取绝大多数无人机的动态信息,包括无人机的姿态、速度、加速度、角速度、高度、GPS 位置、云 台的角度和状态、飞行模式和飞行状态、电机和电池等各类关键信息。 这些信息并不会“一股脑儿地”全部…

100 个网络安全基础知识

1. 什么是网络安全&#xff1f; 网络安全是指采取必要措施&#xff0c;防范对网络的攻击、侵入、干扰、破坏和非法使用以及意外事故&#xff0c;使网络处于稳定可靠运行的状态&#xff0c;保障网络数据的完整性、保密性、可用性。&#xff08;参考《中华人民共和国网络安全法》…