第三章:文件I/O
本章开始讨论UNIX系统的文件I/O函数,包括打开文件、读文件、写文件等。
UNIX系统中的大多数文件I/O只需要用到5个函数:open、read、write、lseek和close。它们每执行一次都会调用内核中的系统调用,也就是常说的不带缓冲的I/O函数。
只要涉及多个进程间共享资源(比如同时读写某个文件),原子操作的概念就变得很重要,我将通过open()函数来讨论此概念。
一、文件描述符
文件描述符是一个非负整数,每一个使用open()函数打开的文件都会分配一个文件描述符。
在第一章提到的标准输入、标准输出、标准错误分别对应文件描述符0、1、2。
每个操作系统中的文件描述符个数都是一定的,我们可以使用如下shell命令查看最大文件描述符数值:
$ ulimit -n
也可使用下面的代码进行查看:
- #include <unistd.h>
- sysconf(_SC_OPEN_MAX)
二、文件操作函数
UNIX系统中的大多数文件I/O只需要用到5个函数:open、read、write、lseek和close。我在下面给出它们的函数声明、例子和函数作用:
- /* 文件打开函数 */
- #include <fcntl.h>
- int open(const char *path, int oflag, ... /* mode_t mode */ );
- int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );
- /* 例子 */
- int fd = open("a.txt", O_RDWD | O_CREAT, );
- if (fd == -) /* 失败 */
- return -;
函数参数以及返回值:
path:需要打开文件的路径
oflag:打开文件的权限(比如是否可以读、是否可以写等)
mode:创建文件(O_CREAT)的权限,0666表示用户权限、组权限和其他人权限,我将在下面介绍
返回值:成功返回文件描述符;出错返回-1
关于oflag的定义一般有以下几类,不同类之间可以如上面例子,使用“|”组合。常用的我会使用加粗字体。
O_RDONLY、O_WRONLY、O_RDWR、O_EXEC:只读、只写、既可以读也可以写、只执行
O_APPEND:追加到文件的尾部
O_CREAT:如果没有名为path的文件,就会创建此文件
O_TRUNC:如果此文件存在,而且以只写或读写打开,将其长度截断为0,也就是删除文件内容
在我们使用shell命令ls -al时,会显示文件的权限,如:
drwxr-xr-x 2 lioker lioker 4096 7月 6 2017 Music
drwxr-xr-x其中的d表示目录,d后面的rwx表示用户权限为7(rwx分别用二进制表示,如果有权限为1,没有权限为0);rwx后面的r-x表示组中没有写权限,为5,其他人权限与组权限相同。
- /* 文件读写函数 */
- #include <unistd.h>
- ssize_t read(int fd, void *buf, size_t nbytes);
- ssize_t write(int fd, const void* buf, size_t n);
- /* 例子 */
- int res; /* 用来接收读写文件的返回值 */
- int buf[] = {, , };
- int rcv;
- res = write(fd, buf, * ); /* 3为个数,4为字节数 */
- printf("Write Bytes: %d\n", res);
- res = read(fd, &rcv, ); /* buf为World World */
- printf("Read Bytes: %d Content: %d\n", res, rcv);
函数参数以及返回值:
fd:文件打开函数返回的文件描述符
buf:读/写变量数据
nbytes:需要读的字节数
n:需要写的字节数
返回值:成功返回实际读写的字节数;出错返回-1
如果读者执行上面两行代码,会发现read函数返回的是写入的值。
这是由于文件有一个与其关联的“文件偏移量”,用于表示当前文件读/写到哪里。这个偏移量我们可以使用lseek函数控制。
还有一点需要读者注意:
对于read和write函数,一定要注意其操作的是内存中的字节数,比如要用read和write去读写int类型变量,则一次性要读写32位,也就是4字节。因此其是二进制还是文本模式取决于对字节的解释。具体请查看上面的例子。
- /* 文件偏移量控制 */
- #include <unistd.h>
- off_t lseek(int fd, off_t offset, int whence);
- /* 例子 */
- lseek(fd, 0, SEEK_SET);
函数参数以及返回值:
fd:文件打开函数返回的文件描述符
offset:偏移量
whence:偏移量设置方式
返回值:成功返回新的文件偏移量;出错返回-1
关于whence有以下几类(下面的offset就是lseek()的第二个参数数值):
SEEK_SET:从文件开始处开始偏移offset
SEEK_CUR:从当前位置处开始偏移offset
SEEK_END:从文件结尾处开始偏移offset
- /* 文件关闭函数 */
- #include <unistd.h>
- int close(int fd);
- /* 例子 */
- close(fd);
函数参数以及返回值:
fd:文件打开函数返回的文件描述符
返回值:成功返回0;出错返回-1
三、文件操作示例
- #include <stdio.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <string.h>
- #define BUFSIZE 4096
- int main()
- {
- int fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, );
- int res; /* 用来接收读写文件的返回值 */
- char snd = 'A';
- int i;
- char buf[BUFSIZE] = {};
- #if 1
- for (i = ; i < BUFSIZE - ; ++i) {
- buf[i] = snd;
- }
- res = write(fd, buf, strlen(buf));
- printf("Write Bytes: %d\n", res);
- #else
- for (i = ; i < BUFSIZE; ++i) {
- write(fd, &snd, );
- }
- #endif
- lseek(fd, , SEEK_SET);
- res = read(fd, buf, BUFSIZE);
- printf("Read Bytes: %d Content: %s\n", res, buf);
- return ;
- }
四、I/O的效率
由于read()和write()是不带缓冲的,因此每一次的调用都会进行一次内核调用,这会对I/O的效率造成很大的影响。
比如把第三节程序的第18行#if 1改为0,然后在命令行编译后使用,可以发现效率有明显的降低:
time ./a.out
一般在使用时,我们会定义一个缓存区用于存储数据,等到数据满了之后,再调用read()和write()。
五、原子操作
原子操作指的是一系列的操作是密不可分的,要么完成全部,要么一个都没完成,是不可能只执行了其中的一部分的。
六、dup()和dup2()
dup()和dup2()用来复制一个现有的文件描述符。函数声明如下:
- /* 复制文件描述符函数 */
- #include <unistd.h>
- int dup(int fd);
- int dup2(int fd, int fd2);
- /* 例子 */
- int new = dup(fd);
- printf("Dup New Fd: %d\n", new);
- int new = dup2(fd, new);
- printf("Dup New Fd: %d\n", new);
函数参数以及返回值:
fd:文件打开函数返回的文件描述符
fd2:需要fd变换为的文件描述符
返回值:成功返回新的文件描述符;出错返回-1。对于dup()来说,它会返回未使用的最小值;对于dup2()来说,如果fd2已经被占用,则会先关闭fd2,然后返回fd2;当fd1和fd2相等时,直接返回fd2。
需要注意的是,dup()和dup2()创建的新fd共享传入参数fd的文件标志和文件偏移量。
七、fcntl()
fcntl()可以用来获取或设置文件描述符的属性。函数声明如下:
- /* 文件描述符属性控制函数 */
- #include <fcntl.h>
- int fcntl(int fd, int cmd, ...);
- /* 例子 */
- int acc = fcntl(fd, F_GETFL, );
- int flg = acc & O_ACCMODE;
- if (flg & O_RDONLY)
- printf("O_RDONLY\n");
- else if (flg & O_WRONLY)
- printf("O_WRONLY\n");
- else if (flg & O_RDWR)
- printf("O_RDWR\n");
- else
- printf("NONE\n");
函数参数以及返回值:
fd:文件打开函数返回的文件描述符
cmd:命令操作
返回值:成功返回对应的值;出错返回-1。
关于cmd有以下几类:
F_DUPFD:复制一个已有的描述符
F_GETFD、F_SETFD:获取或设置文件描述符标志
F_GETFL、F_SETFL:获取或设置文件状态标志
F_GETLK、F_SETLK、F_SETLKW:获取或设置记录锁,其中F_SETLKW表示若设置记录锁失败,则会等待直至成功
利用fcntl()函数修改文件描述符标志或者文件状态标志时,必须先获取当前的标志状态,然后再追加更新,最后将新的状态标志设置写入回去,如果直接设置会导致旧的标志被复位。
第六节讲解的dup()和dup2()也可以使用fcntl()完成。代码如下:
- /* 相当于dup */
- int newfd;
- newfd = fcntl(fd, F_DUPFD, );
- printf("New Fd: %d\n", newfd);
- /* 相当于dup2(),区别需要注意:dup()是原子操作 */
- close(newfd);
- newfd = fcntl(fd, F_DUPFD, newfd);
- printf("New Fd: %d\n", newfd);
八、ioctl
ioctl()函数是一个功能比较混杂的函数。通常用于终端I/O,函数声明如下:
- /* 终端I/O控制函数 */
- #include <sys/ioctl.h>
- int ioctl (int fd, unsigned long int request, ...);
九、其他函数
文件创建函数creat(),函数声明如下:
- /* 文件创建函数 */
- #include <fcntl.h>
- int creat(const char *pathname, mode_t mode);
此函数有一个致命的缺陷:不是原子操作。因此一般使用open()函数和O_CREAT选项代替。
同步函数sync()、fsync()和fdatasync(),函数声明如下:
- /* 同步函数 */
- #include <unistd.h>
- void sync(void);
- int fsync(int fd);
- int fdatasync(int fd);
UNIX操作系统中有磁盘缓冲功能,当程序向硬盘写入内容时,并不会每次都去写硬盘,而是将待写入的东西缓存buffer中,在稍后将多次缓存的数据一次性写入硬盘,这种方式称为延迟写。通常内核会在缓冲区满了或者需要重用缓冲区时进行刷新写入。因此提供了上面三个函数,其作用如下:
sync()对整个缓冲区作用生效,不等待实际磁盘操作的结束就返回;
fsync()对指定的文件描述符作用生效,它等待磁盘操作结束才返回;
fdatasync()函数和fsync()函数类似,区别是它只刷新文件的数据部分,不刷新文件的属性部分。
下一章 第四章:文件和目录
第三章:文件I/O的更多相关文章
- 第三章 文件 I/O
3.1 引言 先说明可用的文件 I/O 函数:open.read.write.close,然后说明不同缓冲区长度对read和write函数的影响. 本章所说的函数经常被称为不带缓冲的 I/O (unb ...
- apue学习笔记(第三章 文件I/O)
本章开始讨论UNIX系统,先说明可用的文件I/O函数---打开文件.读写文件等 UNIX系统中的大多数文件I/O只需用到5个函数:open.read.write.lseek以及close open函数 ...
- 2 python第三章文件操作
1.三元运算 三元运算又称三目运算,是对简单的条件语句的简写,如: 简单条件语句: if 条件成立: val = 1 else: val = 2 改成三元运算: val = 1 if 条件成立 els ...
- 《UNIX环境高级编程》(APUE) 笔记第三章 - 文件I/O
3 - 文件I/O Github 地址 1. 文件描述符 对于内核而言,所有打开的文件都通过 文件描述符 (file descriptor) 引用.当打开一个现有文件或创建一个新文件时,内核向进程返回 ...
- 第三章 文件IO复习
open(const char * path, int flag.../*mode_t*/) #include <fcntl.h> path:绝对路径 flag:O_RDONL ...
- Windows Pe 第三章 PE头文件(上)
第三章 PE头文件 本章是全书重点,所以要好好理解,概念比较多,但是非常重要. PE头文件记录了PE文件中所有的数据的组织方式,它类似于一本书的目录,通过目录我们可以快速定位到某个具体的章节:通过P ...
- 《Django By Example》第三章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:第三章滚烫出炉,大家请不要吐槽文中 ...
- 《Linux内核设计与实现》读书笔记 第三章 进程管理
第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种.我们拥有操作系统就是为了运行用户程序,因此,进程管理就是所有操作系统的心脏所在. 3.1进程 概念: 进程:处于执行期的程序.但不仅局限于 ...
- 精通Web Analytics 2.0 (5) 第三章:点击流分析的奇妙世界:指标
精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第三章:点击流分析的奇妙世界:指标 新的Web Analytics 2.0心态:搞定它.新的闪亮系列工具:是的.准备好了吗?当然 ...
随机推荐
- 使用RQShineLabel
使用RQShineLabel https://github.com/zipme/RQShineLabel 最终效果: 源码: // // RootViewController.m // UseText ...
- 重写UIImageView的image属性
重写UIImageView的image属性 效果: 当你重写了UIImageView的image属性后你就会对UIImageView怎么显示图片了如指掌了:) 源码: UIImageView.h + ...
- How to Remove A Service Entry From Win10 Service List
Warning Please do this operation CAREFULLY, otherwise you may get something wrong with your system. ...
- iOS自动化-iOS录屏xrecord及解决iPhone设备不显示的问题
github地址:https://github.com/WPO-Foundation/xrecord 安装方法: git clone https://github.com/WPO-Foundation ...
- Python3基本数据类型(二、字符串)
Python3字符串 ①字符串比较 1.比较字符串是否相同: ==:使用==来比较两个字符串内的value值是否相同 is:比较两个字符串的id值. 2.字符串的长度比较 len():显示字符串的长度 ...
- HTTP协议图--HTTP 报文实体
1. HTTP 报文实体概述 HTTP 报文结构 大家请仔细看看上面示例中,各个组成部分对应的内容. 接着,我们来看看报文和实体的概念.如果把 HTTP 报文想象成因特网货运系统中的箱子,那么 H ...
- WinRAR(5.21)-0day漏洞-始末分析
0x00 前言 上月底,WinRAR 5.21被曝出代码执行漏洞,Vulnerability Lab将此漏洞评为高危级,危险系数定为9(满分为10),与此同时安全研究人员Mohammad Reza E ...
- 关于$.fn.scrollPath is not a function
关于$.fn.scrollPath is not a function 在做项目过程中,用到了一个jQuery的滚动路径插件——jQuery Scroll Path.引入相关的js文件后,但是控制台一 ...
- git回滚线上代码
由于之前自己推代码的时候操作失误,push代码的时候没有push到线上的dev分支,而是push到了线上master分支(主要是因为没有在命令后写分支名,直接推到默认master分支上了),覆 ...
- BZOJ3997:[TJOI2015]组合数学(DP,Dilworth定理)
Description 给出一个网格图,其中某些格子有财宝,每次从左上角出发,只能向下或右走.问至少走多少次才能将财宝捡完.此对此问题变形,假设每个格子中有好多财宝,而每一次经过一个格子至多只能捡走一 ...