Chinaunix首页 | 论坛 | 博客
  • 博客访问: 852171
  • 博文数量: 286
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1980
  • 用 户 组: 普通用户
  • 注册时间: 2014-05-04 16:41
文章分类

全部博文(286)

文章存档

2020年(2)

2018年(5)

2017年(95)

2016年(69)

2015年(15)

2014年(100)

我的朋友

分类: 系统运维

2014-05-10 15:02:53

原文地址:APUE2读书笔记(3) 作者:mjxian

第三章 文件I/O

这一章讲的是UNIX的基本I/O函数:open, write, read, close, lseekdupfcntl等。它们又被称为不带缓冲的I/O,这是因为readwrite函数直接进行系统调用,而不在进程地址空间中另外开辟缓冲区。

1、文件描述符file descriptor

文件描述符是对文件的引用,本身是个int类型的数值。它的取值在进程内是唯一且循环使用的。文件描述符012(通常使用中定义的STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO)则用作进程的标准输入文件、标准输出文件和标准出错文件。

标准输入、标准输出和标准出错对应的设备文件注册在目录/dev中,文件名分别为stdinstdoutstderr。使用命令"ls -l"可以发现,它们实际上分别是指向/proc/self/fd/0/proc/self/fd/1/proc/self/fd/2的软链接。

虚拟目录/proc/self/fd中记录了当前进程所打开的文件描述符。通过命令"ls -l"可以看到这些文件描述符分别引用了系统中哪些文件(软链接的目标)。可以看到文件描述符012会指向tty或者pipe之类的设备,这说明这些进程是和这些设备进行数据读/写的。还可以看到对于守护进程,012都是链接到/dev/null的,这说明守护进程不会跟任何的接口进行交互。

2、打开文件:open函数

#include <fcntl.h>

int open(const char *filename, int oflag);
int open(const char *filename, int oflag, mode_t mode);

该函数以oflag指定的方式打开字符串filename指定的文件,成功后返回filename对应的文件描述符,失败时返回-1,并设置errno指代失败原因(例如:EACCES——Permission denied)。

oflag包括了O_RDONLY(以只读方式打开)、O_WRONLY(以只写方式打开)、O_RDWR(以读写方式打开),这三个标志只能使用一个。否则使用例如O_RDONLY | O_WRONLY | O_RDWR这样的方式打开文件,在编译时可以通过甚至不会发出警告(我在gcc 4.2,使用-Wall选项时看到也不会有警告),但此时读写方式是不可预料的;

除了读写方式标志外,oflag还可以通过按位或运算方式同时加入其它标志。包括O_APPEND(写时追加到尾端)、O_CREAT(文件不存在的话则创建,否则忽略此标志)、O_EXCL(只用于与O_CREAT结合,此时文件若已存在open调用将失败)、O_TRUNC(用写标志打开且文件存在时将文件长度截为0)、O_NONBLOCK(以非阻塞方式打开文件,如果要求的读写操作不能马上执行的话立即返回失败,常用于管道、字符终端等特殊文件);

还包括三个POSIX可选的同步标志:O_DSYNCO_RSYNCO_SYNC。对于Linux,三个标志的含义都与O_SYNC这个标志相同,使用此标志时,write操作将阻塞到内核将内容真正同步到设备,文件在这之前将一直保持打开。

mode为文件创建权限,与进程euidumask进行“或”操作成为文件的权限位。熟悉chmod(1)命令则自然知道其具体用法。

3、创建新文件:creat函数

#include <fcntl.h>

int creat(const char *filename, mode_t mode);

据记载这个函数的名字确实是当年实现时的拼写错误,而一直被后世沿袭。它以只写方式创建并打开一个新文件,如果文件已存在,则文件被截短为0。相当于执行了

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

4、关闭文件:close函数

#include <unistd.h>

int close(int filedes);

关闭指定的文件描述符。同时,如果进程在此文件上加有记录锁,将释放。

在进程终止时,内核将自动关闭进程打开的文件。

5、定位文件:lseek函数

#include <unistd.h>

off_t lseek(int filedes, off_t offset, int whence);

lseek执行时,将文件filedes的当前读写位置更改到相对whence指定的位置offset处的地方。whence包括了SEEK_SET(文件开始)、SEEK_END(文件末尾)、SEEK_CUR(当前),后两者的offset可以是负数。offset的类型off_t通常定义为一个机器字的长度( 一般typedeflong类型,因为根据标准Clong在任何机器上都是和机器字的长度相同的。对于32位平台则为4个字节)。

lseek成功时将返回相对于文件开始处的偏移量(可能是负数),失败返回-1并设置errno。在文件是FIFO、管道或者套接字时,lseek将失败并设置errnoESPIPEIllegal seek)。

