概述
LINUX系统编程--7 高级IO
- 七 高级IO
- 1 非阻塞IO与阻塞
- 2 数据中继
- 3 有限状态机
- 4 用有限状态机实现一个数据中继模型(使用非阻塞IO)
- 4.1 中继引擎的实现
- 5 IO 多路转接
- 6 IO 多路转接-select
- 7 IO 多路转接-poll
- 8 IO 多路转接-epoll
- 9 其他读写相关函数
- 10 存储映射
- 11 文件锁
七 高级IO
高级IO主要研究非阻塞IO
首先要区分阻塞IO与非阻塞IO
阻塞和非阻塞是文件本身的属性
回忆在信号中:
信号会打断阻塞中的系统调用。(如open这个系统调用,有可能被信号打断,打断会返回EINTR,这个是假错! open没有出错,只是在阻塞的过程中被信号处理函数打断了!)
还有一种假错,叫EAGAIN,这也是一个假错!他的场景是IO是非阻塞的。以read为例,当read是以非阻塞形式读的时候后,读不到东西会马上返回,并设置error为EAGAIN,这也是一种假错,并不是read出错了,而是没有读到东西。
阻塞和非阻塞是文件本身的属性,而不是read的属性!
内容:
1、有限状态机编程、
2、非阻塞IO
3、IO多路转接
4、其他读写函数
5、存储映射IO
6、文件锁
1 非阻塞IO与阻塞
之前所做IO的都是阻塞的。
假设有两个设备,在进行通信。
假设要进行读左写右
有限状态机可以解决复杂流程的问题。
简单流程:一个程序的自然流程是结构化的
复杂流程:一个程序的自然流程不是结构化的。如网络协议,网络协议的处理是很复杂的
(自然流程:如大象装冰箱)
2 数据中继
两个设备之间的交换数据:
若使一个进程完成这个流程:读左-写右-读右-写左
如果是以阻塞IO的形式进行,那么若1号设备一直没有信息进来,那么会使读左的操作一直阻塞。
解决的方法有两个:
- 设计两个任务/进程/线程,一个任务进行读左写右,另一个任务进行读右写左
- 将阻塞IO换成非阻塞IO
3 有限状态机
通过Executable Code实现映射的FSM:
- 这种方式主要是通过条件分支来处理不同的字符,如if或者switch语句块(下面实例使用这个方法)
通过Passive Data实现映射的FSM:
- 在如上的switch分支中,其使用类型大致相同,因此,我们可以考虑将相似的信息保存到一张表中,这样就可以在程序中避免很多函数调用。在每个状态中都使用一张转换表来表示映射关系,转换表的索引使用输入字符来表示。此外,由于通过转换表就可以描述不同状态之间的变化,那么就没有必要将每种状态定义为一个类了,即不需要多余的继承和虚函数了,仅使用一个State即可。
相关链接: 有限状态机的原理解析与编程思路.
4 用有限状态机实现一个数据中继模型(使用非阻塞IO)
首先画出状态转换图:
//relay.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#define BUFSIZE 1024
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
enum {
STATE_R = 1,
STATE_W,
STATE_Ex,
STATE_T
};
struct fsm_st{ //这就是状态机结构!
int state;
int sfd;//源
int dfd;//目标
char buf[BUFSIZE];
int len;
int pos;
//出错原因
char *errMess;
};
static void fsm_driver(struct fsm_st* fsm){//推状态机函数
int ret;
switch(fsm->state){
case STATE_R:
fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
if (fsm->len == 0){
fsm->state = STATE_T;//正常结束
}else if (fsm->len < 0){
if (errno == EAGAIN)
fsm->state = STATE_R;//假错 保留状态
else{
fsm->state = STATE_Ex;//真正出错
fsm->errMess = "读取失败";
}
}else{
fsm->pos = 0;
fsm->state = STATE_W;//成功读入 转换状态
}
break;
case STATE_W:
ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
if (ret < 0){
if (errno == EAGAIN){
fsm->state = STATE_W;
}else{
fsm->errMess = "写入失败";
fsm->state = STATE_Ex;
}
}else{
//坚持写够
fsm->pos += ret;
fsm->len -= ret;
if (fsm->len == 0){
fsm->state = STATE_R;
}else{
fsm->state = STATE_W;
}
}
break;
case STATE_Ex:
perror(fsm->errMess);
fsm->state = STATE_T;
break;
case STATE_T:
/*do sth*/
break;
default:
abort();
break;
}
}
static void relay(int fd1,int fd2){ //中继引擎函数
int old_fd1,old_fd2;
struct fsm_st fsm12,fsm21;//定义状态机。读左写右 读右写左
old_fd1 = fcntl(fd1,F_GETFL);
fcntl(fd1,F_SETFL,old_fd1|O_NONBLOCK);//设为非阻塞
old_fd2 = fcntl(fd2,F_GETFL);
fcntl(fd2,F_SETFL,old_fd2|O_NONBLOCK);//设为非阻塞
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_R;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
while(fsm12.state != STATE_T || fsm21.state != STATE_T){//状态机没有停转
fsm_driver(&fsm12);//推状态机
fsm_driver(&fsm21);//推状态机
}
//恢复原来的文件描述符状态
fcntl(fd1,F_SETFL,old_fd1);
fcntl(fd2,F_SETFL,old_fd2);
}
int main()//main函数通常是模拟用户的操作
{
int fd1,fd2;
//假设用户使用阻塞的方式打开设备
fd1 = open(TTY1,O_RDWR);
if (fd1 < 0){
perror("open()");
exit(1);
}
write(fd1,"TTY1n",5);
fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
if (fd2 < 0){
perror("open()");
close(fd1);
exit(1);
}
write(fd2,"TTY2n",5);
relay(fd1,fd2);//中继引擎函数
close(fd1);
close(fd2);
exit(0);
}
4.1 中继引擎的实现
5 IO 多路转接
IO多路转接,监视文件描述符读,写,异常状态
上面的relay.c,处于盲等状态,原因在于一直在处于EAGAIN,若没有 数据,一直会盲等。
为什么引入多路转接:
-
在前面 状态机实现拷贝两个设备之间的数据的程序中,可以发现这样的问题:程序在运行期间,一定是一个忙等的状态,CPU 使用率很高,很浪费CPU时间,原因在于程序其实大部分时间都在忙于 判断假错,重读 重写的状态中循环。这个属于IO 密集,负载不密集的任务,即数据量不大,但是IO 很密集。对于IO密集型的任务 可以对程序进行IO多路转接,本质就是监视文件描述符的行为,当 当前文件描述符发生了我感兴趣的行为的时候,才会去做后续操作,如前面 两个设备之间的数据交换,前面的做法是盲推,一直不停的探测,尝试,看有没有内容可读或者可写。如果我们用IO 多路转接,就可以变成 当某个文件描述符状态发生了我感兴趣的动作的时候,我才会做后续操作,节省CPU时间。
-
解决IO密集型任务中盲等的问题,监视文件描述符的行为,当当前文件描述符发生了我们感兴趣的行为时,才去做后续操作,可以实现安全的休眠(替代sleep)
IO多路转接比较:
- select() :比较古老,可移植,兼容性好 但设计有缺陷,以事件为单位组织文件描述符。
- poll():可移植,以文件描述符为单位组织事件。
- epoll ():linux的poll封装方案,不可移植
6 IO 多路转接-select
可以实现安全的休眠(替代sleep)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
/* nfds : 文件描述符数量,不过指的是,所包含的最大文件描述符 +1 的值
readfds:可读的文件描述符集合
writefds:可写的文件描述符集合
exceptfds:异常的文件描述符集合
timeout 超时设置,阻塞。如果不实现超时设置,那么该函数会死等
*/
void FD_CLR(int fd, fd_set *set);//从指定的文件描述符集合中删除指定文件描述符fd
int FD_ISSET(int fd, fd_set *set);//判断文件描述符fd 是否存在于文件描述符集合set中
void FD_SET(int fd, fd_set *set);// 将文件描述符fd 放到 文件描述符集合set中
void FD_ZERO(fd_set *set); //清空一个文件描述符集合
selectd的返回值:
- 返回值是现在发生了我们感兴趣事件的文件描述符行为的个数,并且依然将这些文件描述符放在 读集,写集,错误集这些文件描述符集合中。
- 失败返回-1,设置errno。EINTR 属于假错,阻塞等待可以被信号打断
ERROR:
EINTR A signal was caught; see signal(7). 阻塞等待 可以被信号打断
EINVAL nfds is negative or exceeds the RLIMIT_NOFILE resource limit (see getrlimit(2)).
EINVAL the value contained within timeout is invalid.
ENOMEM unable to allocate memory for internal tables.
The time structures involved are defined in <sys/time.h> and look like
秒+微秒
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
实验:改进上面的用状态机实现的中继模型,加入多路IO转接机制(select)
(注意:只对relay函数做出了改变)
程序就不再是一直处于 假错 重读 重写 中循环,cpu使用率大大降低,因为大多数时间都阻塞在 select()等待文件描述符变化。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024
enum
{
STATE_R = 1,
STATE_W,
STATE_AUTO,
STATE_Ex,
STATE_T
};
struct fsm_st
{
int state;
int sfd;
int dfd;
char buf[BUFSIZE];
int len;
int pos;
char* errstr;
};
static void fsm_driver(struct fsm_st *fsm)
{
int ret;
switch(fsm->state)
{
case STATE_R:
fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
if(fsm->len == 0)
fsm->state = STATE_T;
else if(fsm->len < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_R;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos = 0;
fsm->state = STATE_W;
}
break;
case STATE_W:
ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
if(ret < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_W;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos += ret;
fsm->len -= ret;
if(fsm->len == 0)
fsm->state = STATE_R;
else
fsm->state = STATE_W;
}
break;
case STATE_Ex:
perror(fsm->errstr);
fsm->state = STATE_T;
break;
case STATE_T:
/* do something*/
break;
default:
abort();
break;
}
}
static int max(int a,int b)
{
if(a > b)
{
return a;
}
return b;
}
static void relay(int fd1,int fd2)
{
int fd1_save,fd2_save;
struct fsm_st fsm12,fsm21;
fd_set rset,wset;
fd1_save = fcntl(fd1,F_GETFL);
fcntl(fd1,F_SETFL,fd1_save|O_NONBLOCK);
fd2_save = fcntl(fd2,F_GETFL);
fcntl(fd2,F_SETFL,fd2_save|O_NONBLOCK);
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_W;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
while(fsm12.state != STATE_T || fsm21.state != STATE_T) //状态机没有停转
{
//设置现场
FD_ZERO(&rset);
FD_ZERO(&wset);
if(fsm12.state == STATE_R)
FD_SET(fsm12.sfd,&rset);
if(fsm12.state == STATE_W)
FD_SET(fsm12.dfd,&wset);
if(fsm21.state == STATE_R)
FD_SET(fsm21.sfd,&rset);
if(fsm21.state == STATE_W)
FD_SET(fsm21.dfd,&wset);
//监控
if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO)
{
if(select(max(fd1,fd2)+1,&rset,&wset,NULL,NULL) < 0)
{
if(errno == EINTR)
continue;
perror("select()");
exit(1);
}
}
/*
struct timeval ts;
ts.tv_sec = 0;
ts.tv_usec= 2;
int maxfd = fd1>fd2?fd1:fd2;
if (fsm12.state < STATE_AUTO ||fsm21.state < STATE_AUTO)
{
if (select(maxfd+1,&rset,&wset,NULL,&ts) < 0)
{
if (errno == EINTR)
continue;
perror("select()");
exit(1);
}
}
*/
//根据监控结果 最下一步动作
if(FD_ISSET(fd1,&rset) || FD_ISSET(fd2,&wset) || fsm12.state > STATE_AUTO)
fsm_driver(&fsm12);
if(FD_ISSET(fd2,&rset) || FD_ISSET(fd1,&wset) || fsm12.state > STATE_AUTO)
fsm_driver(&fsm21);
}
fcntl(fd1,F_SETFL,fd1_save);
fcntl(fd2,F_SETFL,fd2_save);
}
int main()
{
int fd1,fd2;
fd1 = open(TTY1,O_RDWR);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
relay(fd1,fd2);
close(fd2);
close(fd1);
}
注意:
- 该函数 如果不实现超时设置,那么该函数会阻塞死等,一直等到感兴趣的事件发生,即关心的可读,可写的文件描述符集合中 文件描述符变成 可读,可写状态的时候,该函数才会返回。
- 注意三个 fd_set 不是const类型,是可变的。当select()返回的时候,这三个集合当中就不再是我们之前所布置的监事现场了,而是存放结果的场所。如果select()不设置超时时间 阻塞等待 被信号打断,发生了假错,那么这三个集合也将被清空。
select()的问题:
-
1 监视现场位置(三个集合) 和 监视结果位置(三个集合) 相同,用的是同一块空间,如果 读集合,写集合,出错集合 各设置10个文件描述符,一共三十个文件描述符,只要任意一个文件描述符发生变化,如读集中某一个文件描述符 变成了可读状态,则函数返回,即返回读集合中能读的文件描述符个数,即1。而其他集合就会被清空。再次监控,需要重新设置监视现场。
-
2 一个进程中能够打开的文件描述符地方个数是可以更改的,ulimit -a。 那么第一个参数 nfds就有问题,如果我们监视的文件描述符个数 超过了 有符号整形最大值范围,则会溢出。
7 IO 多路转接-poll
poll 引进: 补充 select()的不足。他俩的区别在于:
- 1 poll() 的 监视现场位置 和 监视结果位置分开存放,不需要向select()一样 每次返回都发生覆盖,不需要重复设置监视现场
- 2 poll() 所能监控的状态有7中,并且可以自己添加需要的其他状态位,比select()多
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能与select()相似,等待目标文件描述符发生所需要的的变化,返回。
参数:
fds: pollfd 结构体数组的起始位置。
nfds:数组元素个数,即有多少个需要监视的文件描述符
timeout: 超时设置,单位是毫秒,即设置1000,为1秒。如果不设置超时,则函数阻塞等待
- 0: 以非阻塞方式 poll()
- -1: 阻塞方式poll()
- 时间:超时设置
返回值:
成功返回一个整数,表示有多少个事件已经发生,失败返回-1,并且设置 errno,如假错EINTR
ERRORS
EFAULT The array given as argument was not contained in the calling program's address space.
EINTR A signal occurred before any requested event; see signal(7). 假错,信号打断阻塞 poll
EINVAL The nfds value exceeds the RLIMIT_NOFILE value.
ENOMEM There was no space to allocate file descriptor tables.
pollfd中记录一个文件描述符相关内容,包括对文件描述符所关心的状态,和返回的状态。
events 和 revents是两个位图,有7中状态,比select多,select只有3种状态,除了读写 都是异常
events 和 revents是两个位图 可以添加我们需要的状态
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */ 所关心的状态
short revents; /* returned events */已经发生的事件,与所关心的事件分开存放!与select()不同
};
The bits that may be set/returned in events and revents are defined in <poll.h>:
POLLIN 是否可读
POLLPRI
There is urgent data to read (e.g., out-of-band data on TCP socket; pseudoterminal master in packet mode has seen state change in slave).
POLLOUT 是否可写
POLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut down writing half of connection. The _GNU_SOURCE feature test macro must be defined (before including any header files) in order
to obtain this definition.
POLLERR
Error condition (only returned in revents; ignored in events).
POLLHUP
Hang up (only returned in revents; ignored in events). Note that when reading from a channel such as a pipe or a stream socket, this event merely indicates that the peer closed
its end of the channel. Subsequent reads from the channel will return 0 (end of file) only after all outstanding data in the channel has been consumed.
POLLNVAL
Invalid request: fd not open (only returned in revents; ignored in events).
实验:改进上面的用状态机实现的中继模型,加入多路IO转接机制(poll)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
//#include <sys/select.h>
#include <poll.h>
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024
enum
{
STATE_R = 1,
STATE_W,
STATE_AUTO,
STATE_Ex,
STATE_T
};
struct fsm_st
{
int state;
int sfd;
int dfd;
char buf[BUFSIZE];
int len;
int pos;
char* errstr;
};
static void fsm_driver(struct fsm_st *fsm)
{
int ret;
switch(fsm->state)
{
case STATE_R:
fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
if(fsm->len == 0)
fsm->state = STATE_T;
else if(fsm->len < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_R;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos = 0;
fsm->state = STATE_W;
}
break;
case STATE_W:
ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
if(ret < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_W;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos += ret;
fsm->len -= ret;
if(fsm->len == 0)
fsm->state = STATE_R;
else
fsm->state = STATE_W;
}
break;
case STATE_Ex:
perror(fsm->errstr);
fsm->state = STATE_T;
break;
case STATE_T:
/* do something*/
break;
default:
abort();
break;
}
}
static void relay(int fd1,int fd2)
{
int fd1_save,fd2_save;
struct fsm_st fsm12,fsm21;
struct pollfd pfd[2];
fd1_save = fcntl(fd1,F_GETFL);
fcntl(fd1,F_SETFL,fd1_save|O_NONBLOCK);
fd2_save = fcntl(fd2,F_GETFL);
fcntl(fd2,F_SETFL,fd2_save|O_NONBLOCK);
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_W;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
//布置 监视现场
pfd[0].fd = fd1;
pfd[1].fd = fd1;
while(fsm12.state != STATE_T || fsm21.state != STATE_T)
{
//以文件描述符为单位组织事件,设置文件描述符所关心的状态,如状态机fsm12 为读状态时候,关心的文件描述符状态为读,只要文件可读,就 通知poll()返回,即只要目标文件中有内容可读,这里就是当TTY11 中有户数输入后,即为可读。
pfd[0].events = 0;
if(fsm12.state == STATE_R)
pfd[0].events |= POLLIN;
if(fsm21.state == STATE_W)
pfd[0].events |= POLLOUT;
pfd[1].events = 0;
if(fsm12.state == STATE_W)
pfd[1].events |= POLLOUT;
if(fsm21.state == STATE_R)
pfd[1].events |= POLLIN;
if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO)
{
//等待目标文件描述符发生状态变化,这里就是当TTY11 中有户数输入后,即为可读,则通知poll返回。
while(poll(pfd,2,-1) < 0)
{
if(errno == EINTR)
continue;
perror("poll()");
exit(1);
}
}
if(pfd[0].revents & POLLIN || pfd[1].revents & POLLOUT || fsm12.state > STATE_AUTO)
fsm_driver(&fsm12);
if(pfd[1].revents & POLLIN || pfd[0].revents & POLLOUT || fsm12.state > STATE_AUTO)
fsm_driver(&fsm21);
}
fcntl(fd1,F_SETFL,fd1_save);
fcntl(fd2,F_SETFL,fd2_save);
}
int main()
{
int fd1,fd2;
fd1 = open(TTY1,O_RDWR);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
relay(fd1,fd2);
close(fd2);
close(fd1);
}
8 IO 多路转接-epoll
epoll不可移植。
epoll VS poll
- poll() 可以理解为我们在用户态创建并维护一个结构体数组,而 epoll() 则是相当于,poll 中的结构体数组被放在内核态,内核为我们维护该数组,内核为我们提供一些方法(系统调用,即下面的三个函数)来管理这个数组。
- epoll是针对linux优化了的,性能要比poll好。
需要三部分:
1、int epoll_create(int size);
- 创建一个epoll的句柄,当创建好epoll句柄后,它就是会占用一个fd值,使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。成功返回一个文件描述符,失败返回-1。
2、 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 对 epfd实例当中的 fd文件描述符 进行 op(添加,删除,更改)epoll_event 动作行为。这个函数是epoll的事件注册函数
- op:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
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 */
};
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3、 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 等待事件的产生
实验:改进上面的用状态机实现的中继模型,加入多路IO转接机制(epoll)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
//#include <sys/select.h>
//#include <poll.h>
#include <sys/epoll.h>
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024
enum
{
STATE_R = 1,
STATE_W,
STATE_AUTO,
STATE_Ex,
STATE_T
};
struct fsm_st
{
int state;
int sfd;
int dfd;
char buf[BUFSIZE];
int len;
int pos;
char* errstr;
};
static void fsm_driver(struct fsm_st *fsm)
{
int ret;
switch(fsm->state)
{
case STATE_R:
fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
if(fsm->len == 0)
fsm->state = STATE_T;
else if(fsm->len < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_R;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos = 0;
fsm->state = STATE_W;
}
break;
case STATE_W:
ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
if(ret < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_W;
else
{
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else
{
fsm->pos += ret;
fsm->len -= ret;
if(fsm->len == 0)
fsm->state = STATE_R;
else
fsm->state = STATE_W;
}
break;
case STATE_Ex:
perror(fsm->errstr);
fsm->state = STATE_T;
break;
case STATE_T:
/* do something*/
break;
default:
abort();
break;
}
}
static void relay(int fd1,int fd2)
{
int fd1_save,fd2_save;
struct fsm_st fsm12,fsm21;
struct epoll_event ev;
int epfd;
fd1_save = fcntl(fd1,F_GETFL);
fcntl(fd1,F_SETFL,fd1_save|O_NONBLOCK);
fd2_save = fcntl(fd2,F_GETFL);
fcntl(fd2,F_SETFL,fd2_save|O_NONBLOCK);
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_W;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
//创建一个epoll的句柄
epfd = epoll_creat(10);
if(epfd < 0)
{
perror("epoll_creat()");
exit(1);
}
//添加需要监视的文件描述符
ev.events = 0;
ev.data.fd = fd1;
epoll_ctl(epfd,EPOLL_CTL_ADD,fd1,&ev);
ev.events = 0;
ev.data.fd = fd2;
epoll_ctl(epfd,EPOLL_CTL_ADD,fd2,&ev);
while(fsm12.state != STATE_T || fsm21.state != STATE_T)
{
//针对文件描述符fd1 布置监视现场
ev.data.fd = fd1;
ev.events = 0;
if(fsm12.state == STATE_R)
ev.events |= EPOLLIN;
if(fsm21.state == STATE_W)
ev.events |= EPOLLOUT;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd1,&ev);
//针对文件描述符fd2 布置监视现场
ev.data.fd = fd2;
ev.events = 0;
if(fsm12.state == STATE_W)
ev.events |= EPOLLOUT;
if(fsm21.state == STATE_R)
ev.events |= EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd2,&ev);
if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO)
{
//监视fd1 fd2
while(epoll_wait(epfd,&ev,1,-1) < 0)
{
if(errno == EINTR)
continue;
perror("epoll_wait()");
exit(1);
}
}
//根据监视结果做出相应动作
if( ev.data.fd == fd1 && ev.events & EPOLLIN || ev.data.fd == fd2 && ev.events & EPOLLOUT || fsm12.state > STATE_AUTO)
fsm_driver(&fsm12);
if( ev.data.fd == fd1 && ev.events & EPOLLOUT|| ev.data.fd == fd2 && ev.events & EPOLLIN || fsm12.state > STATE_AUTO)
fsm_driver(&fsm21);
}
fcntl(fd1,F_SETFL,fd1_save);
fcntl(fd2,F_SETFL,fd2_save);
close(epfd);
}
int main()
{
int fd1,fd2;
fd1 = open(TTY1,O_RDWR);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
relay(fd1,fd2);
close(fd2);
close(fd1);
}
9 其他读写相关函数
readv和writev
向多个地址中读或写。
10 存储映射
mmap()函数
将一块内存或者是某一个文件的存储内容 映射到当前进程空间里面来。结果就是 我们在当前进程中访问目标空间中的数据。
int munmap(void *addr, size_t length);
-
addr: 目标内存空间 放到当前进程空间的起始地址,若为空,则函数自己找可用的位置
-
length:需要映射目标空间的长度
-
port : 映射过来的内存属性,即可以对该内存做什么操作,即映射后的操作权限
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed. -
flags :标记,特殊要求,位图
必选:
MAP_SHARED : 进程对进程对映射过来的内存修改,会同步到真实的内存空间。
MAP_PRIVATE:进程对映射过来的内存修改,只是改动当前进程空间中目标内存空间,不会同步到真实的内存空间
可选:
如:MAP_ANONYMOUS 匿名映射,当前映射不依赖于任何文件(fd选项为-1),类似于 malloc()功能,空间会被初始化为0 -
fd : 文件描述符,如 需要映射一个文件过来,那么需要先将目标文件打开。
-
offset :偏移量
从 fd 文件的 offset偏移量开始映射,映射length长度的空间到当前进程空间的addr地址,空间权限设置为port ,特殊要求是flags
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- 解除映射
- 对 addr地址处 映射过来的空间(大小为length) 进行解除映射
- 如果 mmap()的时候 flags 有MAP_ANONYMOUS 匿名映射要求,则此处munmap() 类似于 free()
实验1 :
查看一段内存空间中有多少 ‘a’。 用mmap()将目标空间映射过来,并做查找处理。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main(int argc,char *argv[])
{
int fd;
struct stat statres;
char *str;
int i,count=0;
if(argc < 2)
{
fprintf(stderr,"Usage...n");
exit(1);
}
fd = open(argv[1],O_RDONLY);
if(fd < 0)
{
perror("open()");
exit(1);
}
if(fstat(fd,&statres) < 0)
{
perror("fstat()");
exit(1);
}
str = mmap(NULL,statres.st_size,PROT_READ,MAP_SHARED,fd,0);
if(str == MAP_FAILED)
{
perror("mmap()");
exit(1);
}
close(fd);
for(i = 0; i < statres.st_size; i++)
{
if(str[i] == 'a')
count ++;
}
printf("%dn",count);
munmap(str,statres.st_size);
}
实验2:父子进程之间的通信,mmap()实现共享内存。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#define MEMSIZE 1024
int main(int argc,char *argv[])
{
char *ptr;
pid_t pid;
//不依赖于任何文件fd, 相当于 malloc()一段空间后映射过来,不依赖任何文件 所以fd==-1
ptr = mmap(NULL,MEMSIZE,PROT_READ | PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if(ptr == MAP_FAILED)
{
perror("mmap()");
exit(1);
}
pid = fork();//先mmap 后fork
if(pid < 0)
{
perror("fork()");
munmap(ptr,MEMSIZE);
exit(1);
}
if(pid == 0) //Child write
{
strcpy(ptr,"Hello!");
munmap(ptr,MEMSIZE);
exit(0);
}
else //Parent read
{
wait(NULL);
puts(ptr);//puts()将字符串s和末尾的换行符写入标准输出。
munmap(ptr,MEMSIZE);
exit(0);
}
}
11 文件锁
fcntl();
lockf();
flock();
int lockf(int fd, int cmd, off_t len);
- fd:需要锁的目标文件
- len: 要锁多长,0表示 文件有多长 锁多长,即加锁到文件末端,就算文件加长 也会随之锁上
- cmd :实现的命令
F_LOCK 解锁,阻塞式加锁
F_TLOCK 尝试加锁,非阻塞式加锁
F_ULOCK 解锁
F_TEST 测试有没有锁
注意:
给一个文件加锁,参数指定fd,通过文件描述符给文件加锁,加锁是加到了文件本身,即inode层面,并不是每个fd所对应的文件属性结构体。注意:当一个进程打开两次同一个文件的时候,如图中所用的第一个和第三个,指向不同的文件属性结构体,但是都是指向同一个 inode文件,这种情况 如果一个加锁后,另一个执行close()文件,会造成加锁的文件被意外解锁。
实验:多进程并发,实现20个进程操作同一个文件,每个进程打开+1 。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#define PROCNUM 20
#define FNAME "/home/mhr/Desktop/xitongbiancheng/super_io/out"
#define LINESIZE 1024
static void func_add(void)
{
FILE *fp;
int fd;
char linebuf[LINESIZE];
fp = fopen(FNAME,"r+");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
fd = fileno(fp);//返回文件描述符
//if(fd < 0)
//上锁,加锁到文件末端
lockf(fd,F_LOCK,0);
fgets(linebuf,LINESIZE,fp);
fseek(fp,0,SEEK_SET);
//sleep(1);
//文件的全缓冲,而fprintf是航缓冲,所以解锁前没有close() 写操作就不能将数据写到文件中,需要ffllush()刷新流
fprintf(fp,"%dn",atoi(linebuf)+1);
fflush(fp);//刷新
//解锁
lockf(fd,F_ULOCK,0);
//解锁后 close() 防止意外解锁
fclose(fp);
return;
}
int main()
{
int i,err;
pid_t pid;
for(i = 0; i < PROCNUM; i++)
{
pid = fork();//创建20个子进程
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0)//Child
{
func_add();
exit(0);//子进程操作后就结束
}
}
for(i = 0;i < PROCNUM; i++)
{
wait(NULL);//父进程收尸
}
exit(0);
}
最后
以上就是甜蜜楼房为你收集整理的LINUX系统编程--7 高级IO七 高级IO的全部内容,希望文章能够帮你解决LINUX系统编程--7 高级IO七 高级IO所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复