hello,各位小伙伴,本篇文章跟大家一起学习《Linux:文件与fd(被打开的文件)》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
如果本篇文章对你有帮助,还请各位点点赞!!!
话不多说,开始正题:
文章目录
前置准备
问大家一个问题,空文件有没有大小?答案是有:
- 文件 = 内容 + 属性
即使一个文件是空文件,也只是没有内容而已,还是要有相关的属性如:名称、创建时间、权限…… - 在访问任何一个文件之前,都必须打开它,为什么?
想想在访问文件之前,文件在哪?没错,在磁盘上 - 访问一个文件,是谁在访问?
当我们写完一个有fopen
的代码的时候,文件打开了吗?没有
当我们将其转化为可执行文件的时候,文件打开了吗?没有
当我们运行可执行文件的时候,文件打开了吗?当fopen
运行结束,文件打开了!!
所以,访问文件的是进程!!! - 进程是在内存当中的,本质上就是
CPU
在跑进程,但是文件在磁盘中,根据冯诺依曼原理,CPU
只能跑内存上的数据,所以,要想访问文件,就必须把文件加载到内存当中,也就是说,打开文件,其实就是将文件加载到内存当中!! - 将一个文件加载到内存,无非就是加载属性或者内容嘛,一个进程能够打开一个文件,那么能打开多个文件吗?包的!那一个系统里那么多进程,岂不是非常多文件被打开?那么操作系统要不要对其进行管理呢?答案是肯定的!如何管理:
先描述再组织!
- 盲猜一手:在内核中:文件 = 内核数据结构 + 文件内容
- 进程 = 内核数据结构 + 程序的代码和数据;和文件十分相像,所以结论:
我们在研究打开的文件,是在研究进程和文件的关系! - 那没有被打开的文件呢?在哪里?在磁盘中!
以下是本文所要讲解的内容:
- 被打开的文件——内存
- 没有被打开的文件——磁盘
- 二者结合就是文件系统!
被打开的文件——内存
文件IO基础讲解
fopen
有很多个选项:
首先讲解w
:打开文件,若文件不存在会帮助我们创建文件;若文件存在,将会把原先的文件内容清空。
这个操作和命令行中的>
输出重定向很相像,一样是清空原先文件内容,再将内容输出在文件中,但是若>
右侧没有内容,相当于清空文件。
myfile.c:
#include <stdio.h>
int main()
{
FILE *fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[1024];
const char *message = "hello";
int i = 0;
while(i < 10)
{
snprintf(buffer, sizeof(buffer),"%s: %d\n", message, i);
fputs(buffer, fp);
i++;
}
fclose(fp);
return 0;
}
可以看到,执行了两次myfile
,log.txt
的内容并没有变化,那是当然!
要是把fopen
的w
改为a
:
发现端倪了吧!其实a
就是append
的意思——追加,所以打印结果才会这样,追加并不会将原先的文件清空
在命令行中也有追加重定向>>
:
剩下的选项都比较简单就不讲了
三大流
一个程序在默认启动时给我们打开这三种输出输入流
- stdin 标准输入 键盘
- stdout 标准输出 显示器
- stderr 标准错误 显示器
可以看到这三种输出流都属于FILE*
,这不和fopen
的返回值一模一样吗?
其实这三种输出流都是C语言给我们默认提供的
但是键盘、显示器都是硬件啊,怎么能联系在一起?
没错,我们语言层是不能直接与硬件层直接互通的,必须通过操作系统,才能实现互通
所以,我们所用的C文件接口,底层一定要封装对应的文件类系统调用!
系统调用接口
open:
- 第一个参数
pathname
就是要打开文件的路径名称 - 第二个参数
flags
就是标记位
主要用得到标记位:
这些标记为其实都是些宏,对于flags
看上去是一个整型int
,实际上是一个32比特位的位图,而这些宏,实际上就是只有一个比特位为1的值
其实很好理解,看如下代码:
#include <stdio.h>
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)
void Test(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()
{
Test(ONE);
printf("========================\n");
Test(ONE | TWO);
printf("========================\n");
Test(ONE | TWO | THREE);
return 0;
}
我们使用一下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
open("log.txt", O_WRONLY | O_CREAT);
return 0;
}
文件是创建出来了,怎么权限是乱码啊?
要记住,我们上述使用的fopen
是已经被包装好的函数,我们在这使用的open是系统级的调用接口,是不会帮助我们设置权限的,也没有权限去帮我们设置默认权限(因为都是系统级别),需要我们在创建的时候设置好默认权限!
怎么设置,我们上述参数还少了一个没讲哦,没错,就是mode_t mode
,所以一般我们都建议使用三参数的open,除非你只读或只写并且文件存在,看如下代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
open("log.txt", O_WRONLY | O_CREAT, 0666);// 一般建议在前面加0
return 0;
}
权限变正常了,但是为什么不是我设置的0666
的权限?这是因为权限掩码的问题:
在设置权限的时候,你设置的权限会按位与上权限掩码,然后取反,得到的结果就是最终的权限!
当然了umask
也是可以改变的:
umask(0);
将其设置为0之后,我们设置的权限就是最终的权限:
要注意的是:你在程序里设置的umask
并不会影响系统的umask
,也就是说每个进程都会有属于自己的umask
,继承于父进程的umask
你也可以修改系统的权限掩码:
umask 0777
剩下最后就是open的返回值:
返回值是一个文件描述符,并且是个int
,那到底是什么呢?我们看一下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd1 = open("log.txt", O_WRONLY | O_CREAT, 0666);
if(fd1 < 0)
{
perror("open");
}
printf("fd1: %d\n", fd1