现在的位置: 首页 > 综合 > 正文

APUE 第三章 文件I/O 文件的打开、读写、设置等

2013年10月02日 ⁄ 综合 ⁄ 共 3722字 ⁄ 字号 评论关闭

本章介绍了UNIX系统中文件操作的函数,主要包括open、read、write、lseek、close等。这些函数被称为unbuffered I/O,unbuffered意味着每次调用read、write都会调用一个系统调用,这些函数不是ISO C的组成部分,但是是POSIX的组成部分。

本章的所有内容基本可以用这样一个图来描述,理解了这个图,基本就理解了本章的内容。

clip_image001

从上图可以看出在整个关系链中,出现了进程、文件描述符(fd)、文件表、进程表、v-node表等。

系统维护了一张进程表,其中的每一项称为进程表项,即上图最左边的是表示进程表中的一个条目,一个进程表条目可以理解为是一个数组,包括了该进程打开的所有文件的描述。在对文件的描述中包括了某个文件描述符的flag(主要是close-on-exec标记)和指向文件表项的指针。(即fd flags和file pointer)

如果进程重新打开一个文件,那么会增加一条fd和file pointer组成的条目。

文件指针指向的是文件表项,文件表项中包含看对文件属性的描述(例如以读的方式打开、写的方式打开、读写方式打开、append方式、nonblock方式等),同时标记了文件当前的偏移量和执行v-node的指针

v-node表项可以理解为是文件的信息吧(其实不一定是),包括了文件的信息,例如文件大小等等

从上面可以知道,这是一个三级结构,而我们主要关注的有文件描述符fd的flag、文件的属性等,这两个是完全不同的概念,fd flags是对文件描述符性质的描述,主要说明该文件描述符的性质,目前主要是由close-on-exec属性组成的。

而文件的属性主要是文件操作的一些性质,例如以读的方式打开、写的方式打开、读写方式打开、append方式、nonblock方式等,可见他不同于文件描述符的属性

关于什么是close-on-exec,我个人的理解是这样的:当调用fork后,子进程会很父进程拥有同样的文件描述符,如下图所示

clip_image002

此后子进程可能会调用exec函数执行其他的程序,而close-on-exec属性就在这里有所显示了,如果某个文件描述符fd设置了close-on-exec属性的话,那么子进程中的这个文件描述符fd在执行exec函数时就会被关闭。!!!例如如果fd0设置了close-on-exec属性,那么在执行exec函数族后,子进程中的fd0会被关闭。下面是一种解释:

clip_image003

有了上面对close-on-exec属性的了解,下面可以介绍fcntl函数了,fcntl函数主要是用于设置文件描述符fd的属性、文件的属性(读方式打开、写方式打开等)、dup(复制)已经存在的文件描述符等其他功能

fcntl函数可以设置文件描述符fd是否是close-on-exec的;可以设置文件的属性是读方式打开的、写方式打开的还是其他等等。

综上所述,文件描述符fd的属性和文件的属性是完全不同的概念,文件描述符的属性目前主要有close-on-exec属性;而文件的属性有O_RDONY、O_WRONLY、O_RDWR、O_APPEND、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC、O_ASYNC等

好,下面从头分析文件的描述符。

  • 在linux中,系统为打开的文件用一个非负整数来标识文件。这个非负整数就是文件描述符,在一般文件的操作过程中,他是通过int open(const char* pathname, int flag, ...)的返回值来获得的。其中flag的值就是用来指明文件打开时的属性的,主要有O_RDONY、O_WRONLY、O_RDWR、O_APPEND、O_NONBLOCK、O_SYNC、O_CREAT、O_EXCL、O_TRUNC等,其中O_RDONY、O_WRONLY、O_RDWR这三个只能选一个,他们是互斥的关系,而后面的可以与这三个中的一个进行共存。O_APPEND表示打开文件后在文件尾进行操作。O_CREAT表示如果要打开的文件不存在,则新建一个同名的文件。
  • 打开文件后,就可以对文件进行读写、seek等操作了,在操作结束后,调用close函数将文件描述符关闭,调用close过程是不必须的,因为在进程结束的时候会自动将其自己打开的文件全部关闭,但直接调用close是一个很好的习惯。
  • 可以利用lseek函数对文件的offset进行移动,这是一个非常有用途的操作,例如在你操作一个文件时想在某个指定位置对文件进行操作,那么就可以利用off_t lseek(int fd, off_t offset, int whence)函数来对文件偏移量进行操作。具体的offset是根据whence的不同有不同的含义:

1、当whence=SEEK_SET的时候,表示偏移量是相当于文件开始算起的

2、当whence=SEEK_CUR的时候,表示偏移量是相当于现在的偏移量算起的

3、当whence=SEEK_END的时候,表示偏移量是相当于文件末尾算起的

  • 在打开了文件后就可以利用read函数对文件进行读操作了

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

如果read读取成功,那么实际读取的字节数会被返回,如果是读到文件结尾了,会返回0;如果是在读的过程中发生了错误,返回-1

有一些情况下read实际读取的字节数是比请求的nbytes字节数要小的,具体有这些情况:

1、例如在读取一个文件时,每次要读取100个字节(即nbytes=100),但文件的大小不是100字节的整数倍,比如文件是230个字节,那么前两次每次都是读取100个字节,到第三次的时候银行文件剩余不到100个字节,那么实际读取的是30个字节。在第四次读取的时候,发现已经到文件结尾了,则返回0

2、当从终端设备读的时候

3、当从网络接口读取的时候,例如socket

4、当从pipe或者FIFO读取的时候

5、当从一个以record为条目的文件中读取的时候

6、当已经读了部分数据,但被信号中断的时候

等等

因此当利用read读取数据的时候,不能简单通过判断实际读取的字节数和请求的字节数是否相同来判断,下面简要给出一个read的封装版本:

int my_read(int fd, void *buf, int nbytes)

{

int cur_bytes = 0;//标记实际读取了多少字节数

while(cur_bytes < nbytes)

{

int ret_bytes = read(fd, buf+cur_bytes, nbytes - cur_bytes);

//标记实际读取的字节数

if(ret_bytes < 0 && EINTR == errno) //EINTR表示在未进行读取任何数据前被信号中断了

clip_image004

{

continue;

}

else

if(ret_bytes < 0)

{

err_msg("read error.");//读取错误

return -1;

}

else

if(ret_bytes == 0)

{

break; //读取完毕

}

else

{

cur_bytes += ret_bytes;

}

}

return cur_bytes;

}

  • 在打开文件后,就开对文件进行写操作了,写操作主要是利用write函数来实现的。

ssize_t write(int fd, const void *buf, size_t nbytes);//-1 on error;返回实际写的字节数

如果在打开文件的时候设置了O_APPEND属性的话,那么在执行每次写操作的时候,文件的offset都是指向文件的尾部的。即使是多进程同时写的时候,例如有的进程先写,而其他进程后写,那么也能保证其他进程是将数据写到文件的末尾了,例如一些log的写操作,需要加上O_APPEND属性。这个属性可以保证原子操作

当然多个进行同时读取同一个文件是没有问题的,因为大家每个进程都有自己的文件偏移量,在读的过程中不会发生问题。

下面给出一个write的实现:

int my_write(int fd, const void *buf, int nbytes)

{

int cur_bytes = 0;

while(cur_bytes < nbytes)

{

int ret_bytes = write(fd, buf+cur_bytes, nbytes - ret_bytes);

if(ret_bytes <= 0 && EINTR == errno) //EINTR 表示在没有进行写任何数据前被信号中断了

clip_image005

{

continue;

}

else

if(ret_bytes <= 0)

{

err_msg("write error.");

return -1;

}

else

{

cur_bytes += ret_bytes;

}

}

return cur_bytes;

}

  • dup、dup2函数

dup函数是用来复制一个文件描述符的,将两个文件描述符指向同一个文件表项,进行完这个操作后,两个文件描述符指向了同一个文件表项,则他们的文件属性是相同的,例如文件是以可读、可写等方式打开,但需要注意的是两个文件描述符的属性可能是不同的,即两者的close-on-exec属性是不一定相同的。dup函数操作后,新的文件描述符的close-on-exec标志经常会被清除掉

clip_image006

抱歉!评论已关闭.