🎁个人主页:我们的五年
🔍系列专栏:Linux课程学习
🌷追光的人,终会万丈光芒
🎉欢迎大家点赞👍评论📝收藏⭐文章
Linux学习笔记:
https://blog.csdn.net/djdjiejsn/category_12669243.html
前言:
这篇主要谈论的是关于文件的基础,让我们在接下来能更好的理解文件描述符,系统调用的相关的文件函数,和语言层封装的f系列的函数。对于这篇文章,会从C语言文件接口,C语言默认打开的三个输入输出流,认识系统文件调用接口,这几个方面介绍文件,打好基础。
目录
5.2stdin,stdout,stderror的文件描述符:
本节重点知识点:
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每个进程都会有文件描述符表:
每个进程中的文件描述符表中的元素都是指向文件的。应该文件可以被多个进程指向。