二、文件I/O

一、基础

内核为每个进程维护了一个打开文件的列表,该表由文件描述符(file descriptors)称作fds的非负整数进行索引;表中每一项包含一个打开文件的信息其中包括指向文件备份inode的内存拷贝指针和元数据。子进程会默认获得父进程文件表的拷贝;文件描述符用int 类型表示默认从0开始;按照惯例进程至少会有三个文件描述符0是标准输入(stdin),1是标准输出(stdout),2是标准错误输出(stderr)。

二、文件操作

1、open()

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *name, int flags);
int open(const char *name, int flags. mode_t mode); //open()系统调用将路径名name给出的文件与一个成功返回的文件描述符关联,文件位置的指针被设定为0,文件根据flags给出的标志位打开.

flag参数是O_RDONLY、O_WRONLY、O_RDWR表示只读、只写和读写三种模式打开文件。必须包含以上三种可以与下面某个按位或运算。
这里写图片描述
这里写图片描述

新文件限权

当创建新文件是,即O_CREAT需要给出mode参数,常见的是权限位集合,向八进制数0644;为了可移植性引入一下常数。
这里写图片描述
最终写入权限的还需要让mode参数与用户 创建的掩码(umask)按位与。

2、creat()

调用成功返回文件描述符错误返回-1设置errno

// O_WRONLY | O_CREAT | O_TRUNC 组合经常被使用所以有一个专门的系统调用
int creat(const char *name, mode_t mode);
//ex:
int fd;
fd = creat(file, 0644);
if(fd == -1)
    /* error */

3、read()

调用成功返回写入buf的字节数,失败返回-1设置errno

ssize_t read(int fd, void *buf, size_t len); //从fd指向的文件的当前偏移量至多读len个字节到buf中

这里写图片描述

/* 处理上面的所有错误的正确读文件 */
ssize_t ret;
while(len != 0 && (ret = read(fd, buf, len)) != 0)
{
    if(ret == -1)
    {
        if(errno == EINTR)
            continue;
        perror("read");
        break;
    }
    len -= ret;
    buf += ret;
}

非阻塞读:不希望没有数据可读的时候让read()调用阻塞;希望立即返回。

char buf[BUFSIZE];
ssize_t nr;
start:
nr = read(fd, buf, BUFSIZE);
if(nr == -1)
{
    if(errno == EINTR)
        goto start;
    if(errno == EAGAIN)
        /* resubmit later */
    else
        /* error */
}

read()大小限制size_t 和 ssize_t 分别对应 unsigned int和int,所以ssize_t最大值为SSIZE_MAX如果len比它大read()调用将是未定义的。

if(len > SSIZE_MAX)
    len = SSIZE_MAX;

write()

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
//从fd引用的文件的当前位置开始,将buf中至多count个字节写入fd中,不支持定位的文件,成功返回写入的字节数,失败返回-1

//ex:
ssize_t nr;
nr = write(fd, buf, strlen(buf));
if(nr == -1)
    /* error, check errno */
else if(nr != count)
    /* possible error,buf 'errno' not set */

对于套接字需要保证真的写入了所有请求的字节

ssize_t ret, nr;
while(len != 0 && (ret = write(fd, buf, len)) != 0)
{
    if(ret != -1)
        if(errno == EINTR)
        {
            continue;
            perror("write");
            break;
        }
        len -= ret;
        buf += ret;
}

追加模式:当打开时候制定O_APPEND参数的时候写操作就不从文件描述符的当前位置开始,而是从文件末尾开始。
非阻塞写:fd在非阻塞模式下打开(通过设置O_NONBLOCK参数),发起的写操作正常阻塞时,write()系统调用返回-1,设置errno为EAGAIN,请求应该在稍后重新发起。
如果count比SSIZE_MAX大将未定义,count为0将立即返回0。
write()行为:当调用返回的时候,内核已将所提供的缓冲区数据复制到了内核缓冲区但是没有保证数据已写到目的文件。处理器和硬盘的速度差异。write调用立刻返回,内核可以将写入操作推迟到空闲阶段,并将很多写操作一起处理。所以当程序未崩溃但是数据写入磁盘前的read调用会冲缓冲区读。

close()

#include <unistd.h>

int close(int fd);  //解除文件描述符关联,分离进程和文件关联。
if(close(fd) == -1)
    perror("close");

