linux高级文件I/O操作


高级文件I/O

1、散布/聚集I/0

散步/聚集I/O :单个数据流内容->写入多个缓冲区/单个数据量内容读->多个缓冲区

优点:

1.效率 单个向量I/O替代多个线性I/O

2.性能 降低系统调用次数,比线性I/O性能更好

3.原子性 一个线程执行单个向量I/O

头文件:

#include <sys/uio.h>

操作函数:

readv()ssize_t readv(int fd,const struct iovec *iov,int count);

writev()ssize_t writev(int fd,const struct iovec *iov,int count);

2、epoll

event poll(epoll) 相比poll()select()的最大差异监听注册从实际监听中分离出来,解决poll()select()性能问题。

头文件:

#include <sys/epoll.h>

操作函数:

epoll_create()int epoll_create(int size)

功能:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围;这个参数自从Linux 2.6.8之后开始就没有使用了(被忽略了)

返回错误 解析
EINVAL size 不是正数
ENFILE 系统达到打开文件数的上限
ENOMEN 没有足够的内存来完成该次操作

epoll_ctl(): int epoll_ctl(int epfd,int op,int fd,struct epoll_event *evnet)

功能:用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
@epfd:由 epoll_create 生成的epoll专用的文件描述符;
@op:要进行的操作,EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除;
@fd:关联的文件描述符;
@event:指向epoll_event的指针;
成功:0;失败:-1

struct epoll_event
{
    __u32 events;
    union
    {
        void *ptr;
        int fd;
        __u32 u32;
        __u64 u64;
    }data;
};
op参数有效值 解析
EPOLL_CTL_ADD 把fd指定的文件添加到epfd指定的epoll实例监听集中
EPOLL_CTL_DEL 把fd指定的文件从epfd指定的epoll监听集中删除
EPOLL_CTL_MOD 使用event改变在已有fd上的监听行为

epoll event结构体中的events参数列出了给定文件描述符上监听的事件

epoll_events.event 解析
EPOLLERR 文件出错。即使没设置,这个事件也是被监听
EPOLLET 在监听文件上开启边缘触发。(默认行为是水平触发)
EPOLLHUP 文件被挂起。即使没设置,这个事件也是被监听
EPOLLIN 文件未阻塞,可读
EPOLLONESHOT 在一次事件产生并被处理之后,文件不再被监听
EPOLLOUT 文件未阻塞,可写
EPOLLPRI 高优先级的带外数据可读。

epoll_ctl成功后返回0.失败返回-1,并设置了一下errno值

errno值 解析
EBADF epfd不是一个有效epoll实例,或者fd不是有效文件描述符
EEXIST op为EPOLL_CTL_ADD,但是fd已经与epfd关联
EINVAL epfd不是一个epoll实例,epfd和fd相同,或者op无效
ENOENT op是EPOLL_CTL_MOD,或者EPOLL_CTL_DEL,但是fd没有与epfd关联
ENOMEN 没有足够内存完成进程请求
EPERM fd不支持epoll

在epfd实例中加入一个fd指定的监听文件

struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLOUT;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
if(ret)
    perror("epoll_ctl");

修改epfd实例中的fd上的监听事件

struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_MOD,fd,&event);
if(ret)
    perror("epoll_ctl");

从epfd中移除在fd上的一个监听事件

struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
if(ret)
    perror("epoll_ctl");

epoll_wait() : int epoll_wait(int epfd,struct epoll_event *events,int maxevents, int timeout);

功能:该函数用于轮询I/O事件的发生;
@epfd:由epoll_create 生成的epoll专用的文件描述符;
@epoll_event:用于回传代处理事件的数组;
@maxevents:每次能处理的事件数;
@timeout:等待I/O事件发生的超时值,timeout =0 ,没用事件发生,调用也立即返回0,timeout = -1 ,调用一直等待到事件发生;
成功:返回发生的事件数;失败:-1

errno值 解析
EBADF epfd是无效文件描述符
EFAULT 进程对events指向的内存无写权限
EINTR 系统调用在完成前被信号中断
EINVAL epfd不是有效epoll实例,或者maxevents大于EP_MAX_EVENTS或者小于等于0

epoll_wait()例程:

#define MAX_EVENTS 64
struct epoll_event *events;
int nr_events,i,epfd;
events = malloc(sizeof(srtuct epoll_event) * MAX_EVENTS);
if(!events)
{
    perror("malloc");
    return 1;
}
nr_events = epoll_wait(epfd,events, MAX_EVENTS, -1);
if(nr_events < 0)
{
    perror("epoll_wait");
    free("events");
    return 1;
}
for(i = 0;i < nr_events;i++)
{
    printf("event=%ld on fd=%d\n",events[i].events,events.data.fd);
}
free(events);

边沿触发事件与水平触发事件

epoll_ctl()的参数event中的events项配置为EPOLLE,fd上的监听为边沿触发,反之为水平触发。

假设:

1.生产者向管道写入1Kb数据。

2.消费者在管道调用epoll_wait(),等待pipe出现数据,从而可读。

水平监听触发:在步骤2里对epoll_wait()的调用立即返回,以表pipe可读。

边沿监听触发:这个调用直到步骤1发生后才返回,即调用epoll_wait()时管道已经可读,调用仍然会等待直到数据写入,之后返回。

3、内存映射I/O

头文件:

#include <sys/mman.h>

函数:

mmap():void *mmap(void *addr,size_t len,int prot,int flags,int fd, off_t offset);

