[Linux]基础IO
基础IO
- 空文件也要在磁盘占据空间。
- 文件 = 内容 + 属性。
- 对文件操作 = 对内容 + 对属性的操作,或是对内容和属性的操作。
- 标定一个文件,必须使用:文件路径 + 文件名。(可以表示唯一性)
- 如果没有指明对应的文件路径,默认是在当前路径进行文件的访问。
- 当我们把
fopen,fclose,fread,fwrite等接口写完,编译形成二进制可执行文件之后,但是没有运行,文件对应的操作照样不会被执行,只有当程序被加载到内存变成进程后,才会执行文件对应的操作。- 磁盘中有无数的文件,并不是所有的文件都被打开,当一个文件要被访问,前提是它得必须被打开。由用户进程调用对应的接口,操作系统执行打开的操作。
所以要研究文件操作的本质,就要研究进程和被打开文件的关系。
市面上有多种多样的语言,每种语言的操作文件的接口都不一样。但是文件是在磁盘的,磁盘是硬件,硬件由操作系统来管理,因此任何人想要访问磁盘不能绕过操作系统,必须使用操作系统提供的文件级别的系统调用接口,而操作系统只有一个,所以上层语言无论如何变化,它们对文件操作的接口无非就是对系统提供的接口的封装。
标志位传参
当我们在调用某函数的时候,可能会通过传递一个整数来表示某种状态,当参数比较少的时候还比较好说,当参数过多时,就不得不考虑其他的方法了。比如使用比特位的方式,一个整型有32个比特位,如果使用不重复的比特位表示的话,它可以表示32种状态。
示例
#include <stdio.h>
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)
void print(int flags)
{
if (flags & ONE) printf("one\n");
if (flags & TWO) printf("two\n");
if (flags & THREE) printf("three\n");
if (flags & FOUR) printf("four\n");
if (flags & FIVE) printf("five\n");
}
int main()
{
print(ONE);
printf("--------------------------\n");
print(ONE | TWO);
printf("--------------------------\n");
print(ONE | TWO | THREE);
printf("--------------------------\n");
print(ONE | TWO | THREE | FOUR);
printf("--------------------------\n");
return 0;
}
系统调用接口
open
用于打开或创建文件。
-
函数原型:
int open(const char *pathname, int flags)int open(const char *pathname, int flags, mode_t mode) -
flags参数对应的宏
-
O_RDONLY:以只读方式打开文件。
-
O_WRONLY:以只写方式打开文件。
-
O_RDWR:以读写方式打开文件。
-
O_CREAT:如果文件不存在,则创建文件。通常与
O_WRONLY或O_RDWR结合使用。 -
O_EXCL:与
O_CREAT一起使用,用于确保创建的文件是最新的。否则open()函数会返回错误。 -
O_APPEND:以追加方式打开文件。
-
O_TRUNC:如果文件存在,并且以可写方式打开,那么会截断文件(即将文件大小设置为0)。
示例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define FILE_NAME "test.txt" int main() { int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_EXCL); if (fd == -1) { perror("open"); return 1; } close(fd); }
如果使用上面的代码创建文件,会发现创建出来的文件的权限是乱的。那是因为在创建新文件的时候,必须要指定权限。
只需要将
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_EXCL);改为int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0666);
-
write
用于将数据写入指定的文件描述符。
-
函数原型:
ssize_t write(int fd, const void *buf, size_t count);示例
int main() { int fd = open(FILE_NAME, O_WRONLY | O_CREAT, 0666); if (fd == -1) { perror("open"); return 1; } int cnt = 5; char buffer[64]; while (cnt) { sprintf(buffer, "%s:%d\n", "hellworld", cnt--); write(fd, buffer, strlen(buffer)); } close(fd); }
read
用于从文件描述符中读取数据。
- 函数原型:
ssize_t read(int fd, void *buf, size_t count);
文件描述符
上面总是提到文件描述符,那么到底什么是文件描述符呢?
我们知道一个进程可以打开多个文件,那么系统中一定会存在大量的被打开的文件,那么操作系统肯定要将这些被打开的文件管理起来。如何管理?先描述,后组织。即先为文件创建对应的内核数据结构(struct file {};),然后将这些对应的数据结构通过某种数据结构联系起来。struct file {};中包含了文件的大部分属性。
Linux下的struct file结构体。
我们在来看看文件描述符。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FILE_NAME(num) "test.txt"#num
int main()
{
int fd1 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd2 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd3 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd4 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd5 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);
printf("fd: %d\n", fd1);
printf("fd: %d\n", fd2);
printf("fd: %d\n", fd3);
printf("fd: %d\n", fd4);
printf("fd: %d\n", fd5);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
close(fd5);
return 0;
}
到这里,我们会发现两个问题,一是文件描述符为什么是从3开始,二是这个连续的整数是什么意思?
-
为什么是从3开始。
当我们运行一个程序的时候,操作系统已经将3个文件给我们打开了。它们分别是(以C语言为例):标准输入(
stdin),标准输出(stdout),标准错误(stderr)。分别对应着键盘,显示器,显示器。在上面代码的基础上加上如下代码。
printf("stdin->fd: %d\n", stdin->_fileno); printf("stdout->fd: %d\n", stdout->_fileno); printf("stderr->fd: %d\n", stderr->_fileno);
此时前三个文件描述符就出来了。
-
为什么是连续的整数
当一个文件处于被打开的状态时,由于操作系统要管理,所以在内存中一定会为文件创建一个对应的
struct file{}结构体。在进程的task_struct中会有一个struct files_struct *files的一个指针,这个指针指向struct files_struct,而它里面又包含一个struct file* fd_array[]的指针数组,这个指针数组保存的就指向被打开文件的指针。所以文件描述符就是对应着数组的下标。
重定向
stdout(显示器)默认对应的文件描述符是1,既然它是文件,就可以被关闭。关闭之后会出现什么结果呢?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FILE_NAME "test.txt"
int main()
{
close(1);
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("open fd: %d\n", fd);
fflush(stdout);
close(fd);
}
从结果可以看到,当关闭了1号文件描述符后,在运行程序时,并没有对应的内容显示,这当然是正常的。但是当我们查看test.txt文件里面的内容的时候,却发现这个文件对应的文件描述符变成1号了。这是因为文件描述符的分配规则是,按照从小到大,依次寻找最小的且没有被占用fd。由于1号文件本来是对应的标准输出,但是此时你将标准输出关了,此时test.txt就被分到了一号文件描述符。那么输出时当然就是输向test.txt。这也就是重定向。
系统提供的接口
dup2()函数是操作系统为我们提供的重定向的接口。
函数原型:int dup2(int oldfd, int newfd);
参数:
oldfd:是要被复制的原始文件描述符。它必须是一个有效的、已经打开的文件描述符。newfd:是目标文件描述符编号。dup2()会尝试将oldfd的内容复制到newfd所指定的文件描述符位置。
int main()
{
//close(1);
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open");
return 1;
}
dup(fd, 1);
printf("open fd: %d\n", fd);
fflush(stdout);
close(fd);
}
将之前的代码改写为这样,可以实现同样的效果。

浙公网安备 33010602011771号