C言語のselect/pselect/poll/ppoll/epoll関数の違いを教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
C言語のselect/pselect/poll/ppoll/epoll関数の違いを紹介します.
目次
select/pselect関数
1 2 3 4 5 |
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); |
select/pselect関数は,プログラムで複数のファイルディスクリプタを監視し,一つ以上のファイルディスクリプタがある種のI/O操作の「ready(準備ができた)」状態(例:読み込み可能になった状態)になるまで待つことができる関数です.
ここで,ファイルディスクリプタが ready状態とは,read関数等のI/O操作が停止(block)なしに実行したり,write関数を実行したりできる状態にあることを意味します.
select関数とpselect関数の動作は同じですが,以下が異なります.
- select関数では,タイムアウト時間の指定に構造体struct timeval(秒・マイクロ秒単位)を利用しますが,pselect関数では構造体struct timespec(秒・ナノ秒単位)を利用します.
- select関数は残り時間を示すtimeout引数を更新することがありますが,pselect関数はこの引数を変更しない.
- select関数はsigmask引数を持たない.その動作はsigmaskにNULLを指定した場合のpselect関数と同じです.
3つの独立したファイルディスクリプタ集合の監視を行います.
readfdsに入れられたディスクリプタについては,読み込みが可能かどうかを監視します.
より正確にいうと,停止(block)なしで読むことができるかを調べます.
ファイルの終端 (EOF:End-Of-File) の場合も,ファイルディスクリプタは読み込み可能として扱われます.
writefdsに入れられたディスクリプタについては,書き込み用に利用可能な領域があるかを監視します.
ただし,大きな書き込みの場合には停止する可能性はあります.
exceptfdsにあるものについては,例外の監視を行います.
システムコール終了時に,どのファイルディスクリプターの状態が実際に変化したか示すために,集合の内容が変更されます.
ある種別のイベントを監視したいファイルディスクリプタが一つもない場合には,対応するファイルディスクリプタ集合にNULLを指定することができます.
集合を操作するために4つのマクロが提供されています.
FD_ZEROは集合を消去します.
FD_SETとFD_CLRはそれぞれ指定したファイルディスクリプタの集合への追加,削除を行います.
FD_ISSETは集合にファイルディスクリプタがあるかどうか調べます.
fd_setは固定サイズのバッファで,負やFD_SETSIZE(1,024の場合が多い)以上の値を持つfdに対してFD_CLR/FD_SETマクロを実行した場合,未定義に動作になるので注意して下さい.
nfdsは3つの集合に含まれるファイルディスクリプタの最大値に1を足したものです.
timeout引数は,select関数がファイルディスクリプタがready状態になるのを待って停止する時間を指定します.
呼び出しは以下のいずれかになるまで停止します.
- ファイルディスクリプタが利用可能になった.
- システムコールがシグナルハンドラにより割り込まれた.
- タイムアウト時間が満了した.
このtimeout時間はシステムクロックの粒度に切り上げられ,カーネルのスケジューリング遅延により少しだけ長くなる可能性がある点に注意して下さい.
timeval構造体の両方のフィールドが0の場合,select関数はすぐに復帰します.
この機能はポーリング(polling)を行うのに便利です.
timeoutにNULL(タイムアウトなし)が指定されると,select関数は無期限に停止(block)します.
sigmaskは,シグナルマスクへのポインタです.
sigmaskがNULLでない場合,pselect関数はsigmaskが指しているシグナルマスクで現在のシグナルマスクを置き換えてからselect関数を実行し,終了後にシグナルマスクを元のシグナルマスクに戻します.
select関数の使い方
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/select.h> #define TIMEOUT_SEC 3 int main(void) { fd_set rfds; struct timeval tv; int retval; FD_ZERO(&rfds); FD_SET(0, &rfds); tv.tv_sec = TIMEOUT_SEC; tv.tv_usec = 0; if ((retval = select(1, &rfds, NULL, NULL, &tv)) == -1) { perror("select()"); exit(1); } else if (retval) { printf("Now get data!\n"); } else { printf("%d seconds timeout was expired!\n", TIMEOUT_SEC); } printf("FD_ISSET(0, &rfds) = %d\n", FD_ISSET(0, &rfds)); FD_CLR(0, &rfds); return 0; } |
実行結果は以下になります.
エンターキーを入力するとデータを取得したことがわかります.FD_ISSETの返り値は1になります.
また,実行後に何もせずに3秒待つとタイムアウトされた旨が表示されます.FD_ISSETの返り値は0になります.
1 2 3 4 5 6 7 8 |
$ gcc select.c $ a.out Now get data! FD_ISSET(0, &rfds) = 1 $ a.out 3 seconds timeout was expired! FD_ISSET(0, &rfds) = 0 |
pselect関数の使い方
pselect関数の使い方は以下になります.
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/select.h> #define TIMEOUT_SEC 3 int main(void) { fd_set rfds; struct timespec ts; int retval; FD_ZERO(&rfds); FD_SET(0, &rfds); ts.tv_sec = TIMEOUT_SEC; ts.tv_nsec = 0; if ((retval = pselect(1, &rfds, NULL, NULL, &ts, NULL)) == -1) { perror("select()"); exit(1); } else if (retval) { printf("Now get data!\n"); } else { printf("%d seconds timeout was expired!\n", TIMEOUT_SEC); } printf("FD_ISSET(0, &rfds) = %d\n", FD_ISSET(0, &rfds)); FD_CLR(0, &rfds); return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 6 7 8 |
$ gcc pselect.c $ a.out Now get data! FD_ISSET(0, &rfds) = 1 $ a.out 3 seconds timeout was expired! FD_ISSET(0, &rfds) = 0 |
poll/ppoll関数
1 2 3 |
int poll(struct pollfd *fds, nfds_t nfds, int timeout); int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask); |
poll/ppoll関数を紹介します.
poll関数はselect関数と同様のタスクを実行します.
つまり,ファイルディスクリプタのセットのうち,I/Oを実行する準備ができたready状態のものを待ちます.
- fds:監視するファイルディスクリプタ集合
- nfds:fds配列の要素数を指定
- timeout:ファイルディスクリプターが利用可能になるまでpoll関数が停止する時間をミリ秒で指定
- tmo_p:ppoll関数が停止する時間の上限を指定
- sigmask:シグナルマスクのサイズをバイト数で指定(NULLの場合,poll/ppoll関数の違いはtimeout引数の精度のみ)
fdsは,以下のstruct pollfd構造体の配列です.
1 2 3 4 5 |
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ }; |
- fd:オープンされたファイルのファイルディスクリプタ
- events:入力パラメータで,ファイルディスクリプタfd に関して,アプリケーションが興味を持っているイベントのビットマスクを指定
- revents:出力パラメーターで,実際に起こったイベントがカーネルにより設定
tmo_pには以下のstruct timespec構造体へのポインタを指定します.
1 2 3 4 |
struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; |
tmo_pがNULLの場合,ppoll関数は無限に停止する可能性があります.
poll関数とppoll関数の関係は,select関数とpselect関数の関係と同様です.
pselect関数と同様に,ppoll関数を利用するとアプリケーションはファイルディスクリプタの状態変化やシグナルの捕捉を安全に待つことができます.
poll関数の使い方
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/poll.h> #define TIMEOUT_SEC 3 #define NR_FDS 2 int main(void) { struct pollfd fds[NR_FDS] = { {STDIN_FILENO, POLLIN, 0}, {STDOUT_FILENO, POLLOUT, 0} }; int ret; if ((ret = poll(fds, NR_FDS, TIMEOUT_SEC * 1000)) == -1) { perror("poll"); exit(1); } if (!ret) { printf("%d seconds elapsed.\n", TIMEOUT_SEC); exit(2); } if (fds[STDIN_FILENO].revents & POLLIN) { printf("POLLIN: There is data to read.\n"); } if (fds[STDOUT_FILENO].revents & POLLOUT) { printf("POLLOUT: Writing is now possible.\n"); } return 0; } |
実行結果は以下になります.
実行するとPOLLOUTの表示のみがでます.
poll.cファイルを標準入力からのリダイレクトとした場合,POLLINとPOLLOUTの両方の表示がでます.
1 2 3 4 5 6 |
$ gcc poll.c $ a.out POLLOUT: Writing is now possible. $ a.out < poll.c POLLIN: There is data to read. POLLOUT: Writing is now possible. |
ppoll関数の使い方
ppoll関数の使い方は以下になります.
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/poll.h> #define TIMEOUT_SEC 3 #define NR_FDS 2 int main(void) { struct pollfd fds[NR_FDS] = { {STDIN_FILENO, POLLIN, 0}, {STDOUT_FILENO, POLLOUT, 0} }; int ret; struct timespec tmo_p = {TIMEOUT_SEC, 0}; if ((ret = ppoll(fds, NR_FDS, &tmo_p, NULL)) == -1) { perror("ppoll"); exit(1); } if (!ret) { printf("%d seconds elapsed.\n", TIMEOUT_SEC); exit(2); } if (fds[STDIN_FILENO].revents & POLLIN) { printf("POLLIN: There is data to read.\n"); } if (fds[STDOUT_FILENO].revents & POLLOUT) { printf("POLLOUT: Writing is now possible.\n"); } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc ppoll.c $ a.out POLLOUT: Writing is now possible. $ a.out < ppoll.c POLLIN: There is data to read. POLLOUT: Writing is now possible. |
epollファミリー関数
epollファミリー関数を紹介します.
※epollファミリー関数とは,epollがプリフィックスについた関数群のことです.
epoll_create/epoll_create1関数
1 2 |
int epoll_create(int size); int epoll_create1(int flags); |
epoll_create/epoll_create1関数を紹介します.
epoll_create関数は新規のepollインスタンスを作成し,そのファイルディスクリプタを返します.
epoll_create1関数は,flagsが0の場合,現在では使われていないsize引数がなくなっている点を除けばepoll_create関数と同じです.
flagsに以下の値をビット毎の論理和(OR)で指定することで,異なる動作をさせることができます.
- EPOLL_CLOEXEC:新しいファイルディスクリプタに対してclose-on-exec(FD_CLOEXEC)フラグを設定
epoll_ctl関数
1 |
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); |
epoll_ctl関数は,ファイルディスクリプタepfdが参照するepollインスタンスに対する操作を行います.
- epfd:参照するファイルディスクリプタ
- op:対象のファイルディスクリプタfdに対する操作
- fd:対象のファイルディスクリプタ
- event:ファイルディスクリプタfdにリンクされたオブジェクト
event引数のstruct epoll_event構造体(struct epoll_data構造体を含む)は以下になります.
1 2 3 4 5 6 7 8 9 10 11 |
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; epoll_data_t data; }; |
eventsメンバは,利用可能なイベントタイプを使って構成された ビットセットです(詳細はリンク先参照).
epoll_wait/epoll_pwait関数
1 2 3 4 5 |
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask); |
epoll_wait/epoll_pwait関数を紹介します.
epoll_wait関数は,ファイルディスクリプタepfdにより参照されるepollインスタンス上でイベントを待ちます.
eventsが指すバッファは,interest listにあるファイルディスクリプタのうち,何らかのイベントが利用可能なものについての情報をready listから返すために利用されます.
maxeventsまでがepoll_wait関数により返されます.
timeout引数はepoll_wait関数がブロックするミリ秒数を指定します.
時間はCLOCK_MONOTONICクロックに対して測定されます.
epoll_waitの呼び出しは,以下のいずれかが起こるまでブロックされます.
- ファイルディスクリプターがイベントを配送した
- 呼び出しがシグナルハンドラーにより割り込まれた
- タイムアウトが満了した
epoll_wait関数とepoll_pwait関数の関係は,select関数とpselect関数の関係と同様です.
pselect関数と同様に,epoll_pwait関数を利用すると,アプリケーションはファイルディスクリプタが準備できた状態になるか,シグナルが捕捉されるまで,安全に待つことができます.
epollファミリー関数の使い方
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <unistd.h> #include <sys/epoll.h> #define TIMEOUT_SEC 3 #define MAX_EVENTS 5 #define READ_SIZE 10 #define EXIT_STR "exit\n" int main(void) { int event_count; int i; size_t read_bytes; char read_buffer[READ_SIZE + 1]; struct epoll_event event, events[MAX_EVENTS]; int epoll_fd; if ((epoll_fd = epoll_create1(0)) == -1) { perror("epoll_create1"); exit(1); } event.events = EPOLLIN; event.data.fd = 0; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event) != 0) { perror("epoll_ctl"); close(epoll_fd); exit(2); } while (true) { if ((event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, TIMEOUT_SEC * 1000)) == -1) { perror("epoll_ctl"); close(epoll_fd); exit(3); } printf("%d ready events\n", event_count); for (i = 0; i < event_count; i++) { printf("File descriptor = %d\n", events[i].data.fd); if ((read_bytes = read(events[i].data.fd, read_buffer, READ_SIZE)) == -1) { perror("read"); exit(4); } printf("read_bytes = %zu\n", read_bytes); read_buffer[read_bytes] = '\0'; printf("read_buffer = %s", read_buffer); if (!strncmp(read_buffer, EXIT_STR, strlen(EXIT_STR))) { goto out; } } } out: if (close(epoll_fd) == -1) { perror("close"); exit(5); } return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ gcc epoll.c $ a.out 1 ready events File descriptor = 0 read_bytes = 1 read_buffer = abc 1 ready events File descriptor = 0 read_bytes = 4 read_buffer = abc exit 1 ready events File descriptor = 0 read_bytes = 5 read_buffer = exit |
select/pselect/poll/ppoll/epoll関数の違い
select/pselect/poll/ppoll/epoll関数の違いは下表になります.
項目 | select関数 | pselect関数 | poll関数 | ppoll関数 | epollファミリー関数 |
---|---|---|---|---|---|
管理するファイルディスクリプタの確認の計算量 | \(\mathcal{O}(n)\) | \(\mathcal{O}(n)\) | \(\mathcal{O}(n)\) | \(\mathcal{O}(n)\) | \(\mathcal{O}(n)\)※ |
ファイルディスクリプタ数の上限 | FD_SETSIZE (1,024の場合が多い) | FD_SETSIZE (1,024の場合が多い) | なし | なし | なし |
シグナルの捕捉 | なし | あり | なし | あり | あり |
※ready状態のファイルディスクリプタのみを通知して処理するので,他の関数よりも管理するファイルディスクリプタが少なくなり,処理時間が短くなりやすい.
まとめ
C言語のselect/pselect/poll/ppoll/epoll関数の違いを紹介します.
細かい違いですが,まずは概要を理解しましょう!
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!