我们都知道在Linux系统中,“一切皆文件”,所以对于文件的i/o的一系列操作也可以用于诸如管道,终端等所有的文件类型
1.概述
文件描述符:
简单介绍一下文件描述符,文件描述符表示的是进程打开的所有类型的文件,是一个非负整数。针对每一个进程,文件描述符都是自成一套,在程序开始运行之前,我们的父进程shell 就已经帮我们打开了三个我们经常用到的文件描述符,标准输入(stdin)STDIN_FILENO,(stdout)STDOUT_FILENO ,(stderr)STDERR_FILENO,用数字表述分别为0,1,2,三个描述符。其中宏定义是POSIX标准名称,此方法更可取,每个进程能够打开的文件描述符的数量为1024,已经用去3个,还剩1021个,从数字3开始,
通用i/o
1, 打开一个文件:open()
open()函数既能够打开一个已存在的文件,也能够创建一个新的文件。
#include
#include
#include
int open(const char*pathname,int flags);//pathname 表示要打开的文件路径名,如果是符号链接,则可以进行解引用,如果成功打开文件则会返回一个文件描述符,若发生错误则会返回-1,并将errno 置为相应的错误标志。
/* 解释一下errno,errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。
注意:只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误存在。反之,只有当被调用的函数提示有错误发生时检查errno的值才有意义。
所以重要一点:我们在使用库函数的时候,一定要检查返回值,养成良好的编程习惯
https://baike.baidu.com/item/errno/11040395?fr=aladdin
*/
int open(const char* pathname,int flags,mode_t mode);//较上个函数就多了一个mode_t ,此参数表示的是我们要创建的文件的权限设定,此函数表示创建一个新文件并打开它,返回值跟之前一样
标志 | 用途 | 统一unix规范版本 |
O_RDONLY | 表示以只读的方式打开文件 | v3 |
O_WRONLY | 表示以只写的方式打开文件 | v3 |
O_RDWR | 表示以读写的方式打开文件 | v3 |
O_CLOEXEC | 设置close-on-exec标志 close-on-exec字面意思即执行时关闭。进程中每个打开的描述符都有一个执行时关闭标志,若设置此标志,则在执行exec时关闭该描述符,否则该描述符仍打开。 设置该标志位的重要意义在于它可以方便我们关闭无用的文件描述符。 |
v4 |
O_CREAT | 若文件不存在则创建之 | v3 |
O_DIRECT | 始于内核2.4,Linux允许应用程序在执行磁盘I/O时绕过缓冲区高速缓存,从用户空间直接将数据传递给文件或磁盘设备,也可以称之为直接I/O,有时会将直接I/O误认为是获取快速I/O性能的一种手段,然而,对于大多数应用程序而言,使用直接i/o可能会大大降低性能,这是因为为了提高I./O性能,内核针对缓冲区 高速缓存做了不少优化 |
|
O_DIRECTORY | 如果pathname不是目录的话,失败 | v4 |
O_EXCL | 结合O_CREAT使用,专门创建文件 | v3 |
O_LARGEFILE | 在32位系统使用此标志打开大文件 | |
O_NOATIME | 在调用read时,不修改文件的最近访问时间(自LINUX2.6.8开始) | |
O_NOCTTY | 不要让pathname成为控制终端 | V3 |
O_NOFOLLOW | 表示对符号链接不解引用 | v4 |
O_TRUNC | 表示截断文件,使其长度为0 |
v3 |
O_APPEND | 表示总在文件尾部追加数据 | v3 |
O_ASYNC | 当i/o操作可以时,产生信号通知进程 | |
O_DSYNC | 提供同步的i/o操作完整性 | |
O_NONBLOCK | 以非阻塞方式打开 | |
O_SYNC | 以同步方式打开 |
open() 函数常见的错误
EACCES:表示文件权限不允许调用进程以flag指定的方式打开文件
EISDIR ,所指定的文件属于目录
EMFILE:已打开的文件描述符数量达到了进程资源限制的上限
ENFILE:文件打开数量已经达到系统允许的上限
ENOENT:要么文件不存在,要么没指定O_CREAT
EROFS:该文件隶属只读文件系统,而调用者企图以写的方式打开文件。
ETXTBSY:所打开的文件为可执行文件,且正在运行。(系统不允许修改正在运行的程序,必须先终止程序运行,才能修改可执行文件)
2.读取文件内容 read()
#include
ssize_t read(int fd,void *buffer,size_t count)
返回值:调用成功,将返回实际读取的字节数,如果遇到文件结束(EOF)则返回0,出错返回-1
3,数据写入文件:write()
#include
ssize_t write(int fd,void *buffer,size_t count)
4,关闭文件close()
#include
int close(int fd)
5,改变文件偏移量
#include
off_t lseek(int fd,off_t offset,int whence)
SEEK_SET:从文件头部开始
SEEK_CUR:从当前位置
SEEK_END从文件末尾
文件空洞:
实际中的空洞文件会在哪里用到呢?常见的场景有两个:
一是在下载电影的时候,发现刚开始下载,文件的大小就已经到几百M了.
二是在创建虚拟机的磁盘镜像的时候,你创建了一个100G的磁盘镜像,但是其实装起来系统之后,开始也不过只占用了3,4G的磁盘空间,如果一开始把100G都分配出去的话,无疑是很大的浪费.
然后讲一下底层的实现吧,其实这个功能关键得文件系统支持,貌似FAT就不可以吧,linux下一直都很好的支持这一特性,我们举个最简单的ext的例子吧,ext中记录文件实际内容的对应信息的东东是一个叫索引表的东西,里面有十几个条目,每个条目存放对应文件内容块的块号,这样就可以顺序找到对应的文件内容了,大家可能说,几M的一个文件,十几个项哪够啊,不必担心,一般索引表前面几个项目是直接指向文件内容的,如果这几个不够的话,往后的第一个项目不会指向文件内容块,而会指向一个存放项目的块,这样一下多出N个项目来,如果这样还不够,下面的那个是存放指向指向的项目,不好意思,我也绕晕了,总之,前面的是直接指向,下面这个是二级指向,再下面的是二级指向,以此类推,这样,文件系统就可以处理T数量级别的文件,看下图:
到了空洞文件这里呢,我们只需要把指向没有文件内容部分的索引项目置NULL就好了,这样就不会指向实际的数据块了,也不会占用磁盘空间了,就这么easy~
至于btrfs这些新一代文件系统呢,在空洞文件这里的原理跟ext还是类似的.
最后介绍一下linux对空洞文件的处理,经过我最近的一些测试所得:
在同一文件系统ext4下,cat一个空洞文件到新文件,新文件不再是空洞文件,cp一个空洞文件到新文件,新文件仍然是空洞文件.
在btrfs跟ext4之间做的结果同上面是一致的,但是在不同文件系统之间cp,因为不同文件系统分配的最小单元不同,所以du结果会不同.
在nfs的客户端下,在nfs目录下去cp,新文件仍然是空洞文件!!!但是cp会逐个的去比较文件的内容,所以,受网络状况搞得影响,过程有时候会很慢.
---------------------