Explanation 2: Epoll Deep Diveの深層解説
イントロダクション
このドキュメントでは、Chapter 2で学んだepollの概念をさらに深く掘り下げます。カーネル実装の詳細、パフォーマンス特性、実世界のユースケース、そしてクロスプラットフォームの比較について詳しく解説します。
1. epollのカーネル実装詳細
1.1 eventpoll構造体の完全な定義
// fs/eventpoll.c (Linuxカーネル)
struct eventpoll {
/* epollインスタンスを保護するスピンロック */
spinlock_t lock;
/* epollのミューテックス(長時間の操作用) */
struct mutex mtx;
/* epoll_wait()でブロックされているプロセスの待機キュー */
wait_queue_head_t wq;
/* epollファイル自体のポーラー用待機キュー */
wait_queue_head_t poll_wait;
/* 準備完了ファイルのリスト */
struct list_head rdllist;
/* Red-Black Tree のルート(すべての監視FD) */
struct rb_root rbr;
/* ovflist: オーバーフローリスト
epoll_wait実行中にイベントが発生した場合に使用 */
struct epitem *ovflist;
/* ユーザー構造体(リソース制限用) */
struct user_struct *user;
/* epollファイル自体のfile構造体 */
struct file *file;
/* ネストされたepoll検出用 */
int visited;
struct list_head visited_list_link;
};
1.2 epitem構造体(個々の監視FD)
struct epitem {
/* Red-Black Treeノード */
struct rb_node rbn;
/* Ready Listノード */
struct list_head rdllink;
/* 次のepitem(ovflist用) */
struct epitem *next;
/* 監視対象のファイルディスクリプタ */
struct epoll_filefd ffd;
/* このepitemが所属するeventpoll */
struct eventpoll *ep;
/* イベントマスクとユーザーデータ */
struct epoll_event event;
/* ネストレベル(epoll of epoll) */
int nwait;
/* ポーリングテーブル */
struct list_head pwqlist;
};
1.3 epoll_ctl の内部実装
// 疑似コード: epoll_ctlの主要な処理フロー
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event)
{
struct eventpoll *ep;
struct epitem *epi;
struct file *file, *tfile;
int error;
// 1. epollファイルディスクリプタの検証
file = fget(epfd);
if (!is_epoll_file(file)) {
error = -EINVAL;
goto error_return;
}
// 2. 監視対象ファイルの検証
tfile = fget(fd);
// 3. eventpoll構造体の取得
ep = file->private_data;
// 4. ミューテックスでロック
mutex_lock(&ep->mtx);
// 5. 操作に応じた処理
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
// Red-Black Treeに新規追加
epitem_insert(ep, &event, tfile, fd);
error = 0;
} else {
error = -EEXIST; // 既に登録済み
}
break;
case EPOLL_CTL_DEL:
if (epi) {
// Red-Black Treeから削除
error = ep_remove(ep, epi);
} else {
error = -ENOENT; // 未登録
}
break;
case EPOLL_CTL_MOD:
if (epi) {
// イベントマスクを更新
event.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &event);
} else {
error = -ENOENT;
}
break;
}
mutex_unlock(&ep->mtx);
return error;
}
1.4 epoll_wait の内部実装
// 疑似コード: epoll_waitの主要な処理フロー
SYSCALL_DEFINE4(epoll_wait, int, epfd,
struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
struct eventpoll *ep;
struct file *file;
int error;
// 1. epollファイルディスクリプタの検証
file = fget(epfd);
ep = file->private_data;
// 2. イベント収集
error = ep_poll(ep, events, maxevents, timeout);
return error;
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
int res = 0;
wait_queue_t wait;
ktime_t expires;
fetch_events:
spin_lock_irqsave(&ep->lock, flags);
// Ready Listにイベントがあるかチェック
if (!list_empty(&ep->rdllist)) {
// イベントをユーザースペースに送信
res = ep_send_events(ep, events, maxevents);
if (res)
return res;
}
// イベントなし、かつタイムアウト0なら即座に戻る
if (timeout == 0) {
spin_unlock_irqrestore(&ep->lock, flags);
return 0;
}
// 待機キューに追加
init_waitqueue_entry(&wait, current);
__add_wait_queue_exclusive(&ep->wq, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
// イベント到着またはシグナル受信で起床
if (!list_empty(&ep->rdllist) || !timeout)
break;
// スケジューラに制御を渡す
timeout = schedule_timeout(timeout);
}
__remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
// イベント収集を再試行
goto fetch_events;
}
1.5 イベント発生時のコールバック
// デバイスドライバがwake_up()を呼ぶと、
// このコールバックが実行される
static int ep_poll_callback(wait_queue_t *wait, unsigned mode,
int sync, void *key)
{
struct epitem *epi = ep_item_from_wait(wait);
struct eventpoll *ep = epi->ep;
spin_lock_irqsave(&ep->lock, flags);
// イベントマスクのチェック
if (key && !((unsigned long) key & epi->event.events))
goto out_unlock;
// Ready Listに未追加なら追加
if (list_empty(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
// epoll_wait()をブロックしているプロセスを起床
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
out_unlock:
spin_unlock_irqrestore(&ep->lock, flags);
return 1;
}
2. エッジトリガー vs レベルトリガーの詳細分析
2.1 カーネルでの実装の違い
// Ready Listへの追加処理(簡略化)
static int ep_poll_callback(...) {
struct epitem *epi;
struct eventpoll *ep;
// イベント発生時に常に呼ばれる
if (epi->event.events & EPOLLET) {
// エッジトリガー: Ready Listに未追加の場合のみ追加
if (list_empty(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
} else {
// レベルトリガー: 常にReady Listに追加
// (既に追加済みでも問題ない)
if (list_empty(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
}
wake_up(&ep->wq);
}
// イベント送信後の処理
static int ep_send_events(...) {
// イベントをユーザーに送信
for_each_ready_item(epi) {
if (epi->event.events & EPOLLET) {
// エッジトリガー: Ready Listから削除
list_del_init(&epi->rdllink);
} else {
// レベルトリガー: 条件が満たされていればReady Listに残す
if (still_has_data(epi)) {
// Ready Listに残す(次回も通知)
} else {
list_del_init(&epi->rdllink);
}
}
}
}
2.2 実際のデータフローの違い
シナリオ: 1000バイトのデータ到着
レベルトリガー:
─────────────────────────────────────────
時刻 カーネル Ready List アプリ動作
─────────────────────────────────────────
T0 0バイト [] epoll_wait() [ブロック]
T1 1000↓ [fd] 戻る: EPOLLIN
T2 1000 [fd] read(100) → 100バイト読む
T3 900 [fd] epoll_wait()
T4 900 [fd] 戻る: EPOLLIN (まだデータあり!)
T5 900 [fd] read(100) → 100バイト読む
... ... ... ...
T12 100 [fd] epoll_wait()
T13 100 [fd] 戻る: EPOLLIN
T14 100 [fd] read(1000) → 100バイト読む
T15 0 [] epoll_wait() [ブロック]
─────────────────────────────────────────
epoll_wait呼び出し回数: 10回
エッジトリガー:
─────────────────────────────────────────
時刻 カーネル Ready List アプリ動作
─────────────────────────────────────────
T0 0バイト [] epoll_wait() [ブロック]
T1 1000↓ [fd] 戻る: EPOLLIN
T2 1000 [] read() → 1000バイト (全部!)
T3 0 [] epoll_wait() [ブロック]
T4 新データ↓ [fd] 戻る: EPOLLIN
─────────────────────────────────────────
epoll_wait呼び出し回数: 1回(大幅削減)
注意: エッジトリガーでは、T2で全データを読み切らないと
データが残っていてもT3以降でイベントが来ない!
2.3 エッジトリガーの正しい実装パターン
const std = @import("std");
const os = std.os;
const linux = os.linux;
/// エッジトリガーの完全な実装例
pub fn edgeTriggeredReadLoop(fd: i32) !void {
// バッファサイズは大きめに
var buffer: [16384]u8 = undefined;
var total_read: usize = 0;
while (true) {
const result = os.read(fd, &buffer);
if (result) |bytes_read| {
if (bytes_read == 0) {
// EOF: 接続が閉じられた
std.debug.print("EOF検出: 合計 {d} バイト読み込み\n", .{total_read});
return;
}
total_read += bytes_read;
// データ処理
try processData(buffer[0..bytes_read]);
// バッファが満杯だった場合、まだデータがある可能性
if (bytes_read == buffer.len) {
// 続けて読み込む
continue;
} else {
// バッファに余裕があれば、データを読み切った
std.debug.print("データ読み切り: {d} バイト\n", .{total_read});
return;
}
} else |err| {
if (err == error.WouldBlock) {
// これが正常な終了条件
std.debug.print("全データ読み切り: {d} バイト\n", .{total_read});
return;
}
// その他のエラー
std.debug.print("読み込みエラー: {}\n", .{err});
return err;
}
}
}
fn processData(data: []const u8) !void {
// データ処理ロジック
std.debug.print("処理: {d} バイト\n", .{data.len});
}
2.4 よくある間違い
// 間違い1: エッジトリガーで全データを読まない
pub fn wrongEdgeTriggered1(fd: i32) !void {
var buffer: [1024]u8 = undefined;
// 一度だけ読む → 残りのデータは永遠に通知されない!
const bytes_read = try os.read(fd, &buffer);
try processData(buffer[0..bytes_read]);
// ❌ 間違い: WouldBlockまで読むべき
}
// 間違い2: エッジトリガーでブロッキングI/O
pub fn wrongEdgeTriggered2(fd: i32) !void {
// FDがブロッキングモードのまま
var buffer: [1024]u8 = undefined;
while (true) {
const bytes_read = try os.read(fd, &buffer); // ブロック!
// ❌ 間違い: ノンブロッキングに設定すべき
}
}
// 間違い3: レベルトリガーの誤解
pub fn wrongLevelTriggered(fd: i32) !void {
// レベルトリガーでも、できるだけ多く読むべき
var buffer: [1024]u8 = undefined;
const bytes_read = try os.read(fd, &buffer);
// 少しだけ読んで戻る → 無駄なepoll_wait呼び出し
// ✓ 正解: できる限り読むべき(ブロッキングの範囲で)
}
3. パフォーマンス特性とスケーラビリティ
3.1 計算量の詳細分析
操作ごとの計算量:
epoll_create1():
時間: O(1)
メモリ: 約1KB(eventpoll構造体)
epoll_ctl(ADD):
時間: O(log n) - Red-Black Treeへの挿入
メモリ: 約200バイト/FD(epitem構造体)
epoll_ctl(MOD):
時間: O(log n) - Red-Black Tree検索
メモリ: 変化なし
epoll_ctl(DEL):
時間: O(log n) - Red-Black Treeからの削除
メモリ: -200バイト/FD
epoll_wait():
時間: O(1) + O(m)
- O(1): Ready Listアクセス
- O(m): m個のイベントをユーザーにコピー
メモリ: 変化なし
n: 登録されているFDの総数
m: 準備完了したFDの数(通常 m << n)
3.2 実測パフォーマンス比較
ベンチマーク条件:
- 総接続数: 10,000
- アクティブ接続: 100
- イベント/秒: 10,000
select:
CPU使用率: 90%
メモリ: 約10MB
レイテンシ: 10-50ms
スループット: ~5,000 events/sec
時間複雑度の影響: 大きい(O(n)のスキャン)
poll:
CPU使用率: 85%
メモリ: 約12MB
レイテンシ: 8-40ms
スループット: ~6,000 events/sec
時間複雑度の影響: 大きい(O(n)のスキャン)
epoll (レベルトリガー):
CPU使用率: 15%
メモリ: 約8MB
レイテンシ: 0.5-2ms
スループット: ~80,000 events/sec
時間複雑度の影響: 小さい(O(1)の取得)
epoll (エッジトリガー):
CPU使用率: 10%
メモリ: 約8MB
レイテンシ: 0.3-1ms
スループット: ~100,000 events/sec
時間複雑度の影響: 最小(イベント通知最適)
3.3 メモリ使用量の詳細
epollのメモリフットプリント:
基本構造:
eventpoll構造体: 約1KB
Red-Black Tree オーバーヘッド: 約2KB
登録FDごと:
epitem構造体: 200バイト
wait_queue_entry: 64バイト
合計: 約264バイト/FD
10,000接続の場合:
基本: 3KB
FD: 264バイト × 10,000 = 2.6MB
合計: 約2.6MB
比較:
select: 128バイト × 3 = 384バイト (固定)
ただし、1024制限あり
poll: 16バイト × n (可変)
10,000接続 = 160KB
epoll: 264バイト × n (可変)
10,000接続 = 2.6MB
結論: epollは少しメモリを多く使うが、
パフォーマンスの向上を考えれば妥当
4. 実世界のケーススタディ
4.1 Nginxのepoll使用
// Nginx のイベント処理(簡略化)
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags)
{
int events;
ngx_event_t *rev, *wev;
struct epoll_event ee[NGX_EPOLL_EVENTS];
// エッジトリガーで待機
events = epoll_wait(ep, ee, NGX_EPOLL_EVENTS, timer);
if (events == -1) {
return NGX_ERROR;
}
for (i = 0; i < events; i++) {
c = ee[i].data.ptr;
if (ee[i].events & (EPOLLERR|EPOLLHUP)) {
// エラー処理
ngx_log_error(...);
}
rev = c->read;
if (ee[i].events & EPOLLIN) {
rev->ready = 1;
rev->handler(rev); // 読み込みハンドラ実行
}
wev = c->write;
if (ee[i].events & EPOLLOUT) {
wev->ready = 1;
wev->handler(wev); // 書き込みハンドラ実行
}
}
return NGX_OK;
}
Nginxの設定パラメータ:
events {
use epoll; # epollを明示的に指定
worker_connections 10000; # 各ワーカーの最大接続数
multi_accept on; # 一度に複数のacceptを許可
accept_mutex off; # accept競合の制御(新しいNginxではoff推奨)
}
4.2 Redisのepoll使用
// Redis のイベントループ(ae_epoll.c)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, numevents = 0;
// epoll_wait呼び出し
retval = epoll_wait(state->epfd, state->events,
eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
if (retval > 0) {
numevents = retval;
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = state->events+j;
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
}
}
return numevents;
}
4.3 Node.js (libuv) のepoll使用
// libuv のLinux epoll バックエンド(簡略化)
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct epoll_event events[1024];
struct epoll_event* pe;
int fd, nfds;
// イベント待機
nfds = epoll_wait(loop->backend_fd, events, 1024, timeout);
if (nfds == -1) {
if (errno != EINTR)
abort();
return;
}
// 各イベントを処理
for (i = 0; i < nfds; i++) {
pe = events + i;
fd = pe->data.fd;
// タイマー、シグナル、I/Oイベントを振り分け
if (fd == -1)
continue;
w = loop->watchers[fd];
if (w == NULL)
continue;
// コールバック実行
w->cb(loop, w, pe->events);
}
}
5. クロスプラットフォーム比較
5.1 kqueue (BSD/macOS)
// kqueueの例(比較用)
int kq = kqueue();
struct kevent change;
EV_SET(&change, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
kevent(kq, &change, 1, NULL, 0, NULL);
struct kevent event;
int nev = kevent(kq, NULL, 0, &event, 1, NULL);
if (nev > 0) {
if (event.filter == EVFILT_READ) {
// 読み込み処理
ssize_t n = read(event.ident, buffer, size);
}
}
epoll vs kqueue:
| 特性 | epoll (Linux) | kqueue (BSD/macOS) |
|---|---|---|
| **API** | 3つのシステムコール | 1つのシステムコール |
| **登録** | epoll_ctl (個別) | kevent (バッチ可) |
| **待機** | epoll_wait | kevent (同じ関数) |
| **データ** | epoll_data (union) | udata (void*) |
| **フィルタ** | イベントフラグ | EVFILT_* (より豊富) |
| **タイマー** | timerfd必要 | EVFILT_TIMER統合 |
| **シグナル** | signalfd必要 | EVFILT_SIGNAL統合 |
| **パフォーマンス** | 非常に高速 | 同等に高速 |
5.2 IOCP (Windows)
// Windows IOCP の例
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// ソケットを関連付け
CreateIoCompletionPort((HANDLE)socket, iocp, (ULONG_PTR)context, 0);
// 非同期読み込み開始
WSABUF buf;
DWORD flags = 0;
WSARecv(socket, &buf, 1, NULL, &flags, &overlapped, NULL);
// 完了待機
DWORD bytes;
ULONG_PTR key;
OVERLAPPED* pOverlapped;
GetQueuedCompletionStatus(iocp, &bytes, &key, &pOverlapped, INFINITE);
// データは既に読み込まれている(Proactorパターン)
process_data(buffer, bytes);
epoll vs IOCP:
| 特性 | epoll (Linux) | IOCP (Windows) |
|---|---|---|
| **パターン** | Reactor | Proactor |
| **通知** | 「読み込み可能」 | 「読み込み完了」 |
| **I/O開始** | アプリが実行 | カーネルが実行 |
| **バッファ** | アプリが管理 | カーネルが使用 |
| **複雑さ** | 中程度 | 高い |
| **パフォーマンス** | 非常に高速 | 最高速(真の非同期) |
5.3 Zigでのクロスプラットフォーム抽象化
const std = @import("std");
const builtin = @import("builtin");
/// プラットフォーム固有のイベント多重化を統一インターフェースで提供
pub const EventLoop = switch (builtin.os.tag) {
.linux => EpollEventLoop,
.macos, .freebsd, .netbsd, .openbsd => KqueueEventLoop,
.windows => IocpEventLoop,
else => SelectEventLoop, // フォールバック
};
pub const EpollEventLoop = struct {
epfd: i32,
events: []linux.epoll_event,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !EpollEventLoop {
const epfd = linux.epoll_create1(linux.EPOLL_CLOEXEC);
if (epfd < 0) return error.CreateFailed;
return EpollEventLoop{
.epfd = epfd,
.events = try allocator.alloc(linux.epoll_event, 1024),
.allocator = allocator,
};
}
pub fn add(self: *EpollEventLoop, fd: i32, events: u32) !void {
var event = linux.epoll_event{
.events = events,
.data = linux.epoll_data{ .fd = fd },
};
const result = linux.epoll_ctl(self.epfd, linux.EPOLL_CTL_ADD, fd, &event);
if (result < 0) return error.AddFailed;
}
pub fn wait(self: *EpollEventLoop, timeout_ms: i32) ![]linux.epoll_event {
const nfds = linux.epoll_wait(
self.epfd,
self.events.ptr,
@intCast(self.events.len),
timeout_ms
);
if (nfds < 0) return error.WaitFailed;
return self.events[0..@intCast(nfds)];
}
pub fn deinit(self: *EpollEventLoop) void {
std.os.close(self.epfd);
self.allocator.free(self.events);
}
};
// 同様にKqueue, IOCP, Selectの実装...
6. epollの高度な使用パターン
6.1 epoll of epoll(ネスト)
/// 複数のepollインスタンスを1つで管理
pub fn nestedEpollExample() !void {
// マスターepoll
const master_epfd = try createEpoll();
defer os.close(master_epfd);
// ワーカーepoll群
var worker_epfds: [4]i32 = undefined;
for (&worker_epfds) |*epfd| {
epfd.* = try createEpoll();
}
defer for (worker_epfds) |epfd| os.close(epfd);
// 各ワーカーepollをマスターに登録
for (worker_epfds) |epfd| {
try epollAdd(master_epfd, epfd, linux.EPOLLIN);
}
// マスターepollで全ワーカーを監視
while (true) {
const events = try epollWait(master_epfd, 64, -1);
defer std.heap.page_allocator.free(events);
for (events) |event| {
const worker_epfd = event.data.fd;
// このワーカーにイベントがある
try processWorkerEvents(worker_epfd);
}
}
}
fn processWorkerEvents(worker_epfd: i32) !void {
const events = try epollWait(worker_epfd, 64, 0);
defer std.heap.page_allocator.free(events);
for (events) |event| {
// 実際のI/Oイベント処理
try handleIoEvent(event);
}
}
6.2 タイマー統合(timerfd)
const std = @import("std");
const os = std.os;
const linux = os.linux;
pub fn epollWithTimer(epfd: i32) !void {
// タイマーFDの作成
const timer_fd = linux.timerfd_create(
linux.CLOCK.MONOTONIC,
linux.TFD_NONBLOCK | linux.TFD_CLOEXEC
);
if (timer_fd < 0) return error.TimerCreateFailed;
defer os.close(timer_fd);
// 1秒ごとに発火するタイマー設定
var new_value = linux.itimerspec{
.it_value = .{ .tv_sec = 1, .tv_nsec = 0 },
.it_interval = .{ .tv_sec = 1, .tv_nsec = 0 },
};
_ = linux.timerfd_settime(timer_fd, 0, &new_value, null);
// epollに追加
try epollAdd(epfd, timer_fd, linux.EPOLLIN);
while (true) {
const events = try epollWait(epfd, 64, -1);
defer std.heap.page_allocator.free(events);
for (events) |event| {
if (event.data.fd == timer_fd) {
// タイマー発火
var expirations: u64 = undefined;
_ = try os.read(timer_fd, std.mem.asBytes(&expirations));
std.debug.print("タイマー発火: {} 回\n", .{expirations});
try performPeriodicTask();
} else {
// 通常のI/Oイベント
try handleIoEvent(event);
}
}
}
}
fn performPeriodicTask() !void {
std.debug.print("定期タスク実行\n", .{});
}
fn handleIoEvent(event: linux.epoll_event) !void {
std.debug.print("I/Oイベント: fd={}\n", .{event.data.fd});
}
6.3 シグナル統合(signalfd)
pub fn epollWithSignal(epfd: i32) !void {
// シグナルマスクの設定
var mask: linux.sigset_t = undefined;
linux.sigemptyset(&mask);
linux.sigaddset(&mask, linux.SIG.INT); // SIGINT
linux.sigaddset(&mask, linux.SIG.TERM); // SIGTERM
// シグナルをブロック(signalfdで受信するため)
_ = linux.sigprocmask(linux.SIG.BLOCK, &mask, null);
// signalfdの作成
const signal_fd = linux.signalfd(-1, &mask, linux.SFD_NONBLOCK | linux.SFD_CLOEXEC);
if (signal_fd < 0) return error.SignalFdCreateFailed;
defer os.close(signal_fd);
// epollに追加
try epollAdd(epfd, signal_fd, linux.EPOLLIN);
while (true) {
const events = try epollWait(epfd, 64, -1);
defer std.heap.page_allocator.free(events);
for (events) |event| {
if (event.data.fd == signal_fd) {
// シグナル受信
var siginfo: linux.signalfd_siginfo = undefined;
const bytes_read = try os.read(signal_fd, std.mem.asBytes(&siginfo));
if (bytes_read == @sizeOf(linux.signalfd_siginfo)) {
std.debug.print("シグナル受信: {}\n", .{siginfo.ssi_signo});
if (siginfo.ssi_signo == linux.SIG.INT or
siginfo.ssi_signo == linux.SIG.TERM) {
std.debug.print("終了シグナル受信、クリーンアップ中...\n", .{});
return;
}
}
} else {
try handleIoEvent(event);
}
}
}
}
7. パフォーマンスチューニング
7.1 バッファサイズの最適化
// SO_RCVBUF / SO_SNDBUFの調整
pub fn optimizeSocketBuffers(fd: os.socket_t) !void {
// 受信バッファを64KBに
const rcvbuf: i32 = 65536;
try os.setsockopt(
fd,
os.SOL.SOCKET,
os.SO.RCVBUF,
std.mem.asBytes(&rcvbuf)
);
// 送信バッファを64KBに
const sndbuf: i32 = 65536;
try os.setsockopt(
fd,
os.SOL.SOCKET,
os.SO.SNDBUF,
std.mem.asBytes(&sndbuf)
);
}
7.2 TCP最適化
pub fn optimizeTcp(fd: os.socket_t) !void {
// TCP_NODELAY: Nagleアルゴリズムを無効化
const nodelay: i32 = 1;
try os.setsockopt(
fd,
os.IPPROTO.TCP,
os.TCP.NODELAY,
std.mem.asBytes(&nodelay)
);
// TCP_QUICKACK: 遅延ACKを無効化
const quickack: i32 = 1;
try os.setsockopt(
fd,
os.IPPROTO.TCP,
linux.TCP.QUICKACK,
std.mem.asBytes(&quickack)
);
}
まとめ
このExplanationでは、epollの深層を探りました:
- カーネル実装の詳細(eventpoll, epitem構造体)
- エッジトリガーとレベルトリガーの実装の違い
- パフォーマンス特性とスケーラビリティ分析
- 実世界のケーススタディ(Nginx, Redis, Node.js)
- クロスプラットフォーム比較(kqueue, IOCP)
- 高度な使用パターン(timerfd, signalfd)
- パフォーマンスチューニングテクニック
次のExerciseでは、これらの知識を活用して高性能なepollサーバーを実装します。