lseek()查找

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t pos, int origin); //给定文件描述符引用的文件位置设定指定值

//ex1: 调用成功返回新文件位置
off_t ret;
ret = lseek(fd, (off_t) 1825, SEEK_SET);
if(ret == (off_t) -1)
    /* error */
//ex2:设置文件fd到文件末尾
ret = lseek(fd, 0, SEEK_END);
//ex3:设置文件到当前位置+pos
ret = lseek(fd, 0, SEEK_CUR); //确定文件的当前位置
//ex4: 文件末尾后查找
ret = lseek(fd, (off_t) 1688, SEEK_END); //到最近文件位置读会返回EOF,然而有写请求会建立新空间并用零来填充。

pread()和pwrite()定位读写

#define _XOPEN_SOURCE 500
#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t pos);  //调用从文件描述符fd的pos文件位置读取count字节到buf中

ssize_t write(int fd, void *buf, size_t count, off_t pos); //从fd的pos位置写count字节

两个函数调用完成后都不会修改文件位置。

截断文件

#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t len);
int truncate(const char *path, off_t len);
// 两个函数都将文件截断到len这个长度失败返回-1,成功返回0

三、同步I//O

文件描述符对象同步

#include <unistd.h>

int fsync(int fd); //调用该函数可以保证对应文件的脏数据写回到磁盘上,fd必须以写方式打开。该调用会建立时间戳和inode中的其他属性等元数据。

int fdatasync(int fd); //完成和fsync一样区别在于它之写入数据不保证元数据同步。

成功时候都返回0,失败时候返回-1,errno设置为一下三个值EBADF:给定文件描述符不是一个可以写入的描述符
EINVAL:给定描述符不支持同步
EIO:底层I/O错误,通常在错误发生处被捕获。

if(fsync(fd) == -1)
{
        if(errno == EINVAL) //给定文件描述符对应的对象不支持同步
        {
            if(fdatasync(fd) == -1)
                perror("fdatasync");
        }
        else
            perror("fsync");
}

所有缓冲区同步

#include <unistd.h>

void sync(void);    //确保所有缓冲区包括数据和元数据都能写入磁盘

O_SYNC 在 open()系统调用中使用,强制write调用进行I/O同步,看起来像每一次write操作后都执行sync()调用。
O_DSYNC和O_RSYNC另外两个同步标志,O_DSYNC在每次写操作后普通数据被同步元数据不同步;O_RSYNC要求读请求像写请求那样同步。

四、I/O多路复用

I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。I/O多路复用遵循以下原则:

①I/O多路复用:当任何文件描述符准备好I/O时告诉我
②在一个或者更多文件描述符就绪前始终处于睡眠状态
③唤醒:那个准备好了?
④在不阻塞的情况下处理所有I/O就绪的文件描述符
⑤返回第一步,重新开始。

Linux提供三种I/O多路复用方案:select、poll和epoll。

select()多路复用

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds. struct timeval *timeout);
FD_CLR(int fd, fd_set *set); //从指定集合中移除文件描述符
FD_ISSET(int fd, fd_set *set); //测试一个文件描述符在不在集合中
FD_SET(int fd, fd_set *set); //从指定集合中添加文件描述符
FD_ZERO(fd_set *set); //从指定集合中清空文件描述符

在指定文件描述符I/O准备就绪之前或者操作时间限制,select调用就会阻塞。
监测的文件描述符分为三类:
① readfs集合中的文件描述符,确认其中是否有可读数据(读操作可以无阻塞的完成)
②wirtefds集合中的文件描述符,确认其中有一个写操作可以无阻塞的完成
③exceptfds集合,确认其中是否有异常发生或者外带数据(只适用于套接字)。
若指定集合为NULL,则不对此类进行监视。
成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符。第一个参数n等于所有集合文件描述符的最大值加一。timeout参数指向timeval结构体指针

#include <sys/time.h>

struct timeval
{
    long tv_sec; // seconds
    long tv_usec; //microseconds
}

如果这个参数不是NULL,此时也没有文件描述符处于I/O就绪,select将在tv_sec秒tv_usec微妙后返回。
文件描述符集合是静态建立的,所以对于文件描述符的上限有最大值FD_SETSIZE,linux中为1024。
函数调用成功时返回在所有三个集合中I/O就绪文件描述符的数目,如果给出的时间返回值可能是0。错误返回-1,而且errno设置为EBADF:非法文件描述符;EINTR:等待时捕获了一个信号可以重新发起调用
EINVAL 参数n是负数或者时限不合法
ENOMEN 没有足够内存

