【Linux课程学习】:文件第一弹---文件基础(文件描述符的底层设计)
本节重点知识点:
1.fopen和fclose属于运行时操作。
2.深刻理解先描述,再组织。管理对象时,就要先进行描述。
3.理解一切皆文件,硬件设备对于进程来说也是文件。
4.文件描述符的底层设计--->进程与文件是怎么进行关联的。
一.预备知识
文件的分类 位置
被打开的文件 内存
没有被打开的文件 磁盘
在文件中,没有被打开的文件比被打开的文件多的多。下面我们研究的被打开的文件。研究被打开的文件,是在研究文件与进程的关系,但是进程会描述文件,所以我们研究的是文件与file_struct的关系。
1.1文件=内容+属性:
对于我们创建的空文件,也是有大小的。虽然文件没有内容,但是文件有属性,在文件中要保存这些属性,就会有大小。
1.2在访问文件之前,必须先打开它:
在C语言中,我们很明显的感受到,在进行文件操作之前,必须打开文件(fopen)。然后进行一系列的操作,最后关闭文件(fclose)。
FILE* fd=fopen("log.txt","w");
fclose(fd);
1.2.1但是为什么要打开文件呢?
因为文件一开始在磁盘中保存着,要打开文件,也就是把文件加载到内存中,才能对文件进行操作。fopen是运行时操作,当被运行到fopen这一行代码,才被进程所打开。
所以文件被打开时,是进程在进行访问。
但是进程在内存中,CPU要想访问文件,因为冯诺依曼体系,CPU不能直接去磁盘进行访问,所以只能把文件加载到内存,才能被CPU访问。
1.3管理文件---先描述,在组织
凡是要进行管理的,就先要对管理的对象进行描述(struct),描述文件的属性。然后才能对文件进行管理。文件描述的一些属性,可以由文件的属性获得,还有其他很多属性要OS进行描述。
二.C语言文件操作函数
从C语言的角度看文件,和文件接口。有一个预备的知识。也为后面语言层面的封装与系统接口进行区分和比较。
2.1文件打开的方式:
文件打开方式 功能 效果
w
写
文件不存在,新建文件。文件存在,清空文件。
w+ 读写 文件不存在,新建文件。文件存在,清空文件。
r 读 读取存在的文件,文件不存在,报错。
r+ 读写 读写存在的文件,文件不存在,报错。
a 追加 在文件末尾追加,文件不存在,新建文件。
a+ 读写 在文件末尾读写,文件不存在,新建文件。
另外还可以增加b,比如wb,ab,rb。有b的,就是对二进制文件进行操作。其他的效果和上面类似,也是读(read),写(write),追加(append)。如果我们使用w方式,如果文件存在,会对文件进行清空(truncate)。
当我们输入重定向的时候,要以w方式打开文件,如果没有输入什么东西,文件就被我们清空了。
> log.txt
2.2输出信息到显示器的方法:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
fprintf的用法和printf差不多,只需要在前面指明文件对象,就能向指定的文件进行打印。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, ize_t nmemb, FILE *stream);
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fd=fopen("log.txt","w");
if(fd==NULL)
{
printf("open error!\n");
}
fprintf(stdout,"hello fprintf!\n");
const char* message="hello fwrite!\n";
fwrite(message,strlen(message),1,stdout);
fclose(fd);
}
三.进程默认打开的三个标准流
在下面我们会讲解如何理解键盘,显示器这些硬件也是文件。即一切皆文件。
stdin 标准输入 键盘
stdout 标准输出 显示器
stderror 标准错误 显示器
四.系统调用接口---系统文件I/O
我们使用的C语言文件操作函数,底层是封装了系统类的文件调用。比如C语言的w方式打开,可能是在封装open,并且使用O_WRONLY | O_CREAT | O_TRUNC。mode设置为0666。
4.1open系统调用
4.1.1函数原型:
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
4.1.2函数理解:
pathname:
和C语言的的fopen一样,也是文件的名称,是要打开或者创建的文件,名称。
flags:
是标记位,是int类型,表示要以什么方式进行打开,其本质是位图的思想。某个比特位上有1就表示一个功能。后面我们要使用多个功能时,是需要把代表不同功能的整数,进行按位或就可以达到目的。
flags分类有:
O_RDONLY 只读方式打开
O_WRONLY 只写方式打开
O_WDWR 读写方式打开
上面的这三个参数,必须要有一个,并且只能有一个,要不就是只读,要不就是只写,要不就是读写。
O_CREAT 如果文件不存在,创建文件。需要使用mode函数。
O_APPEND 以追加的方式进行写。
O_TRUNC 对文件进行清空。
返回值:
如果打开成功,就返回新打开的文件描述符。
如果失败就返回-1。
mode选项的函数,表示新创建文件的起始权限。文件的起始权限一般是0666,目录的起始权限一般是0777。所以我们在有O_CREAT时,应该加上对应的权限。但是最终的权限会&(~umask)。也可以设置文件的umask,最终可以让权限是0666。系统有umask,进程也有umask,最终使用的umaks是就近原则。
int ft=open("log.txt",O_WRONLY|O_TRUNC);
五.文件描述符fd
5.1见一见其他文件的文件描述符fd:
文件描述符是整数。让我们看看打开其他文件时的文件描述符是多少?
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd1=open("log1.txt",O_WRONLY|O_TRUNC|O_CREAT);
int fd2=open("log2.txt",O_WRONLY|O_TRUNC|O_CREAT);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
close(fd1);
close(fd2);
return 0;
}
我们发现文件描述符fd不是从0开始的,而是从3开始的,为什么呢?
5.2stdin,stdout,stderror的文件描述符:
名称 文件描述符 硬件
stdin 0 键盘
stdout 1 显示器
stderror 2 显示器
在打开我们的文件时,进程已经帮我们打开这个三个文件。这三个文件的文件描述符是0,1,2。所以我们打开其他文件的时候,就只能从3开始了。在后面的文件描述符的分配规则是占据最小空的元素。
5.3文件描述符的底层设计:
我们观察文件描述符是从0开始的,这很像数组。就可能存在一个表结构,让进程与文件管理起来。
5.3.1文件管理:
被打开的文件,会加载到内存中,加载之前就会对文件进行先描述,然后再进行组织。最后在内存中就会有很多的file_struct,他们之间的联系用某种数据结构进行关联起来。最后形成了一张file list的表。
5.3.2进程中的文件描述指针的数组:
在进程中,会存在一个文件描述指针数组的指针。这个指针指向是一个文件描述指针数组。这个数组从0开始。这个数组的第一个元素,也就是0号下标,指向的是stdin文件。
第二个元素,指向的是stdout文件。第三个元素就是指向就是stderror。
其他被打开的文件被串在file list中。如果被该进程打开,就会把这个文件的描述信息的指针放在文件描述符表中。
关闭文件本质就是在文件描述符表中删除某个文件的指针。
打开一个文件本质就是把文件指针放在文件描述符表中。
这样就可以让进程与文件关联起来。
5.3.3每个进程都会有文件描述符表:
每个进程中的文件描述符表中的元素都是指向文件的。应该文件可以被多个进程指向。
- 点赞
- 收藏
- 关注作者
评论(0)