linux之文件IO

1. C标准IO库函数

1.1 打开/关闭文件--fopen

• Makefile

复制代码
# 解释
#有时编译器不只是 gcc,我们将编译器定义为变量 CC,当切换编译器时只需要更改该变量的定义,而无须更改整个 Makefile。

# $@相当于当前 target 目标文件的名称,此处为 fopen_test。
# $^相当于当前 target 所有依赖文件列表,此处为 fopen_test.c
# ./$@的作用是执行目标文件

# rm ./$@的作用是在执行完毕后删除目标文件,如果没有这个操作,当源文件
#fopen_test.c 未更改时就无法重复执行,会提示:make:"fopen_test"已是最新。
#此处删除目标文件,使得我们在不更改源文件的情况下可以多次执行。

# 所有命令前都添加了"-"符号以忽略错误,确保即便上面的命令执行失败,仍然
#会向下执行。这样做是为了在发生错误时,确保删除目标文件,使得再次执行相同 target
#时不会提示:make:"fopen_test"已是最新,可以重新执行 target 下的命令。

CC := gcc

fopen_test: fopen_test.c
	-$(CC) $^ -o $@
	-./$@
-rm ./$@

• 例子

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
    /* code */

        /* 打开文件
	    char *__restrict __filename: 字符串表示要打开文件的路径和名称
	    char *__restrict __modes: 字符串表示访问模式
	    (1)"r": 只读模式 没有文件打开失败
	    (2)"w": 只写模式 存在文件写入会清空文件,不存在文件则创建新文件
	    (3)"a": 只追加写模式 不会覆盖原有内容 新内容写到末尾,如果文件不存在
	    则创建
	    (4)"r+": 读写模式 文件必须存在 写入是从头一个一个覆盖
	    (5)"w+": 读写模式 可读取,写入同样会清空文件内容,不存在则创建新文件
	    (6)"a+": 读写追加模式 可读取,写入从文件末尾开始,如果文件不存在则创建
	    return: FILE * 结构体指针 表示指向一个文件
	    FILE *fopen (const char *__restrict __filename,
	    const char *__restrict __modes)
    */

    FILE * fp = fopen("a.txt","w");
    if(fp == NULL){
        printf("打开失败\n");

    }else{
        printf("打开成功\n");

    }
    return 0;

1.2 fclose:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
  
    FILE * fp = fopen("a.txt","w");
    if(fp == NULL){
        printf("打开失败\n");

    }else{
        printf("打开成功\n");

    }

    /*
        FILE *__stream: 需要关闭的文件
        return: 成功返回 0 失败返回 EOF(负数) 通常关闭文件失败会直接报错
        int fclose (FILE *__stream)
    */

    int result = fclose(fp);
    if(result == EOF)
        printf("关闭失败,%d\n",EOF);
    else if(result == 0)
        printf("关闭成功\n");

    return 0;
}

1.3 向文件写入数据的函数

1.3.1 fputc:一次只能写入一个字符。例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
  
    FILE * fp = fopen("a.txt","a+");
    if(fp == NULL){
        printf("打开失败\n");

    }else{
        printf("打开成功\n");

    }

    /*写入文件一个字符
        int __c: 写入的 char 按照 AICII 值写入 可提前声明一个 char
        FILE *__stream: 要写入的文件,写在哪里取决于访问权限,在fopen参数那里
        return: 成功返回 char 的值 失败返回 EOF
        int fputc (int __c, FILE *__stream)
    */
    int put_result = fputc('A',fp);

    if(put_result == EOF){
        printf("写入失败,%d\n",EOF);
    }else {
        printf("写入%d成功\n",put_result);
    }


    int result = fclose(fp);
    if(result == EOF)
        printf("关闭失败,%d\n",EOF);
    else if(result == 0)
        printf("关闭成功\n");

    return 0;
}

