当前位置: 代码迷 >> 综合 >> Android人生整理第一章:Linux系统编程第二节-文件I/O
  详细解决方案

Android人生整理第一章:Linux系统编程第二节-文件I/O

热度:49   发布时间:2024-02-23 15:11:49.0

在对文件进行读写操作之前,首先需要打开文件,内核会为每个进程维护一个打开文件的列表。该列表称为文件表(file table)。

文件描述符使用C语音的int类型表示。文件描述符的范围从0开始,到上限值减1。默认情况下,上限值为1024,但是可以对它进行配置,最大为1048576。因为负数不是合法的文件描述符,所以当函数出错不能返回有效的文件描述符时,通常会返回-1。

按照惯例,每个进程都至少 包含三个文件描述符:0,1和2。除非显式关闭这些描述符。

文件描述符0表示标准输入(sdtin),1表示标准输出(stdout),2表示标准错误(stderr)。

打开文件:最基本文件访问方法是系统调用read()和write()。但是,在访问文件之前,必须先通过open()或creat()打开该文件。一旦完成文件读写,还应该调用系统调用close()关闭该文件。

 

通过系统调用open(),可以打开文件并获取其文件描述符:

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

int open(const char *name,int flags);

int open(const char *name,int flags,mode_t mode);

open()的flags参数:支持三种访问模式:o_rdonly,o_wronly,o_rdwr  只读,只写,读写模式。

 

新建文件的所有者:文件所有者的uid即创建该文件的进程的有效uid。

通过read()读文件

#include <unistd.h>

ssize_t  read(int fd,void *buf,sise_t len);

每次调用read()函数,会从fd指向的文件的当前偏移开始读取len字节到buf所指向的内存中。执行成功时,返回写入buf中的字节数;出错时,返回-1,并设置errno值。fd的文件位置指针会向前移动,移动的长度由读取到的字节数决定。如果fd所指向的对象不支持seek操作(比如字符设备文件),则读操作总是从“当前”位置开始。

实际上,调用read()有很多kennel结果:

  • 调用返回值等于len,读取到的所有len个字节都被存储在buf中,结果和预期的一致
  • 调用返回值小雨len,大于0。读取到的字节被存储在buf中。这种情况有很多原因,比如在读取过程中信号中断或在读取中出错,可读的数据大于0字节小于len字节,在读取len字节之前到达EOF。再次执行read(分别更新了buf和len值)会把生育的字节读取缓冲区中或者给出错误信息。
  • 调用返回0,表示EOF,没有更多可读的数据。
  • 由于当前没有数据可用,调用阻塞。在非阻塞模式下,不会放生这种情况。
  • 调用返回-1,并把errno设置成EINTR。这表示在读取任何字节之前接收到信号。调用可以重新执行。
  • 调用返回-1,并把errno设置成EAGAIN。这表示由于当前没有数据可用,读操作会阻塞,请求应该稍后再重新执行。这种情况只在非阻塞模式下发生。
  • 调用返回-1,并把errno设置成非EINTR和EAGAIN的一个值。这表示更严重的错误。重新执行读操作不会成功。
ssize_t ret;
while(len!=0&&(ret=read(fd,buf,len))!=0){if(ret==-1){if(errno==EINTR)continue;perror("read");break;}len-=ret;buf+=ret;
}

这段代码处理了五种情况。

非阻塞I/O:读

char buf[BUFSIZ];
ssize_t nr;
start:
nr=read(fd,buf,BUFSIZ);
if(nr==-1){if(errno==EINTR)goto start;/* oh shush*/if(errno==EAFAIN)/* resubmit later */else/* error */
}

调用write()写:

#include <unistd.h>

ssize_t write(int fd,const void *buf,size_t count);

Append追加模式 参数设置O_APPEND打开文件描述符时,写操作不是从文件描述符的当前位置开始,而是从当前文件的末尾开始。

非阻塞写 参数设置O_NONBLOCK打开文件

write()大小限制:

如果count值大于SSIZE_MAX,调用write()的结果是未定文件。

调用write(),如果count值为零,会立即返回,且返回值为0。

 

同步I/O Linux内核提供了一些选择,可以牺牲性能换来同步操作。

fsync() 和fdatasync()

为确保数据写入磁盘,最简单的方式是使用系统调用fsync(),在POSIX.1b标准中定义:

#include <unistd.h>

int fsync(int fd);

fsync() 无法知道数据是否已经真正在物理磁盘上了。

#include <unistd.h>

int fdatasync(int fd);

fsync() 和fdatasync()功能类似,区别在于fdatasync()只会写入数据以及以后要访问文件所需要的元数据。

fsync()通常会涉及至少两个I/O操作:一是回写修改的数据,二是更新索引节点的修改时间戳。因为索引节点和文件数据在磁盘上可能不是紧挨着——因而会带来代价很高的seek操作——在很多场景下,关注正确的食物顺序,但不包括那些对于以后访问文件无关紧要的无数据(比如修改时间戳),使用fdatasync()是提高性能的简单方式。

 

sync()系统调用用来对磁盘上的所有缓冲区进行同步,虽然它效率不高,但还是被广泛应用:

#include<unistd.h>

void sync(void);

 

系统调用open()可以使用O_SYNC标志位,表示该文件的所有I/O操作都需要同步:

int fd;

fd=open(file,O_WRONLY|O_SYNC);

if(fd==-1){

          peeror("open");

         return -1;
}

关闭文件:close()

#include<unistd.h>

int close(int fd);

 

一般情况下,I/O是线性的,由于读写引发的隐式文件位置更新都需要seek操作。

