目录
一.文件
首先我们得对文件有一定的了解, 我们首先来说一下文件把!
- 文件=文件内容+属性(也是数据)。
- 文件的所有操作,其实就两种,对内容,对属性。
- 文件在磁盘(硬件)上.放着,我们访问文件,先写代码–〉编译―〉 exe -〉运行―〉访问文件:本质是谁在访问文件呢,其实是进程。要向硬件写入,只有谁有权利呢,操作系统,普通用户也想写入呢,就必须,让OS提供接口,文件类的系统调用接口。
显示器是硬件,printf向显示器打印,你为什么不觉得奇怪呢,和磁盘写入到文件,没有本质区别,也是一种写入。
Linux认为,一切皆文件,感性的认识:文件而言:曾经理解的文件: read、write,显示器: printf/cout ->一种write,键盘: scanf/cin . ->一种read,站在你写的程序的角度,加载到内存,站在内存的角度,前两种是output,最后一种是input,对于普通文件 -> fopen/fread ->你的进程的内部(内存),是input,你的进程的内部(内存)-> fwrite -〉文件中,是output。
为什么之前我们不知道呢,这里其实有两个原因。
- 第一是比较难,语言上对这些接口做一下封装,为了让接口更好的使用导致了不同的语言,有不同的语言级别的文件访问接口(都不一样),但是都因为封装的是系统接口,为什么要学习OS层面的文件接口,因为这样的接口,只有一套。
- 第二是跨平台,把所有的平台的代码,都实现一遍,条件编译,动态裁剪,如果语言不提供对文件的系统接口的封装是不是所有的访问文件的操作,都必须直接使用OS的接口,而用语言的客户,要不要访问文件呢,当然要一旦使用系统接口,编写所谓的文件代码,无法在其他平台中直接运行了不具备跨平台性。
什么是文件呢:站在系统的角度,能够被input读取,或者能够output写出的设备就叫做文件。狭义文件:普通的磁盘文件,广义上的文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设,都可以称之为文件。
二.系统调用接口
在说系统调用接口前,先看C语言里的文件操作函数,会发现非常的相似,例如C语言中是fopen,fwrite,fread,fclose,而系统接口是open,write,read,close,是不是非常相似,当然在这里C语言的文件接口就不过多介绍了,这里主要讲解一下系统调用接口。
文件操作的系统调用接口:
文件是Linux系统中的重要概念。它不仅仅是对普通文件的操作接口,也是设备通信、进程间通信、网络通信的重要编程接口。因此文件操作的相关调用也是Linux内核提供的最重要的编程接口。
本节将重点叙述如下几个常用的文件操作系统调用。
- open:打开文件。
- read:从已打开的文件中读取数据。
- write:向已打开的文件中写入数据。
- close:关闭已打开的文件。
- 对文件的操作工程一般是这样的:先打开文件,内核对打开的文件进行管理,打开成功后应用程序将获得文件描述符;然后应用程序使用文件描述符对文件进行读写操作;当全部操作完毕后,应用程序需要将文件关闭以释放用于管理打开文件的内存。
- 文件描述符是一个取值从0开始的整数。内核默认一个进程同时打开的文件数有一个上限,也就是文件描述符取值的上限,一般是1024。
- 每个进程在启动后就默认有三个打开的文件描述符0,1,2,如果启动程序时没有进行重定向,则文件描述符0关联到标准输入,1关联到标准输出,2关联到标准错误输出。
打开文件的系统调用接口:open
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int open(const char* pathname, int flags); //函数1
int open(const char* pathname, int flags, mode_t mode); //函数2
pathname·:想要打开/创建的文件(包含路径和文件名)
flags:标志位。表示想要它是如何打开的以及一些属性。对于这个参数系统定义了几个宏。O_WRONLY:只写打开
O_RDONLY:只读打开
O_RDWR:读写打开返回值
成功:返回一个新打开的文件描述符(在后面介绍)
失败:返回-1
上面的这三个宏,再使用open时必须要指定一个,并且也只能指定一个。O_CREAT:如果指定文件不存在,使用这个宏,可以自动创建它。创建的话,需要使用mode参数指明创建出的文件的访问权限。O_APPEND:追加写
关闭文件:close
#include <unistd.h>
int close(int fd); //传入文件描述符
写入,读取
三.文件描述符
一个进程通常会打开多个文件,那么操作系统就需要对这些文件进行管理。Linux操作系统将打开的文件相关属性信息放在了结构体struct file内。而Linux的进程PCB---->task_struct中有一个结构体指针struct files_struct *fs,fs指向的表中又存在着一个数组,这个数组里面的内容正是存放着文件相关属性信息的结构体地址。
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张装files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件:
- 被进程打开的文件(内存文件)
- 没有被打开的文件(磁盘上,文件=内容+属性)(磁盘文件)
总结:文件描述符的本质就是数组下标。
fopen -> open -> fd-> FILE>FILE*
fwrite()->FILE* ->fd -> write -> write(fd,...)-〉自己执行操作系统内部的write方法-〉能找到进程的task_struct->*fs ->files_struct->fd_array[]->fd_array[fd]->struct file->内存文件被找到了。
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件描述符的分配规则:文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
四.重定向
#include<iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main()
{
close(1);
int fd =open("as.txt",O_WRONLY|O_CREAT,0644);
cout<<"gdhslghreoi"<<endl;
return 0;
}
运行上面这一段代码,我们可以发现本来应该输出到屏幕上的内容,都输出到as.txt这个文件里面了,这个就叫做输出重定向。
重定向的本质:其实就是在os系统内部,修改fd对应内容的指向。
dup2系统调用
这个系统调用的作用就是将newfd替换给oldfd。
#include<iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main()
{
int fd =open("as.txt",O_WRONLY|O_CREAT,0644);
dup2(fd,1);
cout<<"gdhslghreoi"<<endl;
return 0;
}
这样其实和我们之前写的,最终结果都是一样的,都会把输出放到文件as.txt中,其实就是将newfd替换给oldfd。
五.缓冲区
1.什么是缓冲区?
就是一段内存空间。
2.为什么要有缓冲区?
首先我们要知道要把数据刷新到屏幕上,就需要进行IO,但是IO相对于我们来说是比较耗时的,所以我们如果一有数据就刷新会非常的拖慢我们的效率,所以我们就需要一段缓冲区将他给展示存储起来,等数据较多时在一起刷新。
3.缓冲区刷新策略。
- 立即刷新
- 行刷新(行缓冲)
- 满刷新(全缓冲)
4.关于缓冲区的认识。
一般而言行缓冲的设备文件--显示器,全缓冲的设备文件--磁盘文件,所有的设备,永远都倾问于全缓冲--缓冲区满了,才刷新--〉需要更少次的I0操作--〉更少次的外设的访问,提高效率,和外部设备IO的时候,数据量的大小不是主要矛盾,你和外设预备I0的过程是最耗费时间的,其他刷新策略是,结合具体情况做的妥协,显示器:直接给用户看的,一方面要照顾效率,一方面要照顾用户体验,极端情况,你是可以自定义规则的。