前言:
本章我们将进入Linux文件相关的学习与操作,重点知识如下:
- 复习C语言文件IO相关操作
- 认识文件相关系统调用接口
- 了解虚拟文件系统,内核管理文件的数据结构
目录
-
- [1. 文件的预备知识](#1. 文件的预备知识)
- [2. 复习C语言的文件IO操作](#2. 复习C语言的文件IO操作)
-
- [2.1 打开文件(fopen 函数)](#2.1 打开文件(fopen 函数))
- [2.2 关闭文件(fclose 函数)](#2.2 关闭文件(fclose 函数))
- [2.3 理论理解:](#2.3 理论理解:)
- [3. Linux系统文件接口](#3. Linux系统文件接口)
-
- [3.1 open函数接口:](#3.1 open函数接口:)
- [3.2 write函数接口:](#3.2 write函数接口:)
- [3.3 read函数接口:](#3.3 read函数接口:)
- [3.4 close函数接口:](#3.4 close函数接口:)
- [4 文件描述符fd:](#4 文件描述符fd:)
-
- [4.1 0 & 1 & 2描述符 :](#4.1 0 & 1 & 2描述符 :)
- [4.2 内核数据结构详解 :](#4.2 内核数据结构详解 :)
- [4.3 如何理解Linux下一切皆文件 :](#4.3 如何理解Linux下一切皆文件 :)
- [5 缓冲区的概念:](#5 缓冲区的概念:)
-
- [5.1 什么是缓冲区:](#5.1 什么是缓冲区:)
- [5.2 缓冲区的刷新策略:](#5.2 缓冲区的刷新策略:)
1. 文件的预备知识
- 文件 = 文件内容 + 文件属性**(文件属性也是数据,也占用磁盘空间)**
- 文件操作 = 文件内容的操作 + 文件属性的操作
所谓的"打开"文件,究竟是在干什么?
- 将文件的属性或内容加载到内存中!(这是由冯·诺依曼体系决定的!CPU 只能在内存中,对文件进行读写操作)
- 打开文件不是目的,访问文件才是目的!(例如:将文件内容中小写字母改成大写字母,是先将文件内容读到内存里,再把buff里面所有的内容改成大写,再写回到文件中。)
程序被加载到内存中后, 磁盘中还有吗?
- 是的程序被加载到内存中后,磁盘中仍然存在程序文件
- 程序文件是存储在磁盘上的二进制文件,它包含了程序的代码、数据和资源等信息。
并不是所有的文件,都会处于被打开的状态
通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?
- fopen,fclose, fread, fwrite
- 当我们的进程运行起来的时候,才会执行对应的代码
- 然后,才是真正的对文件进行相关的操作。
- 真正的是进程对文件进行操作!
小结:
1. 对文件的操作:
- 只有将程序编成可执行程序之后,加载到内存里的时候。
- 变成了一个进程并且被CPU调度,开始执行自己跟文件操作相关的代码(如fclose 、fopen等),这个时候才开始进行文件操作。
- 通常说对文件的操作这句话,应该准确的说是:程序对应的进程对文件的操作。。。
2. 访问一个文件就得先打开,打开就得在内存里
3. 程序要打开文件,必须先把程序变成进程。
4. 所有文件操作的本质都是在研究进程和打开文件的关系。
2. 复习C语言的文件IO操作
2.1 打开文件(fopen 函数)
- 返回值是一个指向文件的**文件指针(FILE*)**
FILE结构包括一个缓冲区和一个文件描述符(打开文件系统自己返回给你的)
cpp
1. "r":以只读方式打开文件。文件必须已经存在才能成功打开。
2. "w":以写入方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则清空文件内容。
3. "a":以追加方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则在文件末尾追加内容。
4. "rb":以二进制只读方式打开文件。类似于 "r",但以二进制模式读取文件。
5. "wb":以二进制写入方式打开文件。类似于 "w",但以二进制模式写入文件。
6. "ab":以二进制追加方式打开文件。类似于 "a",但以二进制模式追加内容。
代码演示:
通过fopen打开文件,r选项,再通过fgets按行读取文件。
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
//1. 默认这个文件会在哪里形成呢?
//2. r, w, r+, w+, a, a+
//(r+ 和 w+ 都叫做既读又写,只不过w+多了一个功能,就是文件不存在会自动创建)
//3. 关注一下文件清空的问题
FILE* fp = fopen("log.txt", "r");
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64] = { 0 };
while(fgets(buffer, sizeof(buffer), fp) != NULL)
{
printf("echo : %s\n", buffer);
}
fclose(fp);
return 0;
}
2.2 关闭文件(fclose 函数)
cpp
FILE* fp = fopen("log.txt", "a"); //写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
fclose(fp);
2.3 理论理解:
- 当我们向文件写入的时候,最终是不是向磁盘写入?是的!
- 磁盘就是硬件,只有操作系统才有资格想硬件写入
- 对硬件对应写入的时候,必须使用操作系统提供的相关系统调用。
为什么要对系统调用接口做封装?
1.原生系统接口,使用成本比较高。
2.语言不具有跨平台性。
所有的跨平台的语言,必须通过自己的方案,对所有的系统接口做相关封装,设计好自己对应语言当中的IO接口。
3. Linux系统文件接口
我们学习的Linux系统文件接口,更接近于操作系统。。。
open、 close、 read、 write四个系统调用接口。
3.1 open函数接口:
cpp
#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);
% pathname: 要打开或创建的目标文件
% flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。
参数:(通过宏来实现的)
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
% 返回值:
成功:新打开的文件描述符
失败:-1
- mode_t理解:直接 man 手册,比什么都清楚
- open 函数具体使用哪个,和具体应用场景相关,
- 如目标文件不存在,需要open创建,则第三个参数表示创建文件
的默认权限,否则,使用两个参数的open
文件的权限主要包括rwx 等。。
3.2 write函数接口:
为什么我们write往文件里写的时候,写的长度为什么不带上最后的'\0'呢
这是刻意为之,因为文件是不认是C语言的'\0'。
在写入的时候,算长度要减去'\0'占据的那一位。。。
3.3 read函数接口:
cpp
int main()
{
char buffer[1024];
ssize_t s = read(0, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = '\0';
printf("echo: %s", buffer);
}
return 0;
}
3.4 close函数接口:
close接口,关闭文件
close是真的将文件销毁了吗?
- 调用 close 函数后,文件仍然存在,并且可以通过重新打开或使用其他文件操作函数来再次访问
- close 操作只是将文件与当前程序的连接断开,而不是销毁文件本身
4 文件描述符fd:
- 通过对open函数返回值的观察,我们知道了文件描述符就是一个小整数
- 文件描述符,其实是数组下标,是进程中保存的文件描述符指针的下标
- 文件描述符:在linux系统中打开文件就会获得文件描述符 ,它是个很小的非负整数。每个进程在PCB(Process Control Block)中保存着一份 文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。
- 文件指针:C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符(打开文件系统自己返回给你的)。而文件描述符是 文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)
4.1 0 & 1 & 2描述符 :
- Linux, 进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
- 0,1,2对应的物理设备一般是:stdin,stdout,stderr
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
4.2 内核数据结构详解 :
一个进程可以打开多个文件。
在系统在运行中,有可能会存在大量的被打开的文件
操作系统要对这些被打开的文件进行管理,要先描述,再组织
描述的话,使用数据结构struct file{ }
- 文件描述符的分配规则
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
- 重定向
本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出 重定向。常见的重定向有:>, >>, <
那重定向的本质是什么呢?
可使用 dup2 系统调用,进行重定向修改
cpp
#include <unistd.h>
int dup2(int oldfd, int newfd);
%% oldfd 想保留的依赖关系
%% newfd 想删除的依赖关系
%%% 最后要指向oldFd的文件描述符代表的那个文件。
dup函数返回新的文件描述符,如果复制成功,则返回的文件描述符与oldfd具有相同的值和属性。如果复制失败,则返回-1,并设置errno来指示错误的原因。
代码演示:
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
4.3 如何理解Linux下一切皆文件 :
- 1. 如何知道这些struct file对应的操作方法是不一样的?
- Linux是C语言写的,虽然不支持结构体中封装函数,但是可以用函数指针
- 通过函数指针的方式,让函数指针指向特定的方法。
- 打开struct file的时候识别底层文件的类型拿到底层文件的读写方法,用自己的函数指针指向就可以了。
- 上层来使用看的时候就 认为是一切皆文件.
补充理解:
** OS内的内存文件系统以统一的视角看待所有的设备!**
- 如果要打开文件,就在内核给硬件创建struct file
- 初始化的时候,将函数指针指向具体的设备
- 但是在内核中存在的永远都是struct file,然后将struct file关联起来
- 对应的设备,对应的读写方法一定是不一样的
** 有没有任何硬件的差别了?
答:没有了,看待所有的文件的方式,都统一成为了struct file**
什么叫做文件呢?
站在系统的角度,能够被input读取或者能够output写出的设备,就叫做文件!
狭义的文件:普通的磁盘文件
广义的文件:显示器、键盘、网卡、声卡、显卡及磁盘,几乎所有的外设,都可以称之为文件。。
上层使用同一个对象,指针指向不同的对象,最终就能调用不同的方法
5 缓冲区的概念:
缓冲区的作用:主要是为了提高用户的响应速度。
5.1 什么是缓冲区:
就是一段内存空间(这个空间谁提供的?用户char buffer[64] ,scanf(buffer)
缓冲区在哪里? 语言里面会设计,os 系统里面也会涉及
5.2 缓冲区的刷新策略:
-
1.立即刷新
-
2.行刷新(行刷新 \n)
-
3.满刷新(全缓冲)
特殊情况时:
- 1.用户强制刷新(fflush)
- 2.进程退出
尾声
看到这里,相信大家对这个Linux 有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