Linux系统编程——1.通用的I/0模型

我们都知道在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<sys/stat.h>

#include<fcntl.h>

#include<sys/types.h>

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<unistd.h>

ssize_t read(int fd,void *buffer,size_t count)

返回值:调用成功,将返回实际读取的字节数,如果遇到文件结束(EOF)则返回0,出错返回-1

3,数据写入文件:write()

#include<unistd.h>

ssize_t write(int fd,void *buffer,size_t count)

4,关闭文件close()

#include<unistf.h>

int close(int fd)

5,改变文件偏移量

#include<unistd.h>

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会逐个的去比较文件的内容,所以,受网络状况搞得影响,过程有时候会很慢.
--------------------- 
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值