unix环境高级编程-文件IO
文件IO:
大多数文件IO只需要用到5个函数:open,read,write,lseek和close
这里描述的函数经常被称为不带缓冲的IO(unbuffered IO),不带缓冲的意思是每个read和write都调用内核中的一个系统调用,这些不带缓冲的IO函数不是ISO C的组成部分,但是却是POSIX的组成。
只要涉及在多个进程之间共享资源,那么原子操作的概念就变得非常重要。
文件描述符:
对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传递给read或write。
一般而言,Unix将文件描述符0余进程的标准输入相关联,文件描述符1与标准输出关联,文件描述符2与标准错误相关联。这三个描述符通常替换成符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量在头文件unistd.h定义。
文件描述符的变化范围是0-OPEN_MAX-1,也即是每个进程最多允许打开文件数-1。
open函数:调用该函数可以创建或打开一个文件
#include<fcntl.h>
int open(const char *pathname, int oflag, ......);
pathname 是要打开或者创建文件的名字,oflag可用来说明函数的多个选项,用下列一个或多个常量进行或运算构成oflag参数,具体oflag参数有如下:
O_RDONLY 只读 0
O_WRONLY 只写 1
O_RDWR 读写 2
O_EXEC 只执行
O_SEAECH 只搜索(应用于目录)
以上5个参数必须选择一个
下列常量是可选择的:
由open返回的文件描述符一定是最小的未用描述符数值,可以被某些应用程序用来标准输入、输出上打开新的文件
creat函数:可调用creat函数创建一个新文件:
#include<fcntl.h>
int creat(const char *pathname , mode_t mode); 返回值:如果成功返回只写打开的文件描述符,若出错则返回-1
此函数等效于:open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
creat函数一个不足之处是它以只写方式打开所创建的文件,如果要创建一个文件,必须先写,然后又读, 也就是先调用creat,close,然后调用open。现在可以用以下方式:
open(pathname, O_RDWR | O_CREAT | O_TRUNC , MODE);
close 函数:关闭一个打开文件
当一个进程终止时,内核自动关闭打开的所有文件,很多程序都利用这一功能而不显示的调用close关闭打开文件。
lseek函数:为一个打开的函数设置偏移量
每个打开文件都有一个与其想关联的当前文件偏移量,用于度量从文件开始处计算的字节数,通常读写操作都是从当前文件偏移量开始。按照系统默认的情况,当打开一个文件时,除非指定了O_APPEND选项,否则偏移量设置为0。
调用lseek显性为一个打开文件设置偏移量
参数offset和参数whence有关:
若whence是seek_SET,则将该文件的偏移量设置为文件开始处offset字节。
若whence是seek_CUR,则将该文件的偏移量设置为当前值+offset字节,offset可正可负。
若whence是seek_END,则将该文件的偏移量设置为文件长度加offset字节,offset可正可负。
文件偏移量是可以大于文件的当前长度,这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,位于文件中单没有写过的字节都被读为0。
文件的空洞并不要求在磁盘上占有存储区。
read函数:从打开文件中读数据
write函数:向打开文件写数据
文件共享:不同进程间共享打开文件
下图显示了一个进程对应三张表之间的关系,该进程有两个不同的打开文件:一个文件从标准输入打开(文件描述符0),另一个从标准输出打开(文件描述符1)
如果两个独立进程各自打开同一个文件,则有图中所示关系
对于多个进程读取同一文件都能正确工作,每个进程有它自己的文件表项,其中也有它自己的当前文件偏移量,但是多个进程写同一文件时,可能产生预想不到的结果。
原子操作:
考虑一个进程,它要将数据追加到一个文件尾端,程序是这样写的
对单个进程而言是可以运行的,但是对于多个进程,同时使用这种方法,会产生问题。
比如两个进程AB对同一文件进行写操作,每个进程都打开该文件,每个进程有自己的文件表项,但是共享一个v节点表项,当两个进程写操作时,会导致信息不一致而出错。
问题出于逻辑操作:先定位到文件尾端,然后写,它使用了两个分开的函数调用,解决方法是使者两个操作对于其他进程而言成为一个原子操作,任何要求多于一个函数调用的操作都不是原子操作,因为在两个函数调用之间,内核有可能临时挂起进程。
Unix系统为这样的操作提供了一种原子操作的办法,在打开文件时设置O_APPEND标志,这样使得内核在每次写操作之前,都将进程的当前偏移量设置到该文件的尾端处。
比如函数pread和pwrite。
一般而言,原子操作指的是由多步组成的一个操作,如果该操作原子执行,则要么执行完所有操作,要么一步也不执行,不可能只执行所有步骤的一个子集。
posted on 2016-07-26 11:10 sichenzhao 阅读(142) 评论(0) 编辑 收藏 举报