複数のファイルディスクリプタを監視し、その中のいずれが入出力可能な状態であるかを確認するCのシステムコールとしては、select、epoll、kqueue(BSD系)が有名だろう。
ここでは、selectとepollを使い、1プロセス1スレッドでのイベント駆動によるI/O多重化コードを備忘録として残しておく。
(参考)
最近ではselectではなく、epoll、kqueueが使われることが多い。
selectは待ち受けられるファイルディスクリプタの数に上限があり、またパフォーマンス問題も存在するためである。
I/Oからの入力に応じて発生するイベントを処理するライブラリとしてはlibevent, libev, libuvなどがあるがこの内部でもepollやkqueueが利用されている。
※libevはlibeventの速度改善、FDの制限撤廃の対応がされた改良版である。
※libuvはnode.js用のためにlibevをベースに開発されたライブラリである。
(select.c)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sys/time.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#define oops(m,x) { perror(m); exit(x); } | |
void showdata(char *, int); | |
int main(int ac, char *av[]) { | |
int fd1, fd2; | |
struct timeval timeout; | |
fd_set readfds; | |
int maxfd; | |
int retval; | |
if ( ac != 4 ){ | |
fprintf(stderr, "usage: %s [file] [file] [timeout]\n", *av); | |
exit(1); | |
} | |
if ((fd1 = open(av[1], O_RDONLY)) == -1) | |
oops(av[1], 1); | |
if ((fd2 = open(av[2], O_RDONLY)) == -1) | |
oops(av[2], 1); | |
maxfd = 1 + (fd1 > fd2 ? fd1 : fd2); | |
while(1) { | |
FD_ZERO(&readfds); | |
FD_SET(fd1, &readfds); | |
FD_SET(fd2, &readfds); | |
timeout.tv_sec = atoi(av[3]); | |
timeout.tv_usec = 0; | |
printf("before wait\n"); | |
retval = select(maxfd, &readfds, NULL, NULL, &timeout); | |
printf("after wait\n"); | |
if( retval == -1 ) | |
oops("select", 1); | |
if ( retval > 0 ){ | |
if (FD_ISSET(fd1, &readfds)) | |
showdata(av[1], fd1); | |
if (FD_ISSET(fd2, &readfds)) | |
showdata(av[2], fd2); | |
} | |
else | |
printf("no input for %d seconds\n", atoi(av[3])); | |
} | |
} | |
void showdata(char *fname, int fd) { | |
char buf[BUFSIZ]; | |
int n; | |
printf("%s: ", fname); | |
fflush(stdout); | |
n = read(fd, buf, BUFSIZ); | |
if ( n == -1 ) oops(fname, 1); | |
write(1, buf, n); | |
write(1, "\n", 1); | |
} |
(epoll.c)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/epoll.h> | |
#include <fcntl.h> | |
#define NEVENTS 16 | |
#define oops(m,x) { perror(m); exit(x); } | |
void showdata(char *, int); | |
int main(int ac, char *av[]) { | |
int fd1, fd2; | |
int epfd; | |
struct epoll_event ev, ev_ret[NEVENTS]; | |
int nfds; | |
int timeout; | |
if ( ac != 4 ){ | |
fprintf(stderr, "usage: %s [file] [file] [timeout]\n", *av); | |
exit(1); | |
} | |
if ((fd1 = open(av[1], O_RDONLY)) == -1) | |
oops(av[1], 2); | |
if ((fd2 = open(av[2], O_RDONLY)) == -1) | |
oops(av[2], 3); | |
timeout = atoi(av[3]); | |
epfd = epoll_create(NEVENTS); | |
if (epfd < 0) { | |
oops("epoll_create", 1); | |
} | |
memset(&ev, 0, sizeof(ev)); | |
ev.events = EPOLLIN; | |
ev.data.fd = fd1; | |
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev) != 0) { | |
oops("epoll_ctl", 1); | |
} | |
memset(&ev, 0, sizeof(ev)); | |
ev.events = EPOLLIN; | |
ev.data.fd = fd2; | |
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev) != 0) { | |
oops("epoll_ctl", 1); | |
return 1; | |
} | |
while (1) { | |
printf("before wait\n"); | |
nfds = epoll_wait(epfd, ev_ret, NEVENTS, timeout*1000); | |
if (nfds < 0) { | |
oops("epoll_wait", 1); | |
return 1; | |
} | |
printf("after wait\n"); | |
if (nfds == 0) { | |
printf("no input for %d seconds\n", atoi(av[3])); | |
} | |
for (int i=0; i<nfds; i++) { | |
if (ev_ret[i].data.fd == fd1) { | |
showdata(av[1], fd1); | |
} else if (ev_ret[i].data.fd == fd2) { | |
showdata(av[2], fd1); | |
} | |
} | |
} | |
return 0; | |
} | |
void showdata(char *fname, int fd) { | |
char buf[BUFSIZ]; | |
int n; | |
printf("%s: ", fname); | |
fflush(stdout); | |
n = read(fd, buf, BUFSIZ); | |
if ( n == -1 ) oops(fname, 5); | |
write(1, buf, n); | |
write(1, "\n", 1); | |
} |
$ gcc ファイル名
$ ./a.out /dev/tty /dev/input/mouse0 10
hello
/dev/tty: hello
no input after 10 seconds
マウス操作
/dev/input/mouse0: (
(補足)
readで指定したバッファサイズ以上のデータがあった場合、ループでepoll_waitの処理へ戻るとはどのような動作をするか分かるだろうか。
残りのデータを引き続き読み込めると判断されるた、待ち状態にはならない。
しかし、次のようにイベント条件を指定した場合は一度しかイベントは発火しない。
ev.events = EPOLLIN | EPOLLONESHOT
イベントを継続させる場合には、再度epoll_ctlを同じepfdとEPOLL_CTL_MOD値を渡して呼べばよい。
epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev)