linux 文件I/O

介绍文件读写的基本要素。

在对文件读写操作钱,需要先打开文件,

内核为每一个进程维护一个打开文件的列表,该表称为文件表 -file table。由一些文件描述符(fds)的非负整数进行索引。

文件描述符 int 类型。

每个进程都会打开3个文件描述符:0,1,2,除非进程显式的关闭。

0=标准输入

1=标准输出

2=标准错误

文件描述符可以访问任何可以读写的东西。

 

文件操作:使用 read() 和write() 系统调用。文件被访问之前,必须打开获取文件描述符。

1. 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);

例:只读方式打开 /home/dir1/dir2/name1 文件

  int fd;

  fd=open("/home/dir1/dir2/name1", O_RDONLY);

  if(fd == -1)

    /*error*/

 

2.creat() 函数 创建文件:

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

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

例:

  int fd ;

  fd=creat(file,0644);

  fd=creat(file, O_WRONLY|O_CREAT|O_TRUNC,0644);

  if(fd == -1)

    /*error*/

在大多是Linux架构上 creat 是一个系统调用。

3.read() 读取文件

#include <unistd.h>

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

例:读入unsigned long 个字节

  unsigned long word;

  ssize_t nr;

  nr = read(file ,&word, sizeof(unsigned long));

  if(fd == -1)

    /*error*/

读入所有字节:

  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;

  }

非阻塞读:

  char buf[BUFSIZE];

  ssize_t nr;

  start:

    nr = read(fd, buf, BUFSIZE);

    if(nr == -1) {

      if(errno == EINTR)

        goto start;

      if(errno == EAGAIN)

        /*resubmit later*/

      else

        /*error*/

    }

  }

 

4.write() 写文件

#include<unistd.h>

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

例:

  const char *buf=" hello ,hello, hei";

  ssize_t nr;

  nr = write(fd, buf,  strlen(buf));

  if(nr == -1)

    /*error*/

 

常用的形式:

  unsigned long word=1720;

  size_t count;

  ssize_t nr;

  count =sizeof(word);

  nr = write(fd, &word, count);

  if(nr == -1)

    /*error*/

  else if (nr != count)

    /*do not write all*/

 

部分写:如针对套接字,使用循环保证写入了所有请求的数据

  ssize_t ret, nr;

  while(len != 0; && (ret = write(fd, buf, len)) !=0){

    if(ret == -1){

      if(error == EINTR)

        continue;

      perror("wirte");

      break;

    }

    len -=ret;

    buf+=ret;

  }

 

更新日志类文件:使用追加模式: O_APPEND 写。

 

5.同步I/O: 确认数据写入磁盘

#include<unitstd.h>

  int fsync(int fd);  

  int fdatasync(int fd);

  void sync(void);

例:

  int ret ;

  ret=fsync(fd);  

  if(ret == -1)

    /*error*/

为保证任何对目录项的更新也同步到磁盘上,必须对目录本身也调用fsync() 进行同步。

当fsync 错误时,应该尝试 fdatasync

  if(fsync (fd) == -1){

    if(error == EINVAL){

      if(fdatasync(fd) == -1)

        perror("fdatasync");

    }

    else

      perror("fsync");

  }

 

可以在打开文件 open() 时,增加 O_SYNC 标志,使所有在文件上的I/O操作同步。

  int fd ;

  fd =open(file, O_WRONLY | O_SYNC);

  if(fd == -1){

    perror("open");

    return -1;

  }

Linux 的O_SYNC 比 fsync() 更有效。

O_DSYNC:每次指定操作后,只有普通数据同步,元数据不同步。

O_RSYNC:要求读请求向写那样进行同步。

 

6.直接I/O:Linux内核实现复杂的缓存、缓冲 以及设备和应用之间的I/O管理的层次结构。

  open() 中使用O_DIRECT标志会使内核最小化I/O 管理的影响:忽略页缓存机制,直接对用户空间缓冲和设备进行初始化,所有的I/O都是同步的:操作在完成之前不能返回。

 

7.关闭文件: close() 

#include <unistd.h>

  int close(int fd);

例:

  if(close (fd) == -1)

    perror("close");

想保证文件在关闭之间写道磁盘,需要使用一个同步操作 fsync。

 

8.文件重定位: lseek() 更新文件指针的位置。

#include <sys/type.h>

#include <unistd.h>

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

 

