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. 变参函数接口
vfscanf
、vscanf
:与fscanf
、scanf
类似,但使用可变参数列表。vfprintf
、vprintf
:与fprintf
、printf
类似,但使用可变参数列表
注意:操作系统会默认打开三个文件,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 资源。文件描述符通常是通过 open
、socket
、pipe
等系统调用获得的。
如果 close
调用成功,它会返回 0
,表示文件描述符已成功关闭,相关的系统资源已被释放。
若调用失败,close
会返回 -1
,并且会设置全局变量 errno
来指示具体的错误原因。
write系统调用接口
fd:这是一个整数类型的文件描述符,代表了要写入数据的目标文件、设备或者其他 I/O 资源。通常,文件描述符是通过 open
、socket
、pipe
等系统调用获得的。例如,标准输出的文件描述符是 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: 这是一个已存在的有效的文件描述符,代表着要复制的源文件描述符。该文件描述符必须已经通过 open
、socket
、pipe
等系统调用打开。
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不管你是啥,都是文件,全部的驱动就是对一个硬件的读写方法,打开一个文件就会有一个函数指针去调对应的驱动方法