linux高级文件I/O操作

linux高级文件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之后开始就没有使用了(被忽略了)

返回错误解析
EINVALsize 不是正数
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值解析
EBADFepfd不是一个有效epoll实例,或者fd不是有效文件描述符
EEXISTop为EPOLL_CTL_ADD,但是fd已经与epfd关联
EINVALepfd不是一个epoll实例,epfd和fd相同,或者op无效
ENOENTop是EPOLL_CTL_MOD,或者EPOLL_CTL_DEL,但是fd没有与epfd关联
ENOMEN没有足够内存完成进程请求
EPERMfd不支持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值解析
EBADFepfd是无效文件描述符
EFAULT进程对events指向的内存无写权限
EINTR系统调用在完成前被信号中断
EINVALepfd不是有效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参数必须按照页大小对齐。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVzJJQqb-1631779048485)(C:\Users\lingmin\AppData\Roaming\Typora\typora-user-images\image-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_MAXhostname最大长度,需小于_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_VERSIONPOSIX版本

获取系统页大小:

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给定文件描述符无效
EINVALaddr,len,off中的一个或者多个无效
ENFILE打开文件数达到系统上限
ENODEV文件所在的文件系统不支持存储映射
ENOMEM没有足够的内存
EOVERFLOWaddr+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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值