例:更新文件位置为123;

  off_t ret;

  ret = lseek(fd, (off_t)123), SEEK_SET);

  if(ret == (off_t)-1)

    /*error*/

例:更新文件位置到文件末尾。

  off_t ret;

  ret = lseek(fd, 0, SEEK_END);

  if(ret == (off_t)-1)

    /*error*/

更新文件位置之后,一般都需要获取文件的当前位置。

例:

  int pos;

  pos=lseek(fd, 0, SEEK_END);

  if(pos == (off_t)-1)

    /*error*/

  else

    /*pos is current position of fd*/

空洞:对文件系统末尾之后的区域进行填充0的空间,不占有物理磁盘空间。

  稀疏文件

  

9.定位读写: pread, pwite: 调用以需要读写的文件位置作为参数,完成时,不修改文件位置。

#define _XOPEN_SOURCE 500

#include <unistd.h>

  ssize_t pread(int fd, void *buf, size_t count, off_t pos);  //从fd文件的pos位置获取count个字节到buf中

  ssize_t pwrite(int fd, const char *buf, size_t count, off_t pos);  //从文件fd 的pos 位置写count 个字节到buf中。  

  只适用于可以进行定位操作的文件描述符fd。

 

10.截短文件 ftruncate(),truncate(),不修改文件当前的位置。

#include <unistd.h>

#include <sys/types.h>

  int ftruncate(int fd, off_t len);  //将指定文件描述符fd 的文件截短到len长度

  int truncate(const char *path, off_t len);  //将文件路径path指向的文件截短到len 长度。

 

例:testfile.txt的内容

  abcdefghijklmnopqrstwuvxyz

  hello, hai,oh, yes, none.

truncate1.c 文件内容

/*********************************************/

#include <unistd.h>
#include <stdio.h>

#include <stlib.h>

  int main(int ac, char *av[])
  {
    int ret;
    ret = truncate("./testfile.txt",off_t(atoi(av[1])));
    if(ret == -1){
      perror("truncate");
      return -1;
    }
    return 0;
  }

/*********************************************/

./truncate1  19

testfile.txt的内容:abcdefghijklmnopqrs

 

11.I/O多路复用:应用程序常常需要在于一个文件描述符上阻塞。

  非阻塞I/O:应用可以发起请求并返回一个特别的错误。从而避免阻塞。

  I/O多路复用:允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时受到通知。

  I/O多路设计原则:

  1.I/O多路复用:当任何文件描述符准备好I/O时告诉我

  2.在一个或更多文件描述符就绪前始终处于睡眠状态

  3.唤醒:哪个准备好了

  4.在不阻塞情况下处理所有I/O就绪的文件描述符

  5.返回1。

  select(),poll(),epoll()。

select() 调用:实现同步I/O多路复用的机制:

#include <sys/time.h>

#include <sys/typesh>

#include <unistd.h>

 