pselect()多路复用

#define _XOPEN_SOURCE 600
#include <sys/select.h>

int pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);

pselect和select有三点不同
①pselect和select的timeout参数类型不同,前者是timespec使用秒和纳秒不是秒和毫秒
②pselect调用不修改timeout参数,在后调中也不需要重新初始化
③select没有sigmask参数。

#include<sys/time.h>

struct timespec
{
    long tv_sec;
    long tv_nsec;
};

sigmask参数来解决信号和等待文件描述符之间的竞争条件。

poll()多路复用

#include <sys/poll.h>

int poll(struct pollfd *fds, unsigned int nfds, int timeout); //成功时返回具有非零的revents描述符的个数,超时前没有任何事件返回0,失败返回-1
struct pollfd
{
    int fd;
    short events; //监视的文件描述符事件的位掩码
    short revents; //发生在该文件描述符上的事件的位掩码
};

events字段用户设置,revents字段是在内核返回时设置,所有events字段请求事件都可能在revents字段返回。
下面是合法事件:
这里写图片描述
下面事件可能在revents返回
这里写图片描述
POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND而POLLOUT等价于POLLWRNORM。
当我们监视一个文件描述符是否可读写,设置events为POLLIN | POLLOUT,返回时将在revents中是否有相应的标志,有POLLIN表示非阻塞读,有POLLOUT非阻塞写。
timeout参数设置I/O就绪等待的时间长度,以毫秒计。负值永远等待。零表示调用立刻返回,列出所有未准备好的I/O,不等待其他时间。这个时候就是轮询。

ppoll()多路复用

#define _GNU_SOURCE
#include <sys/poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask); //timeout以秒和纳秒计指定了时限,sigmask提供一组等待处理的信号

poll()与select()

①poll()无需使用者计算最大的文件描述符值加一和传递参数。
②poll()在应对较大值的文件描述符更具效率。
③select() 的文件描述符集合是静态大小。集合小限制了select()可以监视文件描述符的最大值,要么较大效率不高,尤其是当不能确定集合的组是否稀疏时,对较大位掩码的操作效率不高;使用poll()则可以创建合适大小数组。
④若用select,文件描述符集合会在返回时重新创建,每次调用都必须要重新初始化。poll分离输入和输出,宿主无需改变就可重用
⑤select的timeout返回时未定义,可移植代码需要重新初始化,pselect没有这个问题

select系统调用确有不错的地方
①select的可移植性好。
②select提供了更好的更好的超时方案到微妙级别。

五、内核内幕

虚拟文件系统(VFS)、页缓存、页回写和I/O调度器这些子系统使linux的I/O看起来无缝运转且更高效

虚拟文件系统

是一种内核的文件操作的抽象机制,允许内核无需了解文件系统的情况下使用文件系统函数和操作文件系统数据。
VFS用通用文件模型实现这种抽象方法,基于函数指针和面向对象的方法。文件模型提供内核文件必须遵循的框架,它允许对文件系统发起请求。框架提供了钩子来支持读、建立链接、同步及其他功能。

页缓存

页缓存是一种在内存中保存最近在磁盘文件系统上访问过的数据的方式,利用时间局部性原理。页缓存是内核寻找文件系统数据的第一目的地。页缓存大小是动态的。

页回写

内核使用缓冲来延迟写操作,当一个进程发起写请求,数据被拷贝进一个缓冲区,并将该缓冲区标记为“脏”,这意味着内存中的拷贝要比磁盘上的新。
最终哪些”脏“缓冲区需要写入磁盘,将磁盘文件和内存数据同步,这就是回写,一下两个条件会触发回写:
当空闲内存小于设定的阈值的时候,脏的缓冲区会回写到磁盘,被清理的缓冲区可能会被移除,来释放内存空间。
当一个脏的缓冲区说明超过设定的阈值时,缓冲区被回写到磁盘。
回写有一些叫pdflush的内核线程操作。可能有多个pdflush线程在回写,利用并行性避免阻塞。缓冲区使用buffer_head结构来表示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值