功能:该函数将fd表示的文件从offset处开始的len个字节数据映射到内存中;
@addr:内核映射文件的最佳地址,仅提示,非强制,大部分为0;
@len:映射到内存的字节数;
@prot:内存区域所请求的访问权限,PROT_READ 页面可读,PROT_WRITE 页面可写,PROT_EXEC 页面可执行;

@flag:指定操作行为;

@offset:映射文件的起始地址;

映射文件要求访问模式和打开文件的访问模式不能冲突

flag参数 解析
MAP_FIXED 告诉mmap()把addr看作强制性要求。如果内核无法映射文件到指定地址,调用失败
MAP_PRIVATE 映射区不共享
MAP_SHARED 和所有其他映射该文件的进程共享映射内存

MAP_SHARED和MAP_PRIVATE必须指定其中一个,但不能同时指定。

例子:

void *p;
p = mmap(0,len,PROT_READ,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
    perror("mmap");
}

mmap()调用操作页。addr和offset参数必须按照页大小对齐。

image-20210811115204060image-20210811115204060.png

系统获取页大小方法:

1.sysconf()

#include <unistd.h>
long sysconf(int name)

POSIX.1 name参数列表

name定义 解析
_SC_ARG_MAX 参数长度最大限制
_SC_CHILD_MAX 每个user可同时运行的最大进程数
_SC_HOST_NAME_MAX hostname最大长度,需小于_POSIX_HOST_NAME_MAX (255)
_SC_LOGIN_NAME_MAX 登录名的最大长度(字节数)
_SC_NGROUPS_MAX 进程同时拥有的组IDs的最大个数
_SC_CLK_TCK 每秒对应的时钟tick数
_SC_OPEN_MAX 一个进程可同时打开的文件最大数
_SC_PAGESIZE 一个page的大小,单位byte
_SC_PAGE_SIZE 一个page的大小,单位byte
_SC_RE_DUP_MAX 当使用间隔表示法{m,n}时,regexec和regcomp函数允许的基本正则表达式的重复出现次数
_SC_STREAM_MAX 一个进程能同时打开的标准I/O流数
_SC_SYMLOOP_MAX 在解析路径名时可遍历的符号链接数
_SC_TTY_NAME_MAX 终端设备名长度,包括终止字符null
_SC_TZNAME_MAX 时区名字节数
_SC_VERSION POSIX版本

获取系统页大小:

long page_size =  sysconf(_SC_PAGESIZE);

2.getpagesize()

头文件:

#include <unistd.h>

函数:

int getpagesize(void);

问题:并不是所有unix系统都支持这个函数。

3.<asm/pages.h>中宏PAGE_SIZE定义

编译的时候获得页大小。

mmap()返回值和错误码

成功:返回映射区地址。

失败:返回MAP_FAILED。

mmap错误代码 解析
EACESS 给定文件描述符不是普通文件,或者打开模式和prot或者flags冲突
EAGAIN 文件已被文件锁锁定
EBADF 给定文件描述符无效
EINVAL addr,len,off中的一个或者多个无效
ENFILE 打开文件数达到系统上限
ENODEV 文件所在的文件系统不支持存储映射
ENOMEM 没有足够的内存
EOVERFLOW addr+len的结果超过了地址空间大小
EPERM 设定了PROT_EXEC,但是文件系统以不可执行方式挂载

与映射区域相关的信号:

信号 解析
SIGBUS 当进程试图访问一块已经无效的映射区域时,产生该信号。比如,文件在映射后被截短
SIGSEGV 当进程试图写一块只读的映射区域时,产生该信号

munmap() 取消mmap()映射

头文件:

#include <sys/mman.h>

函数:

int munmap(void *addr,size_t len);

成功返回0;

失败返回-1;

实例:

if(munmap(addr,len) == -1)  perror("munmap");

存储映射例子:

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <sys/mman.h>int main(int argc,char *argv[]){ struct stat sb; off_t len;  char *p;    int fd; if(argc<2)  {       fprintf(stderr,"usage: %s  <file>\n",argv[0]);      return 1;   }   fd = open(argv[1],O_RDONLY);    if(fd == -1)    {       perror("open");     return 1;   }   if(fstat(fd,&sb) == -1) {       perror("fstat");        return 1;   }   if(!S_ISREG(sb.st_mode))    {       fprintf(stderr,"%s is not a file\n",argv[1]);       return 1;   }   p = mmap(0,sb.st_size,PROT_READ,MAP_SHARED,fd,0);   if(p == MAP_FAILED) {       perror("mmap");     return 1;   }   if(close(fd) == -1) {       perror("close");        return 1;   }   for(len == 0;len<sb.st_size;len++)  {       putchar(p[len]);    }   if(munmap(p,sb.st_size) == -1)  {       perror("munmap");       return 1;   }   return 0;}

mmap()优点:

1.使用read()或write()系统调用需要从用户缓冲区进行数据读写,而使用映射文件进行操作,可以避免多余的数据拷贝。

2.除了潜在的页错误,读写映射文件不会带来系统调用和上下文切换的开销。

3.当多个进程映射同一对象到内存中,数据在进程间共享。

4.在映射对象中搜索只需一般的指针操作。不必使用lseek();

mmap()缺点:

1.映射区域大小通常是页大小的整数倍。映射文件大小与页大小的整数倍之间有空间浪费。

2.存储映射区域必须在进程地址空间内。

3.创建和维护映射以及相关的内核数据结构有一定开销。

调整映射的大小

头文件:

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>

函数:

void * mremap(void *addr,size_t old_size,size_t new_size,unsigned long flags);

4、文件I/O提示

5、异步I/O

声明:atom|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - linux高级文件I/O操作


道生一,一生二,二生三,三生万物