【Linux】文件操作

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析(3)

在这里插入图片描述


👉🏻文件是什么?

文件 = 内容+属性
所有对文件的操作:a.对内容操作 b.对属性操作。
内容是数据,属性其实也是数据。我们访问一个文件(我们是一个进程!),一个文件打开前是:普通的磁盘文件,打开后:将文件加载到内存
一个进程可以打开多个文件

我们研究文件操作的本质:就是进程被打开文件的关系

进程通过操作系统打开文件,而操作系统提供接口(如fopen)给进程打开磁盘中的文件。

操作系统访问硬件磁盘中的文件,要加载到内存中,而操作系统要管理文件,必须先描述,再组织,所以一个文件的要被打开,一定先在内核中形成被打开的文件对象,即描述PCB

👉🏻C常见文件接口复习

1.fopen以写的形式打开,源文件内容会被清空

2.向文件写入字符串,strlen(str),不需要加+1,即不用写入\0

👉🏻open函数

C语言中的open()函数是打开文件的函数,它可以用于创建或打开文件,并返回一个文件描述符(file descriptor)。

open()函数的声明如下:

#include <fcntl.h>

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

其中,第一个参数pathname是指要打开(或创建)的文件的路径名,这个参数是必须的。第二个参数flags是一个控制文件打开方式的标志,这个参数也是必须的。第三个参数mode表示新建文件时的权限,只有在创建新文件时才需要传入。

flags参数常用的取值如下:

  • O_RDONLY:只读模式打开文件
  • O_WRONLY:只写模式打开文件
  • O_RDWR:读写模式打开文件
  • 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()函数返回一个文件描述符,这个描述符是一个整数值,它是操作系统为了方便管理文件而提供的一种接口。在后续的文件读写等操作中,我们可以使用这个文件描述符来指代该文件。

如果open()函数执行成功,则返回一个非负整数(文件描述符),否则返回-1。通常,当返回-1时,errno变量会被设置为下面的某个值来表示错误原因:

  • EACCES:无法访问指定文件
  • ENOENT:指定文件不存在
  • EEXIST:文件已经存在,但打开标志中指定了O_CREAT标志
  • EINVAL:打开标志无效

例如,要以只读模式打开一个文件,可以这样调用open()函数:

int fd;
fd = open("/path/to/file", O_RDONLY);
if (fd == -1) {
    perror("Error opening file");
    exit(EXIT_FAILURE);
}

在使用完文件后,应该使用close()函数关闭文件描述符:

int ret;
ret = close(fd);
if (ret == -1) {
    perror("Error closing file");
    exit(EXIT_FAILURE);
}

这是open()函数的基本用法,还有一些高级用法,比如使用open()函数打开套接字或管道等,不过这些用法超出了本回答的范围。

文件返回值fd

文件的返回值fd是一个连续的小整数,fd的本质就是进程文件描述符表数组的下标!

而OS会默认先打开三个标准文件,标准输入stdin,标准输出stdout,标准错误stderr,下标分别为0,1,2。OS默认打开这三个标准,就是为了让程序员默认进行输入输出代码编写。

文件描述符

文件描述符是一个用于标识已被内核打开文件的整数。在Unix和类Unix操作系统中,包括Linux和Mac OS X等,文件描述符是对文件、套接字和其他I/O资源的引用。

在Unix系统中,标准的文件描述符如下:

  • 0:标准输入(stdin)
  • 1:标准输出(stdout)
  • 2:标准错误输出(stderr)

当程序在Unix系统中打开一个文件或者创建一个新的文件时,内核会分配一个文件描述符来标识这个文件。在C语言中,可以使用open()函数或者fopen()函数来打开文件,它们返回的整数就是文件描述符。

文件描述符可以用于进行读取、写入和关闭文件等操作。例如,可以使用read()write()函数来从文件描述符读取数据或者向文件描述符写入数据。另外,可以使用close()函数来关闭文件描述符所指向的文件。

在Unix系统中,文件描述符还可以用于操作套接字(socket),管道(pipe)等I/O资源。因此,文件描述符是Unix系统中重要的概念,它提供了一种统一的方式来处理各种类型的I/O操作。

在C++中,通常使用std::ifstreamstd::ofstream等类来进行文件操作,而不直接使用文件描述符。不过,如果需要的话,可以通过fileno()函数获取std::ifstreamstd::ofstream对象对应的文件描述符。