int select(int n, fs_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()调用就会阻塞。

检测的文件描述符分为3类:

  1.检测readfds集合中的文件描述符,确认其中是否有可读数据。

  2.检测writefds集合中的文件描述符,确认其中是否有可写数据。

  3.检测exceptfds集合中的文件描述符,确实其中述符有出现异常发生或者出现带外数据

  集合为空时,select不对这些数据进行检测。

timeval 结构体

#include <sys/time.h>

  struct timeval{

    long tv_sec;  //秒

    long tv_usec;  //微秒

  };

检测集合中的文件操作符,通过辅助宏来进行管理。在select 之前,需要调用该宏

  fd_set writefds;

  FD_ZERO(&writefds);  //初始化一个宏

  FD_SET(fd, &writefds);  //向指定合集添加一个文件描述符。

  FD_CLR(fd,&writefds);  //从指定合计移除一个文件描述符

  if(FD_ISSET(fd, &writefds) == 1)  //检测文件描述符是否在合集中

 

例:监听标准输入设备5秒(非阻塞),5秒内获取到标准输入时,继续处理。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#define TIMEOUT 5
#define BUF_LEN 1024

int main(int ac, char *av[])
{
  struct timeval tv;
  fd_set readfds;
  int ret;

  FD_ZERO(&readfds);
  FD_SET(STDIN_FILENO,&readfds);
  tv.tv_sec=TIMEOUT ;  //等待5秒
  tv.tv_usec=0;

  ret=select(STDIN_FILENO+1, &readfds, NULL, NULL, &tv);

  if(ret == -1){
    perror("select");
    return 1;
  } else if(!ret){
  printf("%d seconds elapsed.\n",TIMEOUT);
  return 0;
  }

  if(FD_ISSET(STDIN_FILENO, &readfds)){
    char buf[BUF_LEN+1];
    int len;
    len =read(STDIN_FILENO, 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;
}

 

自定义sleep(): 采用微秒级别select 作为睡眠机制

struct timeval tv;

tv.tv_sec=0;

tv.tv_usec=500;

select(0,NULL,NULL,NULL,&tv);

 

pselect():POSIX 自定义。

#define X_OPEN_SOURCE 600

#include <sys/select.h>

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

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

pselect() 和select() 的不同:

1.pselect 的timeout 采用timespec 结构,而不是timeval 结构。更精确。

2.pselect() 调用不修改timeout 参数。

3.select()调用没有 sigmask 参数,当sigmask =0 时,pselect=select.

#include <sys/time.h>

struct timespec{

  long tv_sec;

  long tv_nsec;

}

 

poll():

#include <sys/poll.h>

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

struct pollfd {
  int fd; /* file descriptor */
  short events; /* requested events */
  short revents; /* returned events */
};

每个pollfd结构体监视单一的文件描述符。可以传递多个文件描述符,使得poll 监视多个文件描述符。

  每个结构体的 events 字段是要监视的文件描述符事件的一组位掩码。

  revents字段则是发生在该文件描述符上的事件的位掩码。

例:

#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define TIMEOUT 5
int main(int ac, char *av[])
{
  struct pollfd fds[2];
  int ret;

  fds[0].fd=STDIN_FILENO;
  fds[0].events=POLLIN;

  fds[1].fd=STDOUT_FILENO;
  fds[1].events=POLLOUT;

  ret=poll(fds,2,TIMEOUT*1000);
  if(ret == -1){
    perror("poll");
    return 1;
  }
  if(!ret){
    printf("%d seconds elapsed",TIMEOUT);
    return 0;
  }

  if(fds[0].revents & POLLIN)
    printf("stdin is readable\n");

  if(fds[1].revents & POLLOUT)
    printf("stdout is writeable\n");

  return 0;
}

在一个应用中使用了poll,无需在每次调用时重新构建pollfd结构。相同的结构可能会被反复传递;必要时内核会把revents 字段清空。

ppoll(): Linux 专门的调用

#define _GNU_SOURCE
#include <poll.h>

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

 

poll和select的性能比较:poll 性能较为优越

1.poll() 无需使用计算最大的文件描述符+1和 传递该参数

2.poll() 在应对较大值的文件描述符时更具有效率。select 监视多个文件描述符,内核需要检查每个集合中的每个比特位。

3.select() 的文件描述符集合是竟态的, *poll() 可以创建合适大小的数组,仅需要监视一项或仅仅传递一个结构体。

4.select() 文件描述符集合会在返回时重新创建,这样之后每个调用都必须重新初始化它们。poll() 分离了输入和输出,数组无需改变即可使用

5.select() 的timeout 参数在返回时是未定义的,可移植代码需要重新初始化。poll() 没有这个问题。

 

select() 的优点:

1.select() 的使用范围更广,部分linux 不支持poll()。

2.select() 提供更好的超时方案。

 

12.内核内幕:如何实现I/O的,集中关注三个内核子系统:

1.虚拟文件系统(VFS)。

2.页缓存

3.页回写。

 

虚拟文件系统(VFS):Linux内核的文件操作的抽象机制。内核无需了解文件系统类型的情况下,使用文件系统函数和操作文件系统数据

  通用文件模型:基于函数指针和各种面向对象方法*

 

页缓存:一种在内存中保存最近使用在磁盘文件系统*问过的数据的方式。

  时间局部性,空间局部性。

  内核寻找文件系统数据的第一目的地。

  内核管理预读。

 

页回写:内核使用缓冲区来延迟写操作。

触发回写条件:

1.当空闲内存小于设定的阈值,脏的缓冲区就会回写到磁盘上

2.当脏的缓冲区寿命超过设定的阈值时,缓冲区被回写到磁盘上。

pidflush的内核线程操作。

 

缓冲区在内核中使用buffer_head 结构表示。

 

 
上一篇:给linux新建的用户添加sudo权限


下一篇:linux命令