Linux中的文件操作

1. 前置理论

  • 所有输入/输出设备(如网络、磁盘和终端)都表现为文件,所有输入和输出都是通过读取和写入适当的文件来执行的。这种从硬件设备到文件的映射接口称为I/O,它使所有输入和输出都能够以统一的方式执行。

  • 应用程序通过请求内核打开相应的文件,内核返回一个非负整数,称为描述符,用于在文件的所有后续操作中标识该文件。

  • 操作系统从文件中将数据加载到内存是通过一个batch(n个字节构成),目的是可以以一种统一的方式读写。当读取时,超过了文件中的batch数,则会返回EOF(它并不代表文件的真实结尾,真实的字符数会小于读取的字符;文件末尾也不会存在EOF这个标识符,文件中的batch数是由进程维护的,这个在写入时就已经计算出来了。读取时会根据进程中的信息来判断到底需要读取多少个batch)

  • VFS虚拟文件系统:进程中,在task_struct中存在files_struct结构体,而这个结构体包含了一个文件描述符数组fd_array,这是一个指针数组,所谓的文件描述即是这个数组的下标。数组每个元素指向了内存中对应文件的管理结构体struct file。在struct file中包含了对文件进行读写等操作的函数指针,可以指向不同的函数,满足了对不同文件实现不同方式的读写。实现不同外设的驱动可以通过修改这些函数指针指向的函数,特制相应的操作。
    在这里插入图片描述

  • 文件描述符分配规则:系统在fd_array中找一个最小的未被使用的数组下标分配给新创建打开的文件。

  • 文件包括文件内容和文件属性,新创建的文件即使没有写入,也会在磁盘上占用一部分空间用来存储文件的属性信息。

2. C标准库提供的函数接口

C标准库通过FILE类型的结构体来维护文件,结构体中包含了文件描述符,若想打印出某个文件的描述符,可以通过stdout-> _fileno这个语句来获得。我们常说的标准输入、标准输出、标准错误其本质也是由FILE *指向的文件

stdin ——键盘,其FILE结构体中包含的文件描述符fd=0
stdout ——显示器,其FILE结构体中包含的文件描述符fd=1
stderr —— 显示器,其FILE结构体中包含的文件描述符fd=2

在C标准库中,可以使用以下几个函数来创建和读取文件:

  • fopen
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
// FILE *fp = fopen("./test","w+");

path:文件的路径,例如“/usr/test.c”

mode: 打开文件的方式。r 以只读的方式打开存在的文件;r+ 对存在的文件可读可写; w 创建和重新写入;w+ 可读可写,若文件不存在则创建 ; a 创建和附加写入 a+读和附加写入

  • flose
int fclose(FILE *fp);

关闭文件描述符为fp的文件。

  • 写入函数
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);

int fputc(int c, FILE *stream); //带c的,会将传入字符转化为十进制的ASCII码,返回写入字符的ASCII码
int putc(int c, FILE *stream);
int putchar(int c);  //相当于fputc(char, stdout);
// # 例子
//int main()
// {
//     FILE *fp = fopen("./test.txt","w+");
//     std::string str = "hello world\n";
//     for(int i = 0; i < str.size(); i++)
//     {
//         // int a = fputc(str[i], stdout);
//         // int a = putc(str[i], stdout);
//         int a = putchar(str[i]);
//         std::cout << ":" << a << " ";
//     }
//     str[str.size()] = '\n';
//     return 0;
// }

// # 例子
int fputs(const char *s, FILE *stream); 
int puts(const char *s);
// const char *p = "hello world!\n";
// fputs(p, stdout);
// puts(p);
  • 读取函数
int fgetc(FILE *stream);  //错误返回EOF;使用例子:putc(fgetc(stdin), stdout);
int getc(FILE *stream);  
int getchar(void); //默认从stdin中读取 

char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);

int fscanf(FILE *stream, const char *format, ...);
  • 判断成功
int feof(FILE *stream); //检测EOF(end-of-file)
int ferror(FILE *stream);//检测错误
int fileno(FILE *stream);//返回流文件描述符

3. Linux系统提供的调用接口

3.1 创建并读写

  • open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);

pathname: 文件打开的路径;

flags:必须包含以下任意一种模式 O_RDONLY(只读), O_WRONLY(只写), or O_RDWR(读写)。可选包含: O_APPENDO_CREATO_EXCL(保证了文件是新创建的,否则open失败)、O_TRUNC清空文件中原来的内容)

mode:设定了文件的权限。类似于-r-xrwxrwx。实际结果与umask有关。

返回值:返回了打开或创建的文件描述符。出错返回-1.

  • close
#include <unistd.h>
int close(int fd);
  • write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

fd :写入文件的描述符。

buf:将要写入的文件内容。

count:需要写入字符的个数。

返回值:成功返回写入的字符数;失败返回-1。

  • read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

buf:将要传入内容的空间。

返回值:成功返回读到的字节数,并且更新指向文件内容的指针(会继续从这个指针指向的位置读取);失败返回-1。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int fd = open("./test.txt", O_CREAT | O_RDWR, 0644);
    char buf[1024] = {0};
    if (fd < 0)
    {
        printf("open file failed\n");
        exit(-1);
    }
    const char* Str = "hello world!\n";
    write(fd, Str,strlen(Str));  //系统调用函数和C不同,不用传入'\0'标志结尾
    write(fd, Str,strlen(Str));

    fd = open("./test.txt", O_CREAT | O_RDWR, 0644); //需要重新打开这个文件
    ssize_t reN = read(fd, buf,sizeof(buf) - 1);
    if(reN >= 0)
    {
        //buf[reN] = '\0';  //将'\0'写入buf以供C函数读取。也可不写,因为数组初始化为0
        printf("%s", buf);
    }
    else
        printf("error\n");
    return 0;
}

结果

[test@VM-12-4-centos test1]$ ./main 
hello world!
hello world!
[test@VM-12-4-centos test1]$ cat test.txt 
hello world!
hello world!

3.2 重定向

#include <unistd.h>
int dup2(int oldfd, int newfd);

newfd是oldfd的拷贝,即将oldfd维护的文件同时交给newfd维护,newfd之前的内容被覆盖,此时文件描述符newfd被用于维护oldfd所维护的内容。
在这里插入图片描述

#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("./input.txt", O_CREAT |  O_RDWR | O_TRUNC, 0644);
    dup2(fd, 0); 

    char *p = "hello world\n";
    write(0, p, strlen(p));
    close(fd);

    fd = open("./input.txt", O_CREAT | O_RDWR, 0644);
    dup2(1, fd);  
    write(fd, p, strlen(p));
    close(fd);

    return 0;
}

结果

[test@VM-12-4-centos dup2]$ ./test 
hello world
[test@VM-12-4-centos dup2]$ cat input.txt 
hello world
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值