ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux 文件I/O

2022-02-26 16:04:11  阅读:220  来源: 互联网

标签:文件 int ret 描述符 fd linux include


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

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

内核为每一个进程维护一个打开文件的列表,该表称为文件表 -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 结构表示。

 

 

标签:文件,int,ret,描述符,fd,linux,include
来源: https://www.cnblogs.com/yingziliu-loki/p/15938295.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有