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

Linux基础IO

概念:IO就是内存和外设之间的数据传输过程

c文件IO相关操作

在 C 语言中,FILE* 是一种用于文件操作的指针类型。它被定义在 <stdio.h> 头文件中,用于表示一个文件流,是对文件进行各种操作(如读取、写入、定位等)的关键数据结构。

简单来说:FILE是一个typedef过的结构体,FILE 是一个结构体类型,包含了与文件相关的各种信息,如文件的当前位置、缓冲区状态、文件描述符等。

1. 文件打开与关闭

  • fopen
    • 功能:打开一个文件并返回一个指向 FILE 对象的指针。
    • 原型FILE *fopen(const char *filename, const char *mode);
    • 参数filename 是要打开的文件的名称,mode 是打开文件的模式(如 "r" 表示只读,"w" 表示写入,"a" 表示追加等)。
    • "r":以只读模式打开文件。文件必须存在,否则 fopen 会返回 NULL。此模式下只能从文件中读取数据,不能写入。
    • "w":以写入模式打开文件。如果文件不存在,就会创建一个新文件;若文件已存在,其内容会被清空。该模式下只能向文件写入数据,不能读取。
    • "a":以追加模式打开文件。若文件不存在,会创建一个新文件;若文件已存在,写入的数据会被添加到文件末尾。此模式下只能向文件写入数据,不能读取。
  • freopen(了解)
    • 功能:关闭一个已打开的流,并将该流重定向到指定的文件。
    • 原型FILE *freopen(const char *filename, const char *mode, FILE *stream);
    • 参数filename 是新文件的名称,mode 是打开模式,stream 是要重定向的流。也就是说,原本与 stream 关联的文件会被关闭,然后 stream 会与新的文件(由 filename 指定)建立关联。
  • fclose
    • 功能:关闭一个已打开的文件流。
    • 原型int fclose(FILE *stream);
    • 参数stream 是要关闭的文件流指针。

2. 文件读写操作

字符读写
  • fgetc
    • 功能:从文件流中读取一个字符。
    • 原型int fgetc(FILE *stream);
    • 参数stream 是要读取的文件流指针。
  • fputc
    • 功能:将一个字符写入文件流。
    • 原型int fputc(int c, FILE *stream);
    • 参数c 是要写入的字符,stream 是目标文件流指针。
  • getc
    • 功能:与 fgetc 类似,从文件流中读取一个字符,但通常作为宏实现。
    • 原型int getc(FILE *stream);
  • putc
    • 功能:与 fputc 类似,将一个字符写入文件流,通常作为宏实现。
    • 原型int putc(int c, FILE *stream);
  • getchar
    • 功能:从标准输入读取一个字符。
    • 原型int getchar(void);
  • putchar
    • 功能:将一个字符写入标准输出。
    • 原型int putchar(int c);

注意:也就是getc和putc是一个宏函数,会在调用它的地方展开,有一定的宏风险 。getchar 和putchar函数的功能是从标准输入(通常是键盘)读取一个字符。它不需要传入文件流指针,因为默认是从标准输入流 stdin 读取数据。 

字符串读写
  • fgets
    • 功能:从文件流中读取一行字符串。
    • 原型char *fgets(char *s, int size, FILE *stream);
    • 参数s 是存储读取字符串的缓冲区,size 是缓冲区的大小,stream 是要读取的文件流指针。
  • fputs
    • 功能:将一个字符串写入文件流。
    • 原型int fputs(const char *s, FILE *stream);
    • 参数s 是要写入的字符串,stream 是目标文件流指针。
  • gets
    • 功能:从标准输入读取一行字符串(由于存在缓冲区溢出风险,不建议使用)。
    • 原型char *gets(char *s);
  • puts
    • 功能:将一个字符串写入标准输出,并在末尾添加换行符。
    • 原型int puts(const char *s);
格式化读写
  • fscanf
    • 功能:从文件流中按指定格式读取数据。
    • 原型int fscanf(FILE *stream, const char *format, ...);
    • 参数stream 是要读取的文件流指针,format 是格式化字符串,后续为接收数据的变量地址。
  • fprintf
    • 功能:将数据按指定格式写入文件流。
    • 原型int fprintf(FILE *stream, const char *format, ...);
    • 参数stream 是目标文件流指针,format 是格式化字符串,后续为要写入的数据。
  • scanf
    • 功能:从标准输入按指定格式读取数据。
    • 原型int scanf(const char *format, ...);
  • printf
    • 功能:将数据按指定格式写入标准输出。
    • 原型int printf(const char *format, ...);