总之,文件描述符是Unix系统中用于标识已打开文件、套接字和其他I/O资源的整数,它是进行I/O操作的重要工具。

🌰 f d 的分配规则 fd的分配规则 fd的分配规则
进程默认打开了0,1,2文件描述符
文件描述符的分配规则是,寻找最小的,未被使用的数据的位置,分配给被打开的文件

进程和打开文件的对应关系,如何进行维护?

在这里插入图片描述
🌰 s t r u c t f i l e 内核对象 structfile 内核对象 structfile内核对象
struct file中包含文件的属性和内容文件的操作方法
还有指向文件缓冲区的指针;无论读写,都要先把数据加载到文件缓冲区!而后将缓冲区内的数据拷贝到进程中,所以我们在应用层进行数据的读写,本质上就是进行拷贝
在这里插入图片描述

如何看待一切皆文件?

在这里插入图片描述
stdin,stdout,stderr这些标准文件,都有来实现读写功能的函数,而文件PCB中存储着这些函数的函数指针。

👉🏻read函数

read() 是一个文件操作函数,用于从文件描述符中读取数据。其函数原型为:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

其中,fd 是要读取的文件描述符,buf 是数据缓冲区的指针,count 是要读取的字节数。

read() 函数的返回值是已经读取的字节数,如果读取失败则返回 -1,并设置相应的错误码。如果已经读取的字节数小于 count,则表示读到了文件结束或者读取遇到了错误。

在使用 read() 函数进行文件读取时,需要注意以下几个方面:

  1. read() 函数可能会阻塞程序直到读取到足够的数据,因此在使用时需要注意程序的响应性。
  2. 如果读取的字节数比请求的数量少,可能需要多次调用 read() 函数,直到读取到足够的数据。
  3. read() 函数返回的字节数可能比请求的数量少,因此需要根据实际读取到的数据长度进行处理,避免访问越界或者处理不完整的数据。

总之,read() 函数是文件 I/O 操作中的重要函数之一,使用时需要根据实际情况进行合理调用和处理。

👉🏻write函数

write()函数是C语言中用于向文件描述符(或者套接字)中写入数据的函数。

write()函数的声明如下:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

其中,fd是要写入的文件描述符,buf是指向要写入数据的缓冲区的指针,count是要写入的字节数。

write()函数的返回值是成功写入的字节数,如果出现错误,则返回-1,并设置全局变量errno以指示错误类型。

下面是一个简单的示例,演示如何使用write()函数向文件中写入数据:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    ssize_t bytes_written;
    const char *str = "Hello, world!\n";
    
    fd = open("output.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    bytes_written = write(fd, str, strlen(str));
    if (bytes_written == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }
    
    close(fd);
    return 0;
}

在这个示例中,首先使用open()函数打开一个文件,然后使用write()函数向这个文件中写入数据,最后使用close()函数关闭文件描述符。

需要注意的是,write()函数是以字节为单位进行写入的,如果要写入一个字符串,需要使用strlen()函数来获取字符串的长度。此外,write()函数并不会自动在字符串的末尾添加空字符(‘\0’),所以在写入字符串时需要确保字符串以null结尾,或者传入字符串长度时包含结尾的null字符。

👉🏻重定向

重定向的本质是改变程序的标准输入、标准输出和标准错误输出的目标。在操作系统中,每个进程都有三个默认打开的文件描述符,即标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。这些文件描述符分别对应着文件描述符号0、1和2。

当执行一个程序时,默认情况下,这些文件描述符会连接到终端设备(比如命令行界面)。而通过重定向,我们可以将这些文件描述符连接到其他文件或设备上,从而实现输入输出的改变。

具体来说,当使用输入重定向 < 时,操作系统会将指定文件的内容读取,并作为程序的标准输入(stdin)提供给程序。这样,程序就可以从重定向的文件中读取数据。

当使用输出重定向 > 时,操作系统会将程序的标准输出(stdout)重定向到指定的文件中,程序的输出结果就会被写入到该文件中。

类似地,当使用错误输出重定向 2> 时,操作系统会将程序的标准错误输出(stderr)重定向到指定的文件中,程序的错误信息就会被写入到该文件中。

在操作系统内部,重定向是通过操作文件描述符表来实现的。文件描述符表是一个与进程相关的数据结构,用于记录当前进程打开的文件描述符及其对应的文件或设备。通过修改文件描述符表中的相应项,操作系统就能够实现输入输出的重定向

总结来说,重定向的本质是通过改变程序的标准输入、标准输出和标准错误输出的目标,将其连接到指定的文件或设备上,从而实现输入输出的改变。

🧆 接下来我们举一个操作系统内部的例子

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

#define FILE_NAME "log.txt"

int main()
{
	 close(0);
    int fd = open(FILE_NAME, O_CREAT|O_WRONLY|O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];
    fread(buffer, 1, sizeof(buffer), stdin); // stdin->fd: 0
    printf("%s\n", buffer);
  //  fprintf(stdout, "fd: %d\n", fd);
    fprintf(stdout, "stdout->fd: %d\n", stdout->_fileno);

    fflush(stdout);
    close(fd);
 return 0;
}

上述代码中,先close(0)关闭标准输入stdin,在打开新的文件,此时新的文件的文件描述符就是1,此时输入就会从新打开的文件读取数据,而不是键盘了,这就是输入重定向;输出重定向也同理。

这里为什么要刷新缓冲区?

通过调用 fflush(stdout) 函数,我们可以强制将标准输出缓冲区中的内容刷新到文件中。这样,无论缓冲区是否已满,数据都会被写入到文件中。
如果不进行刷新操作,数据可能会滞留在标准输出缓冲区中,并不会及时写入到文件中。这会导致文件中的数据不完整或不准确。

在这里插入图片描述

👉🏻dup2函数

dup2() 函数是一个文件描述符复制函数,它用于将一个已经打开的文件描述符复制到另一个文件描述符,并且可以选择性地关闭原有的文件描述符。

函数原型如下:

#include <unistd.h>

int dup2(int oldfd, int newfd);

参数说明:

  • oldfd:要复制的源文件描述符。
  • newfd:要复制到的目标文件描述符。

函数返回值:

  • 成功复制文件描述符时,返回新的文件描述符(即 newfd)。
  • 失败时,返回 -1,并设置相应的错误码。

dup2() 函数的作用是将 oldfd 文件描述符复制到 newfd 文件描述符。如果 newfd 已经打开,则会先关闭 newfd,再复制 oldfdnewfd。如果 newfdoldfd 相等,不会进行任何操作。

这个函数在进行文件描述符重定向时非常有用。例如,我们可以将标准输出的文件描述符(stdout)复制到一个文件描述符,使得之后的输出都会写入到该文件中。

以下是一个简单的示例:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 复制标准输出到文件描述符 fd
    if (dup2(fd,stdout)  < 0) {
        perror("dup2");
        return 1;
    }

    // 这里的输出会写入到文件中
    printf("Hello, world!\n");

    close(fd);
    return 0;
}

在上述示例中,我们首先打开一个文件 output.txt ,然后使用 dup2() 函数将标准输出(stdout)复制到文件描述符 fd。之后,所有的输出都会写入到 output.txt 文件中,而不是显示在终端上。

需要注意的是,在使用 dup2() 函数复制文件描述符时,我们需要确保目标文件描述符 newfd 是可用的、未打开的。否则,可能会引起意料之外的结果。

👉🏻stat函数

stat 函数是一个用于获取文件或文件系统状态信息的 POSIX 标准函数,它可以获取文件的诸多属性,比如文件大小、权限、修改时间等。在 C/C++ 中,你可以通过包含 <sys/stat.h> 头文件来使用 stat 函数。

这个函数的原型通常如下所示:

int stat(const char *path, struct stat *buf);
  • path 参数是要获取状态信息的文件路径名。
  • buf 参数是一个 struct stat 类型的结构体指针,用于存储获取到的状态信息。

成功调用 stat 函数会返回 0,失败则返回 -1,并设置 errno 表示错误类型。

struct stat 结构体中包含了文件的各种属性信息,其中常用的成员包括:

  • st_mode:文件的类型和访问权限。
  • st_size:文件大小(以字节为单位)。
  • st_mtime:文件的最后修改时间。

使用 stat 函数可以方便地获取文件的基本信息,用于程序中的文件操作、权限检查和状态监控等。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值