lseek只更改进程打开文件的状态,并不会引起I/O操作。

6、读文件数据到缓冲区(输入操作):read函数

#include <unistd.h>

ssize_t read(int filedes, void *buf, size_t nbytes);


read按指定的字节数nbytes从文件filedes的当前位置处读取数据,输入到缓冲区buf中。

其返回值:

为正数时:为实际读取的字节数,

0时:已经读到EOF

-1时:调用失败,同时errno被设置。

如果文件打开时未指定O_NONBLOCK标志,对其的read调用可能发生阻塞等待可读。阻塞时如果进行了信号捕捉,read将直接失败。

注意参数nbytes的类型为无符号的size_t(即必须为正整数),而返回值是有符号的ssize_t

7、从缓冲区写数据到文件(输出操作):write函数

#include <unistd.h>

ssize_t write(int filedes, const void *buf, size_t nbytes);

write按指定的字节数nbytesbuf处取数据,输出到文件filedes的当前位置处,如果已经到文件末尾,将增加文件长度并在最后添加EOF标志。

其返回值:

为正数时:为实际写入的字节数,

-1时:函数出错,同时errno被设置。

readwrite操作一次写入数据的大小将会影响其I/O效率,通常按文件系统的块大小(文件stat结构的st_blksize)设置。

8、定位同时读写文件的原子操作:preadpwrite函数

#include <unistd.h>

ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);

这两个函数先将文件位置定位到距开始offset处,然后对其按给定参数进行读/写。这两个步骤是原子操作,这意味这要不这些步骤一次性全部执行,要不就不执行。如果不是原子操作,则可能会由于内核调度或者信号处理等原因,使其他进程插到几个步骤之间更改所操作对象的状态,而引起后续操作发生意外。

9、复制文件描述符:dupdup2函数

#include <unistd.h>

int dup(int filedes);
int dup2(int filedes, int filedes2);

dup函数使用一个当前进程中可用的最小文件描述符引用filedes所引用的文件,这个新的文件描述符的状态(打开标志、模式、当前位置等)和filedes相同。若成功,返回这个新的文件描述符。失败时返回-1(例如filedes不存在)并设置errno

dup2可以直接指定dup中新的文件描述符的值为filedes2,如果filedes2已经打开,则先原子的关闭之。如果filedes==filedes2且存在时则直接返回文件描述符而不执行关闭。失败时也返回-1并设置errno

dup2常用于输入/输出重定向以实现管道操作。例如

dup2(filedes, STDOUT_FILENO);

则重定向进程的标准输出到filedes,相当于在shell中使用重定向操作符执行了"> file"

dup2的功能也可以用fcntl(2)实现,但后者不能实现为原子操作且某些errno不同。

10、更新到实际文件:syncfsyncfdatasync函数

通常内核为了考虑吞吐效率等情况,write调用成功后并不马上将数据写到磁盘,而是放在磁盘的缓存区中并通过缓存区交换算法(例如最近最少使用)不定期的同步数据到磁盘,或者通过守护进程定时进行数据同步,也可以通过调用sync使其马上进行数据同步。


#include <unistd.h>

int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);

sync立即将内核缓冲的数据送到磁盘中的写队列,并直接返回。

fsync也立即将内核缓冲的数据送到磁盘中的写队列,等待到磁盘写结束时才返回。

fdatasync类似fync,但除了同步数据外还同步文件的属性(例如stat结构的st_ctime等)。

也可用系统命令sync(1)来同步数据。

11、已打开文件状态的更改:fcntl函数

#include <fcntl.h>

int fcntl(int filedes, int cmd, /* int arg*/);

cmd包括了以下取值:

F_DUPFD 复制文件描述符并返回新的文件描述符(dup)

F_GETFD/F_SETFD 获取/重设文件描述符的标志(FD_CLOEXEC,用于指出执行exec调用时是否关闭此文件)

F_GETFL/F_SETFL 获取/重设文件描述符的打开状态标志(O_RDWRO_NONBLOCK等)

F_GETOWN/F_SETOWN 获取/重设捕捉信号SIGIO(异步I/O时,若I/O已经可用则产生此信号)和信号SIGURG(收到带外数据时产生此信号)的pid

F_GETLK/F_SETLK 获取/设置记录锁,在并发场合用于同步文件的操作时序。

arg为根据cmd进行不同取值的相关参数,略。

12ioctl函数

对设备进行指定的操作,适用于readwritelseekfcntl等函数不能完成的其它功能。对于Linux,手册中称其用于操作STREAMS设备,但Linux下几乎用不上STREAMS设备。其它平台对ioctl函数的用途不尽相同,略。

阅读(1047) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~