二进制读写
  • fread
    • 功能:从文件流中读取二进制数据。
    • 原型size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    • 参数ptr 是存储读取数据的缓冲区指针,size 是每个数据项的大小,nmemb 是要读取的数据项数量,stream 是要读取的文件流指针。
  • fwrite
    • 功能:将二进制数据写入文件流。
    • 原型size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    • 参数ptr 是要写入的数据的缓冲区指针,size 是每个数据项的大小,nmemb 是要写入的数据项数量,stream 是目标文件流指针。

3. 文件定位操作

  • fseek
    • 功能:设置文件流的文件位置指示器。
    • 原型int fseek(FILE *stream, long offset, int whence);
    • 参数stream 是文件流指针,offset 是偏移量,whence 是起始位置(如 SEEK_SET 表示文件开头,SEEK_CUR 表示当前位置,SEEK_END 表示文件末尾)。
  • ftell
    • 功能:返回文件流的当前文件位置指示器。
    • 原型long ftell(FILE *stream);
    • 参数stream 是文件流指针。
  • rewind
    • 功能:将文件流的文件位置指示器设置到文件开头。
    • 原型void rewind(FILE *stream);
    • 参数stream 是文件流指针。

4. 文件状态检查

  • feof
    • 功能:检查文件流是否到达文件末尾。
    • 原型int feof(FILE *stream);
    • 参数stream 是文件流指针。
  • ferror
    • 功能:检查文件流是否发生错误。
    • 原型int ferror(FILE *stream);
    • 参数stream 是文件流指针。
  • clearerr
    • 功能:清除文件流的错误标志和文件结束标志。
    • 原型void clearerr(FILE *stream);
    • 参数stream 是文件流指针。

5. 临时文件操作

  • tmpfile
    • 功能:创建一个临时二进制文件并以读写模式打开。
    • 原型FILE *tmpfile(void);
  • tmpnam
    • 功能:生成一个唯一的临时文件名。
    • 原型char *tmpnam(char *s);
    • 参数s 是存储临时文件名的缓冲区,如果为 NULL,则返回一个指向静态缓冲区的指针。

6. 变参函数接口

  • vfscanfvscanf:与 fscanfscanf 类似,但使用可变参数列表。
  • vfprintfvprintf:与 fprintfprintf 类似,但使用可变参数列表

注意:操作系统会默认打开三个文件,stdin&stdout&stderr 

  • stdin 用于程序获取外部输入,默认是键盘输入。
  • stdout 用于程序向外部输出正常信息,默认显示在终端屏幕。
  • stderr 用于输出程序运行过程中的错误信息,确保错误信息能及时被用户看到。

系统文件I/O 

open系统调用接口 

 可以看到在2号手册open系统接口调用

先看第二个函数,第一个是指向字符串的指针,代表要打开或创建的文件的路径名。路径名可以是绝对路径(如 /home/user/file.txt),也可以是相对路径(如 file.txt)。

第二个是标记位(O_RDONLY等等不同的宏有不同的比特位)

第三个就是权限(这里的权限是文件权限0666等)

返回值:成功返回new file descriptor文件描述符,失败返回-1

第一个函数与第二个的区别:第一个是文件存在时,第二个是文件文件不存在时,所以要指明你创建时候的文件权限

深入理解标记位:

标记位传参:

#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
void Show(int flags)
{if(flags&ONE){printf("one");}if(flags&TWO){printf("two");}if(flags&THREE){printf("three");}
}
int main()
{Show(ONE);Show(ONE|TWO);
}

可以看到ONE等宏定义是1通过左移(也就是向高位移动,低位补0)得到的,按位或|操作能把不同的比特位变为1,flags为传进来的标记位,按位与后如果对应的位都是1就是true,如果不是,则为false,因此我们可以通过标记位然后通过不同的if语句进行不同的流。

本质就是一种标记位传参 

