poll
timeout_msecsが設定されていれば、poll_select_set_timeout()でカレント時間に終了時間timeout_msecsを加算した終了時刻をstruct timespecに設定し、do_sys_poll()でfileIDの状態変化およびシグナルが発生するまでウエイトします。timeout_msecsがマイナスならto=NULLで、タイムアウトしません。
シグナルが発生したら-EINTR、タイムアウトなら0、fileID状態変化なら状態変化したfileID数がretになります。
ret == -EINTRなら、current_thread_info()->restart_blockにpoll引数を、そしてrestart_block->fn = do_restart_pollとし、ret = -ERESTART_RESTARTBLOCKとし、条件がととのへば、ユーザプロセスへの復帰時、restart_syscallシステムコールをコールし、do_restart_poll()がコールされ残った時間分pollします。
poll_initwait()でpoll_wqueues tableを初期化します。poll_wqueues tableは、do_poll()で各FILEオペレーションコールバックpollの引数となり、ウエイトするFILE IDがリストされます。
poll_list->nextの順に、そのentries[]のFILE IDをpfdに設定し、do_pollfd()でpollします。状態変化があればそのevent IDが返ってきて、ブレイクせず残りのFILE IDをチェックし続けます。pt = NULLとする事で、以降のFILE IDは状態変化がなくてもウエイトリストされず、任意1つ状態変化があれば必ず復帰する事になります。countは返り値で、プラスの場合、pollシステムコールの返値は状態変化のFILEの数ということです。(通常は1ですが。沢山ソケットのようなpollでは、1以上の結果となる事が想定されます。)
全FILE IDに状態変化がなければ、signal_pending()でシグナルをチェックし、受信していれば返り値が-EINTRとなります。以降poll_schedule_timeout()を介してループし、返り値が0ならslackと時間となり、timed_out = 1でcount = 0でループをブレイクします。
シグナルが発生したら-EINTR、タイムアウトなら0、fileID状態変化なら状態変化したfileID数がretになります。
ret == -EINTRなら、current_thread_info()->restart_blockにpoll引数を、そしてrestart_block->fn = do_restart_pollとし、ret = -ERESTART_RESTARTBLOCKとし、条件がととのへば、ユーザプロセスへの復帰時、restart_syscallシステムコールをコールし、do_restart_poll()がコールされ残った時間分pollします。
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs) { struct timespec end_time, *to = NULL; int ret; if (timeout_msecs >= 0) { to = &end_time; poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC, NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC)); } ret = do_sys_poll(ufds, nfds, to); if (ret == -EINTR) { struct restart_block *restart_block; restart_block = ¤t_thread_info()->restart_block; restart_block->fn = do_restart_poll; restart_block->poll.ufds = ufds; restart_block->poll.nfds = nfds; if (timeout_msecs >= 0) { restart_block->poll.tv_sec = end_time.tv_sec; restart_block->poll.tv_nsec = end_time.tv_nsec; restart_block->poll.has_timeout = 1; } else restart_block->poll.has_timeout = 0; ret = -ERESTART_RESTARTBLOCK; } return ret; }long stack_pps[POLL_STACK_ALLOC/sizeof(long)]をメモリとするstruct poll_list *const headのentries[]にユーザパラメータのstruct pollfd __user *ufdsを複写します。entries[]の配列はN_STACK_PPSとなります。もしnfdsがN_STACK_PPSより多ければ、kmalloc()で動的にstruct poll_listのメモリを確保し、先のpoll_list->nextにリストし、そのentries[]にstruct pollfd __user *ufdsをセットします。kmalloc()での実装の負荷を軽減するべく、しかも余分な静的メモリ消費を防ぐための実装で、pollの本質と関係ありません。
poll_initwait()でpoll_wqueues tableを初期化します。poll_wqueues tableは、do_poll()で各FILEオペレーションコールバックpollの引数となり、ウエイトするFILE IDがリストされます。
#define FRONTEND_STACK_ALLOC 256 #define SELECT_STACK_ALLOC FRONTEND_STACK_ALLOC #define POLL_STACK_ALLOC FRONTEND_STACK_ALLOC #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \ sizeof(struct pollfd)) struct poll_list { struct poll_list *next; int len; struct pollfd entries[0]; }; int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec *end_time) { struct poll_wqueues table; int err = -EFAULT, fdcount, len, size; long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; struct poll_list *const head = (struct poll_list *)stack_pps; struct poll_list *walk = head; unsigned long todo = nfds; if (nfds > rlimit(RLIMIT_NOFILE)) return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS); for (;;) { walk->next = NULL; walk->len = len; if (!len) break; if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len)) goto out_fds; todo -= walk->len; if (!todo) break; len = min(todo, POLLFD_PER_PAGE); size = sizeof(struct poll_list) + sizeof(struct pollfd) * len; walk = walk->next = kmalloc(size, GFP_KERNEL); if (!walk) { err = -ENOMEM; goto out_fds; } } poll_initwait(&table); fdcount = do_poll(nfds, head, &table, end_time); poll_freewait(&table); for (walk = head; walk; walk = walk->next) { struct pollfd *fds = walk->entries; int j; for (j = 0; j < walk->len; j++, ufds++) if (__put_user(fds[j].revents, &ufds->revents)) goto out_fds; } err = fdcount; out_fds: walk = head->next; while (walk) { struct poll_list *pos = walk; walk = walk->next; kfree(pos); } return err; }最初のifはウエイトしないケースでpt = NULLは、ファイルオペレーションpollコールバックで、ウエイトキューにリストされないという事です。次のifはウエイトするケースでstackがウエイトタイムです。
poll_list->nextの順に、そのentries[]のFILE IDをpfdに設定し、do_pollfd()でpollします。状態変化があればそのevent IDが返ってきて、ブレイクせず残りのFILE IDをチェックし続けます。pt = NULLとする事で、以降のFILE IDは状態変化がなくてもウエイトリストされず、任意1つ状態変化があれば必ず復帰する事になります。countは返り値で、プラスの場合、pollシステムコールの返値は状態変化のFILEの数ということです。(通常は1ですが。沢山ソケットのようなpollでは、1以上の結果となる事が想定されます。)
全FILE IDに状態変化がなければ、signal_pending()でシグナルをチェックし、受信していれば返り値が-EINTRとなります。以降poll_schedule_timeout()を介してループし、返り値が0ならslackと時間となり、timed_out = 1でcount = 0でループをブレイクします。
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, struct timespec *end_time) { poll_table* pt = &wait->pt; ktime_t expire, *to = NULL; int timed_out = 0, count = 0; unsigned long slack = 0; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { pt = NULL; timed_out = 1; } if (end_time && !timed_out) slack = select_estimate_accuracy(end_time); for (;;) { struct poll_list *walk; for (walk = list; walk != NULL; walk = walk->next) { struct pollfd * pfd, * pfd_end; pfd = walk->entries; pfd_end = pfd + walk->len; for (; pfd != pfd_end; pfd++) { if (do_pollfd(pfd, pt)) { count++; pt = NULL; } } } pt = NULL; if (!count) { count = wait->error; if (signal_pending(current)) count = -EINTR; } if (count || timed_out) break; if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } return count; }
追記
シグナルでpollを脱しても、システムコール再実行で、タイムアウトまでpollし続けるとの理解ですが、以下の検証では、そのような結果となりません。シグナルマターかと思いますが、頃合いを見計らって検証したいと思っていますが・・・。ご指摘あれば幸いです。#include <sys/syscall.h> #include <stdio.h> #include <errno.h> #include <poll.h> #include <signal.h> void do_poll(); void poll_stdin(void); static int select_stdin(void); int main() { int p_id; if ((p_id = fork()) == 0) { do_poll(); } else { sleep(1); kill(p_id, SIGUSR1); wait(NULL); } return 0; } void sig_hand(int sig) { char *msg = "signal\n"; write(1, msg, 8); } void do_poll() { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_handler = sig_hand; act.sa_flags = SA_RESTART; sigaction(SIGUSR1, &act, NULL); poll_stdin(); } void poll_stdin(void) { int rc = 0; struct pollfd fds[1]; fds[0].fd = 0; fds[0].events = POLLIN; fds[0].revents = 0; rc = syscall(SYS_poll, fds, 1, 10000); printf("after poll:%d ", rc); if(rc < 0){ printf("%s\n", strerror(errno)); } if(rc == 0){ printf("timeout\n"); } }結果
[root@localhost kitamura]# ./a.out signal after poll:-1 Interrupted system call下記の結果を想定していたのですが・・・
signal after poll:0 timeout