APUE学习笔记2——文件I/O
1 引言
本章首先讨论Unix系统中大多数文件I/O最常用的5个系统函数:open、read、write、lseek以及close。
本章所说明的函数又被成为不带缓冲的I/O,不带缓冲是指每个read和write都调用内核中的一个系统调用。
接着讨论原子操作的概念,只要涉及到多个进程之间共享资源,原子操作相当重要。
最后,本章将进一步讨论在多个进程之间共享文件以及所涉及的内核数据结构,之后将说明dup、fcntl、sync、fsync和ioctl函数。
2 文件描述符
对于内核而言,所有的打开的文件都由文件描述符引用。文件描述符是一个非负整数。Unix系统会按照惯例把下列描述符和系统输入相关联:
0 | 标准输入 | STDIN_FILENO |
1 | 标准输出 | STDOUT_FILENO |
2 | 标准错误输出 | STDERR_FILENO |
3 open、create、close函数
#include <fcntl.h> int open(const char *pathname, int oflag, ... /*mode_t mode*/);
/*返回:成功返回文件描述符,出错返回-1*/
调用open函数可以打开或者创建一个文件。对于open函数而言,仅当创建新文件时才使用最后一个参数,oflag来说明文件选项:
(1)打开方式(必选)
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以读写方式打开 |
(2)可选项
O_APPEND | 每次写时都追加到文件的尾端 |
O_CREAT | 若文件不存在则创建,若文件存在则返回错误。使用此选项时,需要第三个参数mode来指定新文件的访问权限位 |
O_EXCEL | 测试文件是否存在,一般与O_CREAT搭配使用 |
O_TRUNC | 若文件存在,且为只写或读写打开,则将其长度截短为0 |
O_NONBLOCK | 如果pathname指的是一个FIFO、一个块特殊文件或者是一个字符特殊文件,则此选项为文件的本次打开和后续的I/O操作设置为非阻塞模式 |
…… |
由open返回的文件描述符一定是最小的未用描述符数值。
也可调用一个creat创建一个新文件。
#include <fcntl.h> int creat(const char *pathname, mode_t mode);
/*返回:成功返回只写打开的文件描述符,出错返回-1*/
此函数等同于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
调用close函数关闭一个打开的文件。关闭一个文件还会释放该进程加在该文件上的所有记录锁。
#include <unistd.h> int close(int fd)
/*返回:成功返回0,失败返回-1*/
4 lseek函数
每个打开的文件都有一个与其相关联的文件偏移量offset。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常读写操作从当前文件偏移量开始,并使偏移量增加读写的字节数。当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被默认设置为0。
可以调用lseek显式地为一个打开的文件设置文件偏移量。
#include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
/*返回:成功返回新的偏移量,出错返回-1*/
对参数offset的解释与参数whence的值有关,若whence的值是:
SEEK_SET | 将该文件偏移量设置为距文件开始处offset个字节 |
SEEK_CUR | 当前偏移量加offset,offset可正可负 |
SEEK_END | 文件长度加offset,offset可正可负 |
例如,获取当前偏移量:cur_pos = lseek(fd, 0, SEEK_CUR);
若文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。
文件偏移量可以是负值,但对于普通文件其偏移量必须是非负子,因此在比较lseek的返回值时应谨慎,不要测试它是否小于0,而是测试它是否等于-1.
文件偏移量可以大于当前文件长度,只是这样做会在文件中形成一个空洞,位于文件中但没有写过的字节都被读为0,文件的空洞并不占用磁盘存储空间。
lseek只修改文件表项的当前文件偏移量,不进行任何I/O操作。
5 read、write函数
#include <unistd.h> ssize_t read(int fd, void *buf, size_t bytes);
ssize_t write(int fd, const void *buf, size_t bytes);
/*以上函数返回:成功返回读(写)到的字节数,若已到文件尾返回0,出错返回-1*/
有多种情况使得实际读到的字节数小于要求的字节数:
- 读普通文件时,在读到要求的字节数之前就到达了文件尾端
- 从终端设备读取,通常只能一次读一行
- 从网络设备、FIFO或管道读取,设备的缓冲小于要求的字节数
- 当某一设备中断,而已经读了部分数据量时
- ……
write函数的返回值通常与参数nbytes的值相同,否则表示出错。对于普通文件,写操作从文件的当前偏移量开始。
6 文件共享
Unix系统支持在不同的进程之间共享打开的文件。内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响,如下图所示:
(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开的文件描述符表。与每个文件描述符相关联的是:文件描述符标志(close_on_exec)和指向一个文件表项的指针。
(2)内核为所有打开文件维护一张文件表。每个文件表包含:文件状态标志(读、写、添写、同步、非阻塞等)、当前文件偏移量和指向该文件的v节点表项指针。
(3)每个打开的文件(或设备)都有一v节点结构。v节点包含了文件类型和对文件进行各种操作的函数指针。对于大多数文件,v节点还包含了该文件的i节点。
给出了数据结构后,对前面描述的操作进行进一步的说明:
- 在完成每个write之后,在文件表项中的当前文件偏移量即增加的字节数。如果使当前文件偏移量超过了当前文件长度,那么i节点表项的当前文件长度被设置为当前文件偏移量(文件被加长了)
- 如果用O_APPEND选项打开了一个文件,则相应的标志也被设置到文件表项的文件状态标志中。此时每次进行写操作时,文件表项中的当前文件偏移量首先被设置为i节点里的当前文件长度。这就使得每次写的数据都能添加到文件的尾部。
- 如果一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点里的当前文件长度(但是与O_APPEND选项打开文件时不同的)。
- lseek函数只修改文件表项的当前文件偏移量,不进行任何I/O操作
两个独立的进程打开同一个文件:
注意,文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
7 原子操作
之前描述的方法对多个进程读取同一个文件都能正确工作,因为每个进程都有自己的文件表项和当前文件偏移量。但是多个进程写一个文件时,可能产生意想不到的后果。为了避免这种情况,需要理解原子操作的概念。
原子操作是指不会被线程调度打断的操作,因此任何一个需要多个函数调用的操作都不可能是原子操作。
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
调用pread和pwrite相当于顺序调用lseek、read或write,但是他们是原子操作:
- 调用时无法中断其定位和读写操作
- 不更新文件指针
8 dup、dup2函数
#include <unistd.h> int dup(int fd);
int dup2(int fd, int fd2);
/*返回:成功返回新的描述符,失败返回-1*/
dup和dup2函数用来复制一个现有的文件描述符。
dup返回值是当前可用的文件描述符的最小值,而dup2可以用fd2指定新的文件描述符。若fd2已经打开,则将其关闭,若fd2==fd,则dup2返回fd2。
正如之前描述的那样,dup和dup2函数返回的新的文件描述符和参数fd共享同一个文件表项。
复制一个文件描述符的另一个方法是使用fcntl函数:
dup(fd); 等效于 fcntl(fd, F_DUPFD, 0)
dup2(fd, fd2); 等效于 close(fd2); fcntl(fd, F_DUPFD, fd2);
它们之间的区别是:
- dup2是一个原子操作
- dup2和fcntl有某些不同的errno
9 sync、fsync、fdatasync函数
#include <unistd.h> int sync(void);
int fsync(int fd);
int fdatasync(int fd);
/*以上函数返回:成功返回0,出错返回-1*/
Unix内核中一般设有缓存区,大多数磁盘I/O都通过缓冲进行,这样能减少磁盘读写次数。
sync只是将所有修改过的缓冲写入队列,然后就返回,不等待实际磁盘操作结束。通常称为update的系统守护进程会定期的调用sync函数。
fsync只对文件描述符fd指向的单一文件起作用,并等待磁盘操作结束后返回。
fdatasync函数类似于fsync,但它只影响文件数据部分,还会同步更新文件属性。
10 fcntl函数
fcntl函数可以改变以打开文件的性质。
#include <fcntl.h> int fcntl(int fd, int cmd, .../*int arg*/);
/*返回:成功返回依赖于cmd,失败返回-1*/
fcntl函数有5种功能,取决于cmd参数:
F_DUPFD | 复制一个现有的描述符 |
F_GETFD/F_SETFD | 获得/设置文件描述符标记 |
F_GETFL/F_SETFL | 获得/设置文件状态标志 |
F_GETOWN/F_SETOWN | 获得/设置异步I/O所有权 |
F_GETLK、F_SETLK或F_SETLKW | 获得/设置文件记录锁 |
APUE学习笔记2——文件I/O的更多相关文章
- APUE学习笔记3_文件IO
APUE学习笔记3_文件IO Unix中的文件IO函数主要包括以下几个:open().read().write().lseek().close()等.这类I/O函数也被称为不带缓冲的I/O,标准I/O ...
- APUE学习笔记3——文件和目录
1 简介 之前学习了执行I/O操作的基本函数,主要是围绕普通文件I/O的打开.读或写.下面继续学习Unix文件系统的其他特征和文件的基本性质.我们将从stat函数开始,了解stat结构所代表的文件属性 ...
- APUE 学习笔记(三) 文件和目录
1. 文件类型 文件类型信息包含在 struct stat 里的 st_mode 成员 (1)普通文件,unix内核并不区分文本文件和二进制文件 (2)目录文件,这种文件包含了其他文件的名字以及指向这 ...
- APUE 学习笔记(二) 文件I/O
1. 文件I/O 对于内核而言,所有打开的文件都通过文件描述符引用,内核不区分文本文件和二进制文件 open函数:O_RDONLY O_WRONLY O_RDWR create函数: close函 ...
- Linux系统学习笔记:文件I/O
Linux支持C语言中的标准I/O函数,同时它还提供了一套SUS标准的I/O库函数.和标准I/O不同,UNIX的I/O函数是不带缓冲的,即每个读写都调用内核中的一个系统调用.本篇总结UNIX的I/O并 ...
- SpringMVC:学习笔记(8)——文件上传
SpringMVC--文件上传 说明: 文件上传的途径 文件上传主要有两种方式: 1.使用Apache Commons FileUpload元件. 2.利用Servlet3.0及其更高版本的内置支持. ...
- MySQL学习笔记-数据库文件
数据库文件 MySQL主要文件类型有如下几种 参数文件:my.cnf--MySQL实例启动的时候在哪里可以找到数据库文件,并且指定某些初始化参数,这些参数定义了某种内存结构的大小等设置,还介绍了参数类 ...
- Django:学习笔记(8)——文件上传
Django:学习笔记(8)——文件上传 文件上传前端处理 本模块使用到的前端Ajax库为Axio,其地址为GitHub官网. 关于文件上传 上传文件就是把客户端的文件发送给服务器端. 在常见情况(不 ...
- APUE学习笔记——10.9 信号发送函数kill、 raise、alarm、pause
转载注明出处:Windeal学习笔记 kil和raise kill()用来向进程或进程组发送信号 raise()用来向自身进程发送信号. #include <signal.h> int k ...
随机推荐
- mvc cshtml 字符串操作
@using System.Text; @{ ; string str=""; StringBuilder sb = new StringBuilder(); } @foreach ...
- ZBrush看一个球体如何演变为头发
对于头发的制作,ZBrush®是多样的,前面的教程中也有讲解,可以用球体作为子工具,用不同笔刷进行雕刻:还可以使用Z球转网格的方式.今天小编讲述一种最简单的方法,使用球体演变头发,这样的创作手法更高效 ...
- Spring依赖注入:@Autowired,@Resource和@Inject区别与实现原理
一.spring依赖注入使用方式 @Autowired是spring框架提供的实现依赖注入的注解,主要支持在set方法,field,构造函数中完成bean注入,注入方式为通过类型查找bean,即byT ...
- 如何添加删除子网卡eth0:1(linux案例)
这种方法实现了单网卡多IP,我的系统是centos7的,如何添加删除子网卡IP详细请看下面操作例子 添加子网卡IP:ifconfig ens3:1 192.168.0.100/24 ...
- 小白学习Spark系列二:spark应用打包傻瓜式教程(IntelliJ+maven 和 pycharm+jar)
在做spark项目时,我们常常面临如何在本地将其打包,上传至装有spark服务器上运行的问题.下面是我在项目中尝试的两种方案,也踩了不少坑,两者相比,方案一比较简单,本博客提供的jar包适用于spar ...
- 数据结构(5) 第五天 快速排序、归并排序、堆排序、高级数据结构介绍:平衡二叉树、红黑树、B/B+树
01 上次课程回顾 希尔排序 又叫减少增量排序 increasement = increasement / 3 + 1 02 快速排序思想 思想: 分治法 + 挖坑填数 分治法: 大问题分解成各个小问 ...
- BZOJ 3876 [AHOI/JSOI2014]支线剧情 (最小费用可行流)
题面:洛谷传送门 BZOJ传送门 题目大意:给你一张有向无环图,边有边权,让我们用任意条从1号点开始的路径覆盖这张图,需要保证覆盖完成后图内所有边都被覆盖至少一次,求覆盖路径总长度的最小值 最小费用可 ...
- 邓_ Php·面试
1:PHP的意思,它能干什么? PHP是一个基于服务端来创建动态网站的脚本语言,您可以用PHP和HTML生成网站主页,英文的全称(Professional Home Pages)1.Web ...
- Tensorflow高效读取数据的方法
最新上传的mcnn中有完整的数据读写示例,可以参考. 关于Tensorflow读取数据,官网给出了三种方法: 供给数据(Feeding): 在TensorFlow程序运行的每一步, 让Python代码 ...
- keepalived安装实现nginx主备高可用
. keepalived安装环境 su - root yum -y install kernel-devel* yum -y install openssl-* yum -y install popt ...