但是,某些应用要跳跃式读取文件,需要随机访问而不是线性访问。lseek()系统调用能够将文件描述符的位置指针设置成指定值。lseek()只更新文件位置,没有执行其他操作。也并不初始化任何I/O:

#include <sys/types.h>

#include <unistd.h>

off_t lseek(int fd,off_t pos,int origin);

 

定位读写

Linux提供了两种read()和write()系统调用的变体来替代lseek(),每次读写操作时,都把文件位置作为参数,在完成时,不会更新文件位置。

read()的变体时pread():

#define _XOPEN_SOURCE 500

#include <unistd.h>

ssize_t pread(int fd,void *buf,size_t cunt,off_t pos);

该调用从文件描述符fd的pos位置开始读取,共读取count个字节到buf中。

write()的变体是pwrite():

#define _XOPEN_SOURCE 500

#include <unistd.h>

ssize_t pwrite(int fd,const void *buf,size_t count,off_t pos);

定位读写只适用于可查找的文件描述符,包括普通文件。pread()和pwrite()调用的语义相当于在read()和write()调用之前执行lseek()调用,但仍然存在以下三点区别。

1.pread()和pwrite()调用更易于使用,尤其是对于一些复杂的操作,比如在文件中反向或随机查找定位。

2.pread()和pwrite()调用在结束时不会修改文件位置指针。

3.最重要的一点,pread()和pwrite()调用避免了在使用lseek()时会出现的竞争。

 

文件截短

Linux提供了两个系统调用支持文件长度缩短,各个POSIX标准都(不同成都地)定义了它们,分别是:

#include <unistd.h>

#include <sys/types.h>

int ftruncate(int fd,off_t len);

和:

#include <unistd.h>

#include <sys/types.h>

int truncate(const char *path,off_t len);

 

Linux提供了三种I/O多路复用方案:select,poll和epoll。

select()系统调用提供了一种实现同步I/O多路复用的机制:

#include <sys/select.h>

int select (int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

FD_CLR(int fd,fd_set *set); 

FD_ISSET(int fd,fd_set *set);
FD_SET(int fd,fd_set *set);
FD_ZERO(fd_set *set);

在给定的文件描述符I/O就绪之前并且还没有超出指定的时间限制,select()调用就会阻塞。

第一个参数n,其值等于所有集合中文件描述符的最大值加1。因此,select()调用负责检查那个文件描述符值最大,将该最大值加1后传给第一个参数。

参数timeout是指向timeval结构体的指针,定义如下:

#include <sys/time.h>

struct timeval{

          long tv_sec;      /*seconds*/

          long tv_usec;     /*microseconds*/

}

select()示例

#include <stdio.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>#define TIMEOUT 5    /* select timeout in seconds */#define BUF_LEN 1024  /* read buffer in bytes */int main(void){struct timeval tv;fd_set readfds;inr ret;/* Wait on stdin for input. */FD_ZERO(&readfds);FD_SET(STDIN_FILENO,&readfds) ;/* Wait up to five seconds. */tv.tv_sec=TIMEOUT;tv.tv_usec=0;/* All right,now block! */ret=select(STDIN_FILENO+1,&readfds,NULL,NULL,&tv);if(ret==-1){perror("select");return 1;}else if (!ret){printf("%d seconds slapsed.\n",TIMEOUT);return 0;}/** Is our file descriptor ready to read?* (It must be,as it was only fd that* we provided and the call returned* nonzero,but we will humor ourselves.)*/if(FD_ISSET(STDIN_FILEND,&readfds)){char buf[BUF_LEN+1];int len;/* guaranteed to not black */len = read (STDIN_FIENO,buf,BUF_LEN);if(len==-1){perror("read");return 1;}if(len){buf[len]='\0';printf("read:%s\n",buf);}return 0;}fprintf(stderr,"This should not happen!\n");return 1;}

pselect()

select()系统调用很流行,它最初是在4.2BSD中引入的,但是POSIX标准在POSIX1003.g-2000和后来的POSIX 1003.1-2001中定义了自己的pselect()方法。

#define _XOPEN_SOURCE 600

#include <sys/select.h>

int pselect(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const struct timespec *timeout,const sigset_t *sigmask);

/* these are the same as those used by select() */

FD_CLR(int fd,fd_set *set); 

FD_ISSET(int fd,fd_set *set);
FD_SET(int fd,fd_set *s et);
FD_ZERO(fd_set *set);

pseelect()和select()存在三点区别:

1.pselect()的timeout参数使用了timespec结构体,而不是timeval结构体。timesepec结构体使用秒和纳秒,而不是秒和毫秒,从理论上讲更精确写。但实际上,这两个结构体在毫秒精度上已经不可靠了。

2.pselect()调用不会修改timeout参数。因此,在后续调用中,不需要重新初始化该参数。

3.select()系统调用没有sigmask参数。当这个参数设置为NULL时,pselect()的行为和select()相同。

 

poll()系统调用是System V的I/O多路复用解决方案。

#include

int poll(struct pollfd *fds,nfds_t nfds,int timeout);

ppoll()

类似于pselect()和select(),Linux也为poll()提供了ppoll()。然而,和pselect()不同,ppoll()是Linux特有的调用:

#define _GNU_SOURCE

#include <poll,h>

int ppoll(struct poolfd *fds,nfds_t nfds,const struct timespec *timeout,const sigset_t *sigmask);

 

内核的三个主要的子系统:虚拟文件系统(VFS),页缓存(page cache)和页回写(page writeback)。

 

  相关解决方案