close系统调用接口

 fd该参数是一个整数类型的文件描述符,它代表着要关闭的文件或其他 I/O 资源。文件描述符通常是通过 opensocketpipe 等系统调用获得的。

如果 close 调用成功,它会返回 0,表示文件描述符已成功关闭,相关的系统资源已被释放。

若调用失败,close 会返回 -1,并且会设置全局变量 errno 来指示具体的错误原因。

write系统调用接口

fd:这是一个整数类型的文件描述符,代表了要写入数据的目标文件、设备或者其他 I/O 资源。通常,文件描述符是通过 opensocketpipe 等系统调用获得的。例如,标准输出的文件描述符是 1,若你将 fd 设置为 1,数据就会被写入到标准输出(通常是终端屏幕)。

buf:缓冲区

count:要写多少(以字节为单位)

 返回值:

成功返回实际写入的字节数,这个值可能小于 count。例如,当磁盘空间不足或者到达文件系统的限制时,实际写入的字节数可能会少于请求写入的字节数。

如果写入操作失败,write 会返回 -1,并且会设置全局变量 errno 来指示具体的错误原因。

read系统调用接口

与write一样 

lseek系统调用接口 

fd:文件描述符

offset :这是一个 off_t 类型的偏移量,用于指定相对于 whence 参数所指定位置的偏移字节数。offset 可以是正数、负数或零。正数表示向文件末尾方向移动,负数表示向文件开头方向移动,零表示不移动

whence:这是一个整数参数,用于指定 offset 的起始位置。它有以下三个常用的取值:

  • SEEK_SET:表示从文件的开头开始计算偏移量。此时,offset 必须是非负的。
  • SEEK_CUR:表示从文件的当前偏移量位置开始计算偏移量。offset 可以是正数、负数或零。
  • SEEK_END:表示从文件的末尾开始计算偏移量。offset 可以是正数、负数或零。如果 offset 为正数,则会将文件偏移量设置为文件末尾之后的位置;如果 offset 为负数,则会将文件偏移量设置为文件末尾之前的位置。

返回值: 如果 lseek 调用成功,它会返回新的文件偏移量,以字节为单位,从文件开头开始计算                 如果调用失败,lseek 会返回 -1,并且会设置全局变量 errno 来指示具体的错误原因。

简单编写一个系统调用接口实现的文件打开和关闭

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {int fd;char buffer[] = "Hello, World!";char read_buffer[20];off_t new_offset;// 以读写模式打开文件,如果文件不存在则创建fd = open("test.txt", O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");return 1;}// 写入数据到文件ssize_t bytes_written = write(fd, buffer, strlen(buffer));if (bytes_written == -1) {perror("write");close(fd);return 1;}// 将文件偏移量移动到文件开头new_offset = lseek(fd, 0, SEEK_SET);if (new_offset == -1) {perror("lseek");close(fd);return 1;}// 从文件中读取数据ssize_t bytes_read = read(fd, read_buffer, sizeof(read_buffer));if (bytes_read == -1) {perror("read");close(fd);return 1;}read_buffer[bytes_read] = '\0';printf("读取的数据: %s\n", read_buffer);// 关闭文件if (close(fd) == -1) {perror("close");return 1;}return 0;
}

fopen与open:fopen的各个模式就是open的标记位传参的不同 

总结: c语言库函数fopen等都是基于系统调用接口open等的封装

如何理解文件???

文件=文件内容+文件属性

操作文件的方式=操作文件内容+操作文件属性

一个文件没有被打开,可以直接进行文件访问吗???(答案当然是不能,一个文件要被访问必须要先被打开)

由谁打开->用户进程->调接口->os来执行

我操作的文件会把系统所有的都打开吗->不是->(a.被打开的文件b.没有被打开的文件)

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

 

一个文件就是一个struct file结构体对象:struct file 包含了文件的各种信息,如文件指针、文件状态标志、文件操作函数指针,里面还有一个字段去存文件描述符

文件描述符表:一个结构体struct files_struct:里面有一个数组:struct file*fd_arr[](这是一个指针数组),数组中的每一个元素都是一个指向struct file的指针

