我是靠谱客的博主 甜蜜楼房,这篇文章主要介绍LINUX系统编程--7 高级IO七 高级IO,现在分享给大家,希望可以做个参考。

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)

首先画出状态转换图:
在这里插入图片描述

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//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)

在这里插入图片描述

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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 属于假错,阻塞等待可以被信号打断
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()等待文件描述符变化。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#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()多
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#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;
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#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()将目标空间映射过来,并做查找处理。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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()实现共享内存。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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 。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(98)

评论列表共有 0 条评论

立即
投稿
返回
顶部