Unix环境高级编程学习——文件IO

文件I/O

引言

先说明可用的文件I/O函数————打开文件,读文件,写文件等。UNIX系统大多数文件I/O只需要用到5个函数:open、read、write、lseek以及close。然后说明不同缓冲长度对read和write函数的影响。

本章描述的函数经常被称为不带缓冲的I/O,(将于标准的I/O对照)。术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。

文件描述符

对于内核而言,所有打开的文件都可以通过文件描述符引用。它是一个非负整数,变化范围时0~OPEN_MAX-1。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,将这个文件描述符作为参数传递给read或write。

UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述1与标准输出关联,文件描述符2与标准错误关联

在符合POSIX.1的应用程序中,幻数0、1、2虽然已被标准化,但应当把他们替换成符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>中。

函数open和openat

#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*/);
										两函数的返回值:若成功,返回文件描述符;若出错,返回-1

我们将最后一个参数写为…,ISO C 用这种方法表示可变参数和类型。对于open而言,仅当创建新文件时,才使用最后这个参数。在函数原型中此参数放置在注释中。

path参数时要打开或创建的文件名,oflag参数可用来说明此函数的多个选项。用下列一个或多个常量进行”或“运算构成oflag参数。

O_RDONLY 只读打开

O_WRONLY 只写打开

O_RDWR 读写打开

O_EXEC 只执行打开

O_SEARCH 只搜索打开(应用于目录)

这5个常量必须指定一个且只能指定一个。下列是可选的。

O_APPEND 每次写时都追加在文件末尾

O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志

O_CREAT 若此文件不存在则创建它。使用此选项时,需说明mode参数,mode指定文件的访问权限

O_DIRECTORY 如果path引用的不是目录,则报错

O_EXCL 如果同时指定了O_CREAT,而文件已存在,则出错。用此可测试文件是否存在

O_NOCTTY 如果path引用的时终端设备,则不将该设备分配为此进程的控制终端

O_NOFOLLOW 如果path引用的是一个符号链接,则出错

O_NONBLOCK 如果path引用的是一个FIFO、一块特殊文件或一个字符特殊文件,则设置I/O操作为非阻塞

O_SYNC 使每次write等待物理I/O操作完成

O_TRUNC 如果此文件存在,而且为只写或读写打开,则将其长度截断为0

O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios参数。

函数create

#include <fcntl.h>
int create(const char *path, mode_t mode);		//返回值:若成功,返回文件描述符;否则返回-1

注意,此函数等效于: open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);create的不足之处在于它以只写方式打开创建的文件,如果文件需要读的,则不方便,一般不使用。

函数close

#include <fcntl.h>
int close(int fd);		//返回值:若成功,返回0;否则返回-1

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件。利用这一功能而不显示地调用close关闭打开文件。

函数lseek

每个打开文件都有一个与其相关联的”当前文件偏移量“。它是一个非负整数,用以度量文件开始处计算的字节数。通常读写操作都是从当前文件偏移量开始的,并使偏移量增加所读写的字节数。按系统默认,打开一个文件时,除指定O_APPEND,否则该偏移量被设置为0.

#include <fcntl.h>
off_t lseek(int fd, off_t offset, int whence);		//返回值:若成功,返回文件偏移量;否则返回-1

对参数offset的解释和参数whence的值相关。

  • 若whence时SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节
  • 若whence时SEEK_CUR,则将该文件的偏移量设置为当前值加offset,offset可正可负
  • 若whence时SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负

通常,文件当前偏移量是一个非负整数,但是,某些设备允许负的偏移量。所以测试时,不要测试它是否小于0,而要测试它是否等于-1。

函数read

调用函数read从打开文件中读数据

#include <unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);		//返回值:读到的字节数,若已到文件末尾,返回0;如出错,返回-1

有多种情况可使实际读到的字节数少于要求读的字节数:

  • 读普通文件时,在读到要求字节数前已到文件按尾端。
  • 当从终端设备读时,通常一次最多读一行
  • 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
  • 当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际的字节数
  • 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
  • 当一信号中断,而已经读了部分数据,读操作从文件的当前偏移量开始,在成功返回前,该偏移量将增加实际读到的字节数。

函数write

调用函数write从打开文件中写数据

#include <unistd.h>
ssize_t write(int fd, void* buf, size_t nbytes);		//返回值:返回已写字节数;如出错,返回-1

其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见原因是磁盘写满,或者超过一个给定进程文件长度限制。

I/O的效率

大多数文件系统为改善性能都采用某种预读(read ahead) 技术。当检测正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假想应用很快会读这些数据。预读的效果,缓冲区长度小至32字节的时钟时间与较大缓冲区长度的时钟时间几乎一样。

原子操作

1. 追加到一个文件

if (lseek(fd, OL, 2) < 0)	// positon to EOF
	err_sys("lseek error");
if (write(fd, buf, 100) != 100)		//write
	err_sys("write error");

对于多个进程同时使用上述方法将数据追加到同一文件,则会产生问题。假定有两个独立的进程A和进程B都对同一文件进行追加写操作。每个进程都已打开了该文件,但未使用O_APPEND标志。每个进程都有它自己的文件表项,但共享一个v节点表项。假定A调用了lseek,它将进程A的当前偏移量设置为1500(当前文件尾端处)。然后内核切换进程B,进程B也将对该文件的当前偏移量设置为1500。然后B调用write,它将B的该文件当前偏移量增加至1600。然后内核又切换,使进程A运行。当A调用write使,就从其1500处开始将数据写入文件。这样也就覆盖了进程B刚才写入该文件的数据。

Unix系统为这样的操作提供了一种原子操作方法,即在打开文件时设置O_APPEND标志。

2. 函数pread和pwrite

原子性地定位并执行I/O。pread和pwrite就是这种扩展

#include <unistd.h>
ssize_t pread(int fd, void* buf, size_t nbytes, off_t offset);		//返回值:读到的字节数,若已到文件末尾,返回0;如出错,返回-1
ssize_t pwrite(int fd, void* buf, size_t nbytes, off_t offset);		//返回值:返回已写字节数;如出错,返回-1

调用pread相当于调用lseek后调用read,但是pread又与这种顺序调用有下列重要区别。

  • 调用pread时,无法中断其定位和读操作。
  • 不更新当前文件偏移量

3. 创建一个文件

读open函数的O_CREAT和O_EXCL选项进行说明时,我们就见到有关原子操作的例子。

函数dup和dup2

下面两个函数都可以用来复制一个现有的文件描述符。

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);

fcntl函数

fcntl函数可以改变已经打开文件的性质

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

int fcntl(int filedes, int cmd,.../*int arg*/);
												返回:若成功则依赖于cmd(见下),若出错-1

fcntl函数有5中功能:

  • 赋值一个现存的描述符(cmd=F_DUPFD)

  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)

  • 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)

  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

  • 获得/设置记录所(cmd=F_GETLK,F_SETLK或F_SETLKW)

对于fcntl的文件状态标志
文件状态标志说明
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读/写打开
O_APPEND写时添加至文件尾
O_NONBLOCK非阻塞方式
O_SYNC等待写完成
O_ASYNC异步I/O

下面显示了从KornShell调用该程序时的几种情况:

$ a.out 0 < /dev/tty
read only
$ a.out 1 > temp.foo
write only
$ a.out 2 >> temp.foo
write only, append
$ a.out 5 5<> temp.foo
read write

函数sync、fsync和fdatasync

通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。为了保证磁盘实际文件系统与缓冲区内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
														返回值:若成功,返回0;出错,返回-1
void sync(void);

ioctl函数

#include <unistd.h>  //SVR4
#include <sys/ioctl.h>	//4.3+BSD

int ioctl(int filedes, int req,...);
										返回:若出错则为-1;成功为其他值

目前,ioctl的主要用途分类展示于下表中

类型常数名头文件ioctl数
盘标号DIOxxx<disklabel.h>10
文件I/OFIOxxx<ioctl.h>7
磁带I/OMTIOxxx<mtio.h>4
套接口I/OSIOxxx<ioctl.h>25
终端I/OTIOxxx<ioctl.h>35
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值