文件描述符的本质就是数组的下标,结合我们之前讲的,系统会自动打开stdin,stdout,stderr,对应的文件描述符就是0/1/2,以后打开的文件就是3/4/5,分配规则:从小到大且没有被占有的

如果你把1号关闭close(1),并且打开了别的文件open,它就会占用1号,那你使用那些默认输入到屏幕的函数,例如putchar,它就会输入到这个文件中

进程A和B以及每个打开的进程,里面有一个struct files_struct* files指针指向的是文件描述符表,这些进程共享一个文件描述符表

重定向的本质:

使用echo重定向,然后vim打开可以看到test文件中有,hello world

echo >  会删除原来文件的内容,在输入到test

echo >> 不会删除原来文件的内容,追加

重定向的本质就是修改fd,例如刚刚的测试,它是close(1)后在open(test),然后这个test会变成1号,之后你输入内容就会默认输入到test文件中,而不是屏幕

但这个做法有点挫

dup2系统调用接口

oldfd: 这是一个已存在的有效的文件描述符,代表着要复制的源文件描述符。该文件描述符必须已经通过 opensocketpipe 等系统调用打开。

newfd:这是目标文件描述符,dup2 会将 oldfd 关联的 I/O 资源复制到 newfd 上。如果 newfd 已经打开,dup2 会先关闭 newfd,然后再进行复制操作。

返回值:如果 dup2 调用成功,它会返回新的文件描述符 newfd,这个返回值和传入的 newfd 是相同的。若调用失败,dup2 会返回 -1,并且会设置全局变量 errno 来指示具体的错误原因。

功能及作用

重定向I/O:dup2 最常见的用途是实现 I/O 重定向。例如,你可以将标准输出(文件描述符为 1)重定向到一个文件,这样程序的输出就会被写入到该文件中,而不是显示在终端屏幕上。 

文件描述符的复用:通过 dup2,可以将一个文件描述符关联的资源映射到另一个文件描述符上,实现资源的复用,避免重复打开文件或设备。

这样你就可以不用close(1),而是直接调用dup2实现重定向,简单来说就是把文件描述符表中oldfd的地址拷贝到newfd中,这样两个都指向你的文件

Linux下一切皆文件,如何理解???

 

os不管你是啥,都是文件,全部的驱动就是对一个硬件的读写方法,打开一个文件就会有一个函数指针去调对应的驱动方法

http://www.xdnf.cn/news/2780.html

相关文章:

  • RFID智能书架:图书馆数字化转型的新核心技术
  • 使用vue3 脚手架创建项目
  • 【AI Weekly】AI前沿热点周刊(4.21~4.27)
  • 考研408-计算机组成原理冲刺考点(1-3章)
  • 状态模式 (State Pattern)
  • Ubuntu安装SRS流媒体服务
  • [实战] IRIG-B协议详解及Verilog实现(完整代码)
  • 第十三节:实战与工程化高频题-TypeScript集成要点
  • 香港科技大学广州|智能交通学域博士招生宣讲会—电子科技大学
  • css网格布局Grid
  • 在服务器中,搭建FusionCompute,实现集群管理
  • Qt/C++面试【速通笔记五】—子线程与GUI线程安全交互
  • AWS PrivateLink vs Lattice:深度解析两大网络服务的异同
  • 恰好边数限制的最短路(边的数量很大)
  • 《淘宝 API 数据湖构建:实时商品详情入湖 + Apache Kafka 流式处理指南》
  • MySQL最新版9.3.0安装教程
  • PyCharm 2023升级2024 版本
  • Linux:ftp 配置实验
  • terraform使用workspace管理多工作环境
  • List--链表
  • 【C++ 核心知识点面试攻略:从基础到实战(上位机开发视角)】
  • Linux调试器 - gdb使用指南
  • 【虚幻5蓝图Editor Utility Widget:创建高效模型材质自动匹配和资产管理工具,从3DMax到Unreal和Unity引擎_系列第二篇】
  • Rabbitmq下载和安装(Windows系统,百度网盘)
  • SQL Server 存储过程开发规范
  • 普通IT的股票交易成长史--20250428晚
  • InferType和_checked_type的区别?
  • 开发vue项目所需要安装的依赖包
  • leetcode128-最长连续序列
  • 聊天室系统:多任务版TCP服务端程序开发详细代码解释