UNIX io¶
1 dup和dup2¶
- 功能:复制文件描述符
- 语法:
int dup (int __fd)
:复制fd文件描述符信息,并返回一个新的文件描述符(当前最小可用文件描述符号)int dup2 (int __fd, int __fd2)
:复制fd描述符到fd2,如果fd2已打开就先关闭再复制- 区别:dup2可以指定被复制的描述符号,而dup由系统分配
- 示例:dup2
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
int main()
{
int fd_new=100;
int fd=open("file1",O_RDWR);
printf("fd=%d\n",fd);
printf("ret=%d",dup2(fd,fd_new));
printf("fd_new=%d\n",fd_new);
write(fd_new,"hello",5);
close(fd_new);
}
//输出:
//fd=3
//ret=100fd_new=100
- 示例:dup
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
int main()
{
int fd=open("file1",O_RDWR);
printf("fd=%d\n",fd);
int fd_new=dup(fd);
printf("fd_new=%d\n",fd_new);
write(fd_new,"hello",5);
}
//输出:
//fd=3
//fd_new=4
2 fcntl¶
- 功能:用来操作文件描述符的一些特性(比如改变open时设置的flag信息)
- 语法:
int fcntl (int __fd, int __cmd, ...);
fd
:文件描述符cmd
:文件描述符要操作的指令F_DUPFD
:用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词F_GETFD
:获取close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭F_SETFD
:设置close-on-exec旗标。该旗标以参数arg 的FD_CLOEXEC位决定F_GETFL
:取得文件描述词状态旗标,此旗标为open()的参数flagsF_SETFL
:设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响F_GETLK
:取得文件锁定的状态F_SETLK
:设置文件锁定的状态(非阻塞)。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1F_SETLKW
:设置文件锁定的状态(阻塞)。与 F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止
可选参数
:根据不同的参数cmd
决定
2.1 设置文件状态¶
- 示例:覆盖写改为追加写
#include<fcntl.h>
#include<unistd.h>
int main()
{
char buf[1024]={0};
int fd=open("file1",O_RDWR);
if(fd<0)
return 0;
int sz=0;
unsigned int flag=fcntl(fd,F_GETFL);
//给文件增加追加模式,取代open时设置的覆盖写模式
flag=fcntl(fd,F_SETFL,flag|O_APPEND);
sz=write(fd," world",6);
close(fd);
}
2.2 扩展:fd的close on exec¶
- 参考资料
- 功能:当父进程fork子进程时,使子进程不继承父进程中的文件描述符(默认继承),可以在
open
中设置也可以通过fcntl
设置 - 示例
#include<fcntl.h>
#include<unistd.h>
int main()
{
char buf[1024]={0};
//在open时设置FD_CLOEXEC
int fd=open("file1",O_RDWR|FD_CLOEXEC);
if(fd<0)
return 0;
int sz=0;
unsigned int flag=fcntl(fd,F_GETFD);
//通过fcntl设置FD_CLOEXEC
flag=fcntl(fd,F_SETFD,FD_CLOEXEC);
sz=write(fd," world",6);
close(fd);
}
3 ioctl¶
4 io复用模式¶
select
有描述符限制,目前最多有FD_SETSIZE=1024上限;pool
则没有此限制select
和poll
采用轮询方式,效率低;epoll
采用事件通知机制
4.1 select¶
- 功能:用来等待文件描述词状态的改变
- 语法:
int select (int nfds, fd_set *readfds,fd_set *writefds,fd_set *exceptfds,timeval *timeout);
nfd
:是readfds
和writefds
文件描述符集合中最大值+1readfds
:可读文件描述符集writefds
:可写文件描述符集exceptfds
:异常文件描述符集timeout
:设置监视的等待事件;当NULL时,表示会一直阻塞等待;当超过timeout时会返回;当timeout设置为0时,会立即返回- 返回值:执行成功,返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1
- 关联接口
FD_SET(fd, fdsetp)
:向fdsetp中添加fdFD_CLR(fd, fdsetp)
:从fdsetp移除fdFD_ISSET(fd, fdsetp)
:测试fd是否是fdsetp中且状态改变了的(用于判断哪一个描述符状态变了)FD_ZERO(fdsetp)
:清空fdsetp集合- 示例
#include<sys/select.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
timeval tv;
fd_set rfds;//作为读描述符集合
FD_ZERO(&rfds);//清零描述符集合
int fd=0;
FD_SET(fd, &rfds);//设置监听标准输入
tv.tv_sec = 5;//设置最大等待时长为5秒
int ret=select(fd+1,&rfds,NULL,NULL,&tv);//等待描述符可读
if (ret<0)
{
perror("select");
return -1;
}
else if (ret==0)
{
printf("timeout\n");
}
else
{
printf("read\n");
}
return 0;
}
//输出
//ret=1
//read
//write data is hello world!
4.2 poll¶
- 功能:用来等待文件描述词状态的改变
- 语法:
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
fds
:文件描述符信息集合nfds
:fds集中最大描述符+1timeout
:阻塞等待时间;-1一直等待;0会立即返回;大于0表示会阻塞等待最多多少毫秒- 返回值:执行成功,返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1
- pollfd结构如下
/* Data structure describing a polling request. */
struct pollfd
{
int fd; /* File descriptor to poll. */
short int events; /* Types of events poller cares about. */
short int revents; /* Types of events that actually occurred. */
};
- 事件类型如下:
/* Event types that can be polled for. These bits may be set in `events'
to indicate the interesting event types; they will appear in `revents'
to indicate the status of the file descriptor. */
#define POLLIN 0x001 /* There is data to read. */
#define POLLPRI 0x002 /* There is urgent data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
#if defined __USE_XOPEN || defined __USE_XOPEN2K8
/* These values are defined in XPG4.2. */
# define POLLRDNORM 0x040 /* Normal data may be read. */
# define POLLRDBAND 0x080 /* Priority data may be read. */
# define POLLWRNORM 0x100 /* Writing now will not block. */
# define POLLWRBAND 0x200 /* Priority data may be written. */
#endif
#ifdef __USE_GNU
/* These are extensions for Linux. */
# define POLLMSG 0x400
# define POLLREMOVE 0x1000
# define POLLRDHUP 0x2000
#endif
/* Event types always implicitly polled for. These bits need not be set in
`events', but they will appear in `revents' to indicate the status of
the file descriptor. */
#define POLLERR 0x008 /* Error condition. */
#define POLLHUP 0x010 /* Hung up. */
#define POLLNVAL 0x020 /* Invalid polling request. */
- 示例
#include<sys/poll.h>
#include<stdio.h>
int main()
{
struct pollfd fds[2];
fds[0].fd = 0;
fds[0].events = POLLIN;
fds[1].fd = 1;
fds[1].events = POLLOUT;
int ret=poll(fds, 2, -1);
printf("ret=%d\n", ret);
for (size_t i = 0; i < 2; i++)
{
if (fds[i].revents & POLLIN)
{
printf("i=%lu fd=%d, POLLIN\n", i,fds[i].fd);
}
if (fds[i].revents & POLLOUT)
{
printf("i=%lu fd=%d, POLLOUT\n", i,fds[i].fd);
}
}
return 0;
}
//输出
//ret=1
//i=1 fd=1, POLLOUT
4.3 epoll¶
- 参考 epoll库提供了3个系统函数:
epoll_create
:创建epoll实例,返回的epoll实例需要使用close关闭epoll_ctl
:管理epoll实例epoll_wait
:等待发生在epoll实例上的事件
epoll的事件类如下:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
4.3.1 LT、ET 模式¶
- LT (Level Trigger):水平触发,一个事件只要有,就会一直触发。(默认)
- ET (Edge Trigger):边缘触发,只有一个事件从无到有才会触发。(设置 ev.events=EPOLLET)
title: socker读写事件理解LT、ET模式
- 以 socket 的读事件为例,对于水平模式,只要 socket 上有未读完的数据,就会一直产生 POLLIN 事件;
- 而对于边缘模式,socket 上第一次有数据会触发一次,后续 socket 上存在数据也不会再触发,除非把数据读完后,再次产生数据才会继续触发。
- 对于 socket 写事件,如果 socket 的 TCP 窗口一直不饱和,会一直触发 POLLOUT 事件;
- 而对于边缘模式,只会触发一次,除非 TCP 窗口由不饱和变成饱和再一次变成不饱和,才会再次触发 POLLOUT 事件。
- 使用 ET 模式需要注意正确的读写方式为:
- 读:只要可读,就一直读,直到返回 0,或者 errno = EAGAIN
- 写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN
- 正确的读 :
n = 0;
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
n += nread;
}
if (nread == -1 && errno != EAGAIN) {
perror("read error");
}
- 正确的写 :
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
nwrite = write(fd, buf + data_size - n, n);
if (nwrite < n) {
if (nwrite == -1 && errno != EAGAIN) {
perror("write error");
}
break;
}
n -= nwrite;
}
4.3.2 epoll_create¶
- 功能:创建epoll实例
- 语法:
int epoll_create(int size);
size
:从Linux 2.6.8后,可以忽略,但必须指定一个大于0的数
int epoll_create1(int flags);
flags
: 当 flags==
0 时和 epoll_create 相同;flags 还可以设置 EPOLL_CLOEXEC
- 返回值:成功时,返回引用到epoll实例的文件描述符,这个描述符会在后续epoll操作上使用;错误时,返回-1
4.3.3 epoll_ctl¶
- 功能:管理epoll实例
- 语法:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd
:epoll_create返回的epoll实例op
:可选3种选项EPOLL_CTL_ADD
1 / Add a file descriptor to the interface. /EPOLL_CTL_DEL
2 / Remove a file descriptor from the interface. /EPOLL_CTL_MOD
3 / Change file descriptor epoll_event structure. /
fd
:需要操作的文件描述符event
:指定事件信息- 返回值:成功返回0;失败返回-1
4.3.4 epoll_wait¶
- 功能:等待io事件的发生
- 语法:
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epfd
:epoll_create
返回的描述符events
:如果有事件到来,存储事件timeout
:阻塞等待时间;-1一直等待;0会立即返回;大于0表示会阻塞等待最多多少毫秒- 返回值:大于0,表示到来的事件数;等于0,表示没有事件发送;小于0,表示发送错误
int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask);
- 示例
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
using namespace std;
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main(int argc, char* argv[])
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
if ( 2 == argc )
{
if( (portnumber = atoi(argv[1])) < 0 )
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
}
else
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
//setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);
serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
//setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
cout << "accapt a connection from " << str << endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
cout << "EPOLLIN" << endl;
if ( (sockfd = events[i].data.fd) < 0)
continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
line[n] = '/0';
cout << "read " << line << endl;
//设置用于写操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的写操作事件
ev.events=EPOLLOUT|EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else if(events[i].events&EPOLLOUT) // 如果有数据发送
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
//设置用于读操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}
5 mmap和munmap¶
- 功能:一种将文件或设备映射或解除映射到内存中的技术
- 优点:
- 读写文件避免了 read() 和 write() 系统调用,也避免了数据的拷贝。
- 除了潜在的页错误,读写 map 后的文件不引起系统调用或者上下文切换。就像访问内存一样简单。
- 多个进程 map 同一个对象,可以共享数据。
- 可以直接使用指针来跳转到文件某个位置,不必使用 lseek() 系统调用。
- 劣势
- 内存浪费。由于必须要使用整数页的内存。
- 导致难以找到连续的内存区域
- 创建和维护映射和相关的数据结构的额外开销。在大文件和频繁访问的文件中,这个开销相比 read write 的 copy 开销小。
- 参考资料
- 示例
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
char *addr;
int fd;
struct stat sb;
off_t offset, pa_offset;
size_t length;
ssize_t s;
if (argc < 3 || argc > 4) {
fprintf(stderr, "%s file offset [length]\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_RDONLY);
if (fd == -1)
handle_error("open");
if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");
offset = atoi(argv[2]);
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
/* offset for mmap() must be page aligned */
if (offset >= sb.st_size) {
fprintf(stderr, "offset is past end of file\n");
exit(EXIT_FAILURE);
}
if (argc == 4) {
length = atoi(argv[3]);
if (offset + length > sb.st_size)
length = sb.st_size - offset;
/* Canaqt display bytes past end of file */
} else { /* No length arg ==> display to end of file */
length = sb.st_size - offset;
}
addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
MAP_PRIVATE, fd, pa_offset);
if (addr == MAP_FAILED)
handle_error("mmap");
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
munmap(addr, length + offset - pa_offset);
if (s != length) {
if (s == -1)
handle_error("write");
fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
5.1 mmap¶
- 语法:
void *mmap (void *addr, size_t len, int prot,int flags, int fd, __off_t offset)
addr
:一般设置NULL即可,mmap执行成功会返回一个执行原文件数据的地址len
:文件长度prot
:指定映射内存保护方式,注意不能与文件访问方式冲突,比如文件以读打开,prot则不能是PROT_WRITEPROT_READ
: 0x1 / Page can be read. /PROT_WRITE
: 0x2 / Page can be written. /PROT_EXEC
: 0x4 / Page can be executed. /PROT_NONE
: 0x0 / Page can not be accessed. /
flag
:指定映射的类型MAP_SHARED
:和其他进程共享这个文件。往内存中写入相当于往文件中写入。会影响映射了这个文件的其他进程MAP_PRIVATE
:任何内存中的改动并不反映到文件之中,也不反映到其他映射了这个文件的进程之中。如果只需要读取某个文件而不改变文件内容,可以使用这种模式
fd
:打开的文件描述符(mmap之后引用技术+1,因此可以close关闭)offset
:文件偏移长度- 返回值:成功,返回内存映射地址;失败返回MAP_FAILED(-1)
5.2 munmap¶
- 语法:
int munmap (void *addr, size_t len);
addr
:映射的内存地址(对应mmap的返回值)len
:内存字节长度(对应mmap的len参数)- 返回值:成功返回0,失败返回-1