【linux深入剖析】基础IO操作 | 使用Linux库函数实现读写操作 | 文件相关系统调用接口
CSDN 2024-06-19 08:07:03 阅读 98
目录
前言1.复习C文件IO相关操作1.1 fopen函数1.1.1 w模式1.1.2 a模式 1.2 fwrite函数函数介绍函数使用 1.3 fgets函数 2.程序默认打开的文件流3. 系统文件I/O标志位flagw清空文件a追加文件r读取文件内容open函数返回值
前言
文件 = 内容 + 属性访问文件之前,都得先打开,然后再进行修改文件的操作,通过执行代码的方式完成修改,这期间文件必须被加载到内存中—内存文件打开文件的操作是通过进程的形式来实现的一个进程可以打开多个文件进程没有打开的文件会被存在在磁盘中—磁盘文件本节的学习我们需要弄清几个概念
一定时间段内,系统中存才多个进程,也可能同时存在更多的被打开的文件,操作系统(OS)要不要管理多个被进程打开的文件呢?
这个答案是肯定的,但是我们更需要理解的是其如何对这些进行管理的?
先描述再组织
内核中一定要有描述被打开文件的结构体,并用其定义对象
1.复习C文件IO相关操作
C语言提供了一些文件操作函数,用于对文件进行读写和管理。以下是一些常用的C语言文件操作函数:
fclose():关闭文件。语法为:int fclose(FILE *stream);fgetc():从文件中读取一个字符。语法为:int fgetc(FILE *stream);fputc():将一个字符写入文件。语法为:int fputc(int c, FILE *stream);fgets():从文件中读取一行字符串。语法为:char *fgets(char *str, int n, FILE *stream);fputs():将一个字符串写入文件。语法为:int fputs(const char *str, FILE *stream);fprintf():将格式化的数据写入文件。语法为:int fprintf(FILE *stream, const char *format, …);fscanf():从文件中读取格式化的数据。语法为:int fscanf(FILE *stream, const char *format, …);fseek():设置文件指针的位置。语法为:int fseek(FILE *stream, long offset, int origin);ftell():获取当前文件指针的位置。语法为:long ftell(FILE *stream);rewind():将文件指针重置到文件开头。语法为:void rewind(FILE *stream);feof():检查文件结束标志。语法为:int feof(FILE *stream);
以上是一些常用的C语言文件操作函数,你可以根据需要选择适合的函数来进行文件操作。
1.1 fopen函数
我们先认识一下fopen函数
fopen是一个C语言中的标准库函数,用于打开文件。它的原型如下:
FILE *fopen(const char *filename, const char *mode);
其中,filename是要打开的文件名,mode是打开文件的模式。fopen函数返回一个指向FILE结构的指针,该结构用于后续对文件进行读写操作。
常见的文件打开模式有以下几种:
“r”:以只读方式打开文件,文件必须存在。“w”:以写入方式打开文件,如果文件不存在则创建,如果文件存在则清空内容。“a”:以追加方式打开文件,如果文件不存在则创建。“rb”、“wb”、“ab”:以二进制模式打开文件,用于处理二进制文件。fopen函数还可以用于打开其他类型的文件,例如网络流、设备文件等。
linux系统下的打开模式:
注意,在使用完文件后,需要使用fclose函数关闭文件,以释放资源。
1.1.1 w模式
#include<stdio.h>int main(){ FILE *fp = fopen("./log.txt","w"); if(fp == NULL) { perror("fopen"); return 1; } fclose(fp); return 0;}
运行结果:
加上一点文件操作:
#include<stdio.h>int main(){ FILE *fp = fopen("./log.txt","w"); if(fp == NULL) { perror("fopen"); return 1; } //文件操作 const char *str = "hello file\n"; fputs(str,fp); fclose(fp); return 0;}
运行结果:
将文件操作注释掉:
#include<stdio.h>int main(){ FILE *fp = fopen("./log.txt","w"); if(fp == NULL) { perror("fopen"); return 1; } //文件操作 //const char *str = "hello file\n"; //fputs(str,fp); fclose(fp); return 0;}
运行结果:
结果刚刚写入的hello file被清空了
结论:
以W方式访问文件时,首先清空原始文件,如果没有文件,会进行创建文件,在文件的开头对文件进行修改。
echo命令+重定向:
可以发现其本质就是我们的w模式,文本文件会被清空然后再往其中进行写入,我们单纯使用重定向(大于符号),文件会直接被清空
1.1.2 a模式
#include<stdio.h>int main(){ FILE *fp = fopen("./log.txt","a"); if(fp == NULL) { perror("fopen"); return 1; } //文件操作 const char *str = "hello file\n"; fputs(str,fp); fclose(fp); return 0;}
运行结果:
a模式本质也是写入,只不过其写入是追加在文件末尾
echo命令+追加重定向:
可以发现其本质就是我们的a模式
1.2 fwrite函数
函数介绍
fwrite是C语言中的一个函数,用于将数据块写入文件。它的原型如下:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
参数说明:
ptr:
指向要写入的数据块的指针。
size:
每个数据块的字节数。
count:
要写入的数据块的数量。
stream:
指向要写入的文件的指针。
fwrite函数将数据块从内存写入到文件中。它会返回成功写入的数据块数量。如果返回值与count不相等,可能表示写入失败或者到达了文件末尾。
使用fwrite函数时,需要注意以下几点:
写入的数据块大小应与实际数据类型相匹配,以避免数据损坏或类型错误。写入的文件必须以二进制模式打开,以确保数据以原始格式写入文件。写入的文件必须存在且可写。
函数使用
#include<stdio.h>#include<string.h>int main(){ FILE *fp = fopen("./log.txt","a"); if(fp == NULL) { perror("fopen"); return 1; } //文件操作 const char *str = "hello file\n"; fputs(str,fp); int count = 5; while(count--) { fwrite(str,strlen(str),1,fp); } fclose(fp); return 0;}
运行结果:
对log.txt文件中写入了6个hello file,fputs写了一个,fwrite写了5个
1.3 fgets函数
fgets函数会从指定的文件流中读取一行字符串,并将其存储到str所指向的字符数组中。它会读取n-1个字符,或者直到遇到换行符(‘\n’)为止。如果成功读取到字符串,则会在字符串末尾添加一个空字符(‘\0’)作为结束标志。fgets函数的返回值是一个指向str的指针,如果成功读取到字符串,则返回该指针;如果到达文件末尾或发生错误,则返回NULL。需要注意的是,fgets函数会将换行符也读取进来,并存储在字符串中。如果不希望包含换行符,可以使用字符串处理函数(如strlen和strtok)来去除它。fgets是C语言中的一个函数,用于从文件或标准输入流中读取一行字符串。它的函数原型如下:
char *fgets(char *str, int n, FILE *stream);
其中,str是一个指向字符数组的指针,用于存储读取到的字符串;n是一个整数,表示最多读取的字符数(包括换行符和空字符);stream是一个指向FILE结构的指针,表示要读取的文件流。
#include<stdio.h>#include<string.h>int main(){ FILE *fp = fopen("./log.txt","r"); if(fp == NULL) { perror("fopen"); return 1; } char buffer[64]; while(1) { char *r = fgets(buffer,sizeof(buffer),fp); if(!r) break; printf("%s",buffer); } fclose(fp); return 0;}
2.程序默认打开的文件流
C默认会打开三个输入输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针
stdin、stdout和stderr是与输入输出相关的三个标准流。它们在计算机程序中起着重要的作用。
stdin(标准输入):stdin是程序接收输入数据的标准输入流。它通常与键盘输入相关联,用于从用户那里接收输入。程序可以通过读取stdin来获取用户输入的数据。
stdout(标准输出):stdout是程序输出结果的标准输出流。它通常与屏幕输出相关联,用于向用户显示程序的输出结果。程序可以通过将数据写入stdout来输出结果。
stderr(标准错误):stderr是程序输出错误信息的标准错误流。它通常也与屏幕输出相关联,用于向用户显示程序的错误信息。与stdout不同的是,stderr主要用于输出程序运行过程中的错误和异常信息。
这三个标准流在程序中起着重要的作用,它们可以通过重定向进行控制。例如,可以将stdin重定向到文件中,以便从文件中读取输入;可以将stdout和stderr重定向到文件中,以便将输出结果和错误信息保存到文件中。
stdout就是我们的显示器,于是我们就多了几种打印的方式:
#include<stdio.h>#include<string.h>int main(){ printf("hello printf\n"); fputs("hello file\n",stdout); const char *msg="hello fwrite\n"; fwrite(msg,1,strlen(msg),stdout); fprintf(stdout,"hello fprintf\n"); return 0;}
stdin是程序接收输入数据的标准输入流。我们可以这样输入:
#include<stdio.h>#include<string.h>int main(){ char buffer[64]; fscanf(stdin,"%s",buffer); printf("%s",buffer); return 0;}
3. 系统文件I/O
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
我们先认识Linux的open接口
Linux的open接口是用于打开文件或创建文件的系统调用函数。它的原型如下:
man 2 open
其中,pathname参数是文件路径名,flags参数指定了打开文件的方式和行为,mode参数用于指定新创建文件的权限。
flags参数可以使用以下常用的标志位进行组合:
O_RDONLY
:只读方式打开文件。
O_WRONLY
:只写方式打开文件。
O_RDWR
:读写方式打开文件。
O_CREAT
:如果文件不存在,则创建文件。
O_EXCL
:与O_CREAT一起使用,如果文件已存在则返回错误。
O_TRUNC
:如果文件存在且以写方式打开,则将其长度截断为0。
O_APPEND
:以追加方式打开文件,即每次写操作都追加到文件末尾。
mode参数用于指定新创建文件的权限,它是一个八进制数,常用的权限值有:
S_IRUSR
:用户可读权限。
S_IWUSR
:用户可写权限。
S_IXUSR
:用户可执行权限。
S_IRGRP
:组可读权限。
S_IWGRP
:组可写权限。
S_IXGRP
:组可执行权限。
S_IROTH
:其他人可读权限。
S_IWOTH
:其他人可写权限。
S_IXOTH
:其他人可执行权限。
如果open函数调用成功,则返回一个非负整数的文件描述符,该文件描述符可以用于后续的读写操作。如果调用失败,则返回-1,并设置errno变量来指示错误原因。
标志位flag
标志位flag类似于一个一个宏,我们在如下代码使用按位与实现对12345的输出,另一方面模拟实现了open接口里的flag
#include<stdio.h>#define ONE 1#define TWO (1<<1)#define THREE (1<<2)#define FOUR (1<<3)#define FIVE (1<<4)void Print(int flag){ if(flag & ONE) printf("1\n"); if(flag & TWO) printf("2\n"); if(flag & THREE) printf("3\n"); if(flag & FOUR) printf("4\n"); if(flag & FIVE) printf("5\n");}int main(){ Print(ONE); printf("-------------------------\n"); Print(TWO); printf("-------------------------\n"); Print(ONE|TWO); printf("-------------------------\n"); Print(THREE|FOUR|FIVE); printf("-------------------------\n"); Print(ONE|TWO|THREE|FOUR|FIVE); return 0;}
w清空文件
我们可以使用open接口以写的形式打开文件#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){ int fd = open("log.txt",O_WRONLY); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } return 0;}
因为什么都没做。
删掉log.txt后,便会报错
我们可以使用open接口以写的形式打开文件,并实现文件不存在时创建文件
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } return 0;}
这里文件权限上出现了S,这是我们从没见过的参数,也就是权限位乱码了,这是因为我们使用C语言新建的文件,并不是系统默认的
所以我们在实现创建文件的操作时,我们需要告诉系统文件的权限
我们可以使用open接口以写的形式打开文件,并实现文件不存在时创建文件(改良)
mode参数那里我们填入普通文件权限0666权限掩码
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT,0666); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } return 0;}
我们可以使用open接口以写的形式打开文件,并实现文件不存在时创建文件,往其中写入文件
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT,0666); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } const char* str="hello system call\n"; write(fd,str,strlen(str)); close(fd); return 0;}
我们可以使用open接口以写的形式打开文件,并实现文件不存在时创建文件,往其中写入文件,文件里有内容验证是否去清空文件内容重新写入
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT,0666); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } const char* str="aaaa\n"; write(fd,str,strlen(str)); close(fd); return 0;}
在这里并没有清空源文件的内容,只是在开头用aaaa\n替换了开头五个字符长度的字符串
实现清空(实现w模式)
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } const char* str="aaaa\n"; write(fd,str,strlen(str)); close(fd); return 0;}
a追加文件
#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>int main(){ int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666); //以写的方式打开 if(fd == -1) { perror("open"); return 1; } const char* str="aaaa\n"; write(fd,str,strlen(str)); close(fd); return 0;}
r读取文件内容
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(){ int fd = open("log.txt", O_RDONLY);if(fd < 0){ perror("open");return 1;} const char *msg = "hello bit!\n"; char buf[1024]; while(1) { ssize_t s = read(fd, buf, strlen(msg));//类比write if(s > 0) { printf("%s", buf); } else { break; } }close(fd);return 0;}
open函数返回值
open的函数返回值不是int吗?我们来输出一下
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(){ int fd1 = open("log.txt", O_WRONLY); int fd2 = open("log.txt", O_WRONLY); int fd3 = open("log.txt", O_WRONLY); int fd4 = open("log.txt", O_WRONLY); int fd5 = open("log.txt", O_WRONLY); printf("fd1: %d\n",fd1); printf("fd2: %d\n",fd2); printf("fd3: %d\n",fd3); printf("fd4: %d\n",fd4); printf("fd5: %d\n",fd5); return 0;}
这里为什么是34567?为啥不见012呢?
这是因为012已经被默认使用了
0:标准输入
1:标准输出
2:标准错误
在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数
上面的 fopen fclose fread fwrite
都是C标准库当中的函数,我们称之为库函数(libc)。而,open close read write lseek
都属于系统提供的接口,称之为系统调用接口
我们之前的标准输入、标准输出、标准错误
其类型都是FILE,这其实是我们C语言库里的一个结构体,如果他们能变成我们的012,必须是在其内部封装了
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(){ printf("%d\n",stdin->_fileno); printf("%d\n",stdout->_fileno); printf("%d\n",stderr->_fileno); int fd1 = open("log.txt", O_WRONLY); int fd2 = open("log.txt", O_WRONLY); int fd3 = open("log.txt", O_WRONLY); int fd4 = open("log.txt", O_WRONLY); int fd5 = open("log.txt", O_WRONLY); printf("fd1: %d\n",fd1); printf("fd2: %d\n",fd2); printf("fd3: %d\n",fd3); printf("fd4: %d\n",fd4); printf("fd5: %d\n",fd5); return 0;}
结论:
C语言的文件接口,本质就是封装了系统调用!
我们使用的fopen就相当于我们的open接口调用了不同的标志位,也就是我们的C语言对于文件的接口都是对系统调用进行封装的结果
为什么C语言要封装?
这是为了C语言的可移植性,在不同系统都可以调用,保证C语言的平台性
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。