欢迎来到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::ifstream
和std::ofstream
等类来进行文件操作,而不直接使用文件描述符。不过,如果需要的话,可以通过fileno()
函数获取std::ifstream
或std::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()
函数进行文件读取时,需要注意以下几个方面:
read()
函数可能会阻塞程序直到读取到足够的数据,因此在使用时需要注意程序的响应性。- 如果读取的字节数比请求的数量少,可能需要多次调用
read()
函数,直到读取到足够的数据。 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
,再复制 oldfd
到 newfd
。如果 newfd
和 oldfd
相等,不会进行任何操作。
这个函数在进行文件描述符重定向时非常有用。例如,我们可以将标准输出的文件描述符(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
函数可以方便地获取文件的基本信息,用于程序中的文件操作、权限检查和状态监控等。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长