1.3.2 fputs:一次写入一个字符串,例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
  
    FILE * fp = fopen("a.txt","a+");
    if(fp == NULL){
        printf("打开失败\n");

    }else{
        printf("打开成功\n");

    }
   
    int putc_result = fputc('A',fp);

    if(putc_result == EOF){
        printf("写入失败,%d\n",EOF);
    }else {
        printf("写入%d成功\n",putc_result);
    }

    /*写入文件一个字符串
        char *__restrict __s: 需要写入的字符串
        FILE *__restrict __stream: 要写入的文件,写在哪里取决于访问权限
        return: 成功返回非负整数(一般是 0,1) 失败返回 EOF
        int fputs (const char *__restrict __s, FILE *__restrict __stream)
    */    


    int puts_result = fputs(" love B\n",fp);
    if(puts_result == EOF){
        printf("写入失败,%d\n",EOF);
    }else {
         printf("写入%d成功\n",puts_result);
    }


    int result = fclose(fp);
    if(result == EOF)
        printf("关闭失败,%d\n",EOF);
    else if(result == 0)
        printf("关闭成功\n");

    return 0;
}

1.3.3 fprintf:一次可以写很长的字符串,通过格式化输出的方式,输出一个长字符串,直接写入到文件,例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
  
    FILE * fp = fopen("a.txt","a+");
    if(fp == NULL){
        printf("打开失败\n");

    }else{
        printf("打开成功\n");

    }
   
    int putc_result = fputc('A',fp);

    if(putc_result == EOF){
        printf("写入失败,%d\n",EOF);
    }else {
        printf("写入%d成功\n",putc_result);
    }

    int puts_result = fputs(" love B\n",fp);
    if(puts_result == EOF){
        printf("写入失败,%d\n",EOF);
    }else {
         printf("写入%d成功\n",puts_result);
    }
    
     /*
        FILE *__restrict __stream: 要写入的文件,写在哪里取决于访问模式
        char *__restrict __fmt: 格式化字符串
        ...: 变长参数列表
        return: 成功返回正整数(写入字符总数不包含换行符) 失败返回 EOF
        fprintf (FILE *__restrict __stream, const char *__restrict
        __fmt, ...)
    */

    int s_len = fprintf(fp,"床前明月光,\n疑是地上霜。\n举头望明月,\n低头思故乡        \n\t\t%s\n","李白");
    if(s_len == EOF){
        printf("写入失败\n");
    }else {
         printf("写入成功\n");
    }

    int result = fclose(fp);
    if(result == EOF)
        printf("关闭失败,%d\n",EOF);
    else if(result == 0)
        printf("关闭成功\n");

    return 0;
}

1.4 从文件中读取数据的函数

1.4.1 fgetc:一次读一个字符(字节),例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE * fp = fopen("a.txt","r");
    if(fp == NULL)
        printf("文件打开失败\n");

    /*
        FILE *__stream: 需要读取数据的那个文件
        return: 读取的一个字节 到文件结尾或出错返回 EOF
        int fgetc (FILE *__stream)
    */

    //假如要读取的是中文,在UTF-8中普通的中文一般占3个字节,一些生僻字可能占4个
    //那么如果用fgetc读取,一次只能读取一个字节,就是说读不完整。需要连续读取(中间不能有换行)
    //char c = fgetc(fp);
    //printf("%c",c);
    char c = fgetc(fp);
    while (c != EOF)
    {
        printf("%c",c);
        c = fgetc(fp);
       
    }
    

    int result = fclose(fp);
    if(result == EOF)
        printf("关闭文件失败\n");

    return 0;
}

1.4.2 fgets:一次读取一行字符串,并且读取一行的数据长度是有限制的,例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE * fp = fopen("a.txt","r");
    if(fp == NULL)
        printf("文件打开失败\n");


    /*
        char *__restrict __s: 接收读取的数据字符串
        int __n: 能够接收数据的长度
        FILE *__restrict __stream: 需要读取的文件
        return: 成功返回字符串 失败返回 NULL(可以直接用于 while)
        fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
    */
    char buff[128];
    //char * readBuf = fgets(buff,sizeof(buff),fp);
    
    while (fgets(buff,sizeof(buff),fp) != NULL)
    {
        //printf("读取出来的数据是:%s\n",buff);
        printf("%s",buff);
    }
    //printf("读取出来的数据是:%s\n",readBuf);
    //printf("读取出来的数据是:%s\n",buff);
    
    

    int result = fclose(fp);
    if(result == EOF)
        printf("关闭文件失败\n");

    return 0;
}

1.4.3 fscanf:按指定格式从文件读取数据,例子:

cpp 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE * fp = fopen("user.txt","r");
    if(fp == NULL)
        printf("文件打开失败\n");


    /*
        FILE *__restrict __stream: 读取的文件
        char *__restrict __format: 读取的字符串
        ...: 变长参数列表 用于接收匹配的数据,
        return: 成功返回参数的个数 失败 返回 0报错 或结束返回 EOF
        int fscanf (FILE *__restrict __stream, const char *__restrict
        __format, ...)
    */
    char name[128];
    int age;
    char wife[128];
    // int count = fscanf(fp,"%s %d %s",name,&age,wife);//出现空行会自动跳过
    // if(count != EOF){
    //     printf("成功配对的个数是%d\n",count);
    //     printf("%s %d %s\n",name,age,wife);
    // }
    while (fscanf(fp,"%s %d %s",name,&age,wife) != EOF)
    {
         printf("%s %d %s\n",name,age,wife);
    }
    int result = fclose(fp);
    if(result == EOF)
        printf("关闭文件失败\n");

    return 0;
}

1.5 标准输入,输出,错误

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>


int main(){
    //从标准输入里面读取数据
    char * buf = malloc(10);

    //stdin: 标准输入 FILE *
    fgets(buf,10,stdin);
    printf("从标准输入读到的数据是%s\n",buf);


    //从标准输出 里面 输出数据

    //stdout: 标准输出 FILE * 这个文件流会将数据输出到控制台
    //printf 底层就是使用的这个
    fputs("baiziyan zhen shuai!",stdout);
    printf("\n");

    //错误输出
    //错误输出 FILE * 一般输出到 错误日志
    fputs("baiziyan zhen shuai!",stderr);
    return 0;
}

1.6 例子

cpp 复制代码
配置文件的修改
如: 
	SPEED=5
	LENG=100
	SCORE=90
    LEVEL=95
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char const *argv[])
{
    FILE * fp = fopen("config.txt","r");
    FILE * fp1 = fopen("config1.txt","w");
   
    char readbuf[100];
    char res[100];
    int i = 0;
    while (fgets(readbuf,sizeof(readbuf),fp) != NULL){
        if(strstr(readbuf,argv[1]) != NULL){
            printf("读取到的数据为:%s\n",readbuf);
            char * rres = strtok(readbuf,"=");//分割字符串,获取键名(如"LEVEL")
            sprintf(res,"%s=%s\n",rres,argv[2]);//拼接新字符串
            printf("拼接之后的数据为%s\n",res);
            fputs(res,fp1);
            
        }else{
            fputs(readbuf,fp1);//一行字符串,原样写入
        }
    }
    fclose(fp);
    fclose(fp1);
    
    system("mv config1.txt config.txt");//覆盖

    return 0;
}
//./xxx.exe LEVEL 50

2. 系统调用

• 系统调用 是 操作系统内核 提供 给应用程序,使其可以间接访问硬件资源的接口

2.1 open:系统调用用于打开一个标准的文件描述符

cpp 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>


int main()
{
    /*
        const char *__path: 文件路径
        int __oflag: 用于指定打开文件的方式,可以是以下选项的组合:
            (1) O_RDONLY: 以只读方式打开文件
            (2) O_WRONLY: 以只写方式打开文件
            (3) O_RDWR: 以读写方式打开文件
            (4) O_CREAT: 如果文件不存在,则创建一个新文件
            (5) O_APPEND: 将所有写入操作追加到文件的末尾
            (6) O_TRUNC: 如果文件存在并且以写入模式打开,则截断文件长度为 0
                还有其他标志,如 O_EXCL(当与 O_CREAT 一起使用时,只有当文件不存在时才创建新文件)
                可选参数: mode -> 仅在使用了 O_CREAT 标志且文件尚不存在的情况下生效,用于
                指定新创建文件的权限位 权限位通常由三位八进制数字组成,分别代表文件所有者、同组
                用户和其他用户的读写执行权限
        return: (1) 成功时返回非负的文件描述符。
                   (2) 失败时返回-1,并设置全局变量 errno 以指示错误原因。
*/
    //linux操作系统有文件权限的保护,就是其他用户没有写权限。
    int fd = open("b.txt",O_RDWR | O_CREAT,0757);
    if(fd == -1){
        printf("文件打开失败\n");
    }

    return 0;
}

2.2 read: 系统调用 用于读取已经打开的文件描述符

cpp 复制代码
#include <unistd.h> 
/* 
int __fd:一个整数,表示要从中读取数据的文件描述符 
void *__buf:一个指向存数据的缓冲区的指针,读取的数据将被存放到这个缓冲区中 
size_t __nbytes:一个 size_t(long类型) 类型的整数,表示要读取的最大字节数 系统调用将 
	                 尝试读取最多这么多字节的数据,但实际读取的字节数可能会少于请求的数量 
return: (1) 成功时,read()返回实际读取的字节数 这个值可能小于__nbytes,如果遇到了文件结尾(EOF)或者因为网络读取等原因提前结束读取 
           (2) 失败时,read()将返回-1 
*/

2.3 write:系统调用用于 对打开的文件描述符写入数据

cpp 复制代码
#include <unistd.h> 
/* 
int __fd:一个整数,表示要写入数据的文件描述符 
void *__buf:一个指向缓冲区的指针,写入的数据需要先存放到这个缓冲区中 
size_t __n:一个 size_t 类型的整数,表示要写入的字节数 write()函数会尝试写 
	         入n 个字节的数据,但实际写入的字节数可能会少于请求的数量 
return: (1) 成功时,write()返回实际写入的字节数 这个值可能小于__n,如果写 
	           入操作因故提前结束,例如: 磁盘满、网络阻塞等情况 
            (2) 失败时,write()将返回-1 
*/

2.4 close:系统调用用于在使用完成之后,关闭对文件描述符的引用

cpp 复制代码
#include <unistd.h> 
/* 
int __fd:一个整数,表示要关闭的文件描述符 
return: (1) 成功关闭时 返回 0 
            (2) 失败时 返回-1 
*/

2.5 exit和_exit

2.5.1 系统调用_exit

• _exit()是由 POSIX 标准定义的系统调用,用于立即终止一个进程,定义在unistd.h 中。这个调用确保进程立即退出,不执行任何清理操作。

• _exit()在子进程终止时特别有用,这可以防止子进程的终止影响到父进程(比如,防止子进程意外地刷新了父进程未写入的输出缓冲区)。

• _exit 和_Exit 功能一样。

cpp 复制代码
#include <unistd.h> 
/** 
* 立即终止当前进程,且不进行正常的清理操作,如关闭文件、释放内存等。这个函数 
   通常在程序遇到严重错误需要立即退出时使用,或者在某些情况下希望避免清理工作时调用。 
*
* int status: 父进程可接收到的退出状态码 0 表示成功 非 0 表示各种不同的错误 
*/ 
void _exit(int status); 
void _Exit (int __status) ;

2.5.2 库函数exit

cpp 复制代码
#include <stdlib.h> 
/** 
* 终止当前进程,但是在此之前会执行 3 种清理操作 
* (1) 调用所有通过 atexit()注册 的 终止处理函数(自定义) 
* (2) 刷新所有标准 I/O 缓冲区(刷写缓存到文件) 
* (3) 关闭所有打开的标准 I/O 流(比如通过 fopen 打开的文件) 
* 
* int status: 父进程可接收到的退出状态码 0 表示成功 非 0 表示各种不同的错误 
*/ 

2.6 例子

cpp 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>


int main()
{
    int fd = open("a.txt",O_RDONLY);
    if(fd == -1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    char buf[1024];
    ssize_t n_read ;
    n_read = read(fd,buf,sizeof(buf));
    if(n_read == -1){
        perror("read");
        close(fd);
        exit(-1);
    }

    write(STDOUT_FILENO,buf,n_read);

    close(fd);
    return 0;
}

3. 文件描述符

3.1 定义

• 在 Linux 系统中,当我们打开或创建一个文件(或套接字)时,操作系统会提供一个文件描述符,这是一个非负整数,我们可以通过它来进行读写等作。

• 文件描述符本身 只是 操作系统给应用程序 操作 底层资源(如文件、套接字等)所提供的一个引用或"句柄 ",其实也可以说文件描述符是一个索引。

• 在 Linux 中,文件描述符 0、1、2 是有特殊含义的。

• 0 是标准输入(stdin)的文件描述符。

• 1 是标准输出(stdout)的文件描述符。

• 2 是标准错误(stderr)的文件描述符。

3.2 文件描述符引用的图解,每个进程都只存自己的文件描述符表。如图:

3.3 每个文件描述符 都关联到 内核一个 struct file 类型的结构体数据

• f_count:多少个 fd/进程在引用这份 file(这个文件),降到 0 时释放对象。

• f_pos:当前文件位置(读写位置)。

• f_path:记录文件的路径。

• f_inode:指向与文件相关联的 inode 对象的指针,该对象用于维护文件元数据,如文件类型、访问权限等。

•const struct file_operations *f_op; // 指向文件操作函数表的指针,定义了文件支持的操作,如读、写、锁定等。

• private_data:存储别的数据。

• 小结:

当我们执行 open()等系统调用时,内核会创建一个新的 struct file(文件描述的结构体),这个数据结构记录了文件的元数据(文件类型、权限等)、文件路径、支持的操作等,然后分配文件描述符,将 struct file 维护在文件描述符表中,最后将文件描述符返回给应用程序。 我们可以通过文件描述符 对文件执行它所支持的各种函数进行操作,而这些函数的函数指针都维护在struct file_operations 数据结构中。文件描述符实质上是底层数据结构 struct file 的一个引用或者句柄,提供了操作底层文件的入口。

• 补充,如图:

3.4 例子,实现linux cp命令的代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(int argc, char const *argv[])
{
    int fd = open(argv[1],O_RDONLY);
    if(fd == -1){
        perror("open");
        close(fd);
        exit(EXIT_FAILURE);
    }
    off_t move = lseek(fd,0,SEEK_END);
    printf("偏移量为%ld\n",move);
    char * buf = malloc(sizeof(char) * move + 8);
    lseek(fd,0,SEEK_SET);

    ssize_t n_read = read(fd,buf,move);

    if(n_read < 0){
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }else{
        int fd1 = open(argv[2],O_WRONLY | O_CREAT | O_TRUNC);
        ssize_t n_write = write(fd1,buf,n_read);
        close(fd1);

    }
    close(fd);

    return 0;
}

4. 系统调用和fopen等这些的区别

• 来源的不同:

• fopen是来自是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。

• 系统调用的open :是由UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。

• 移植性:

• fopen是C标准函数,因此拥有良好的移植性。

• open是由UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile。

• 使用范围:

• open 返回的是文件描述符 ,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。

• fopen是用来操纵普通正规文件(Regular File)的。

• 文件IO层次:如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态

• 缓冲区:

• 使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read,write)。

• 使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换。

相关推荐
FoldWinCard5 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计6 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1237 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质7 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX7 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.7 小时前
Linux系统管理
linux·运维
阿龙的工作记录8 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手8 小时前
Linux的基本指令
linux·运维·服务器
hkj88088 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动10 小时前
Linux编程笔记3【个人用】
linux·笔记