select
selectはpollと同じ機能です。pollと異なりselectするファイルIDをビットで管理します。セットする領域をlong配列とし、ファイルID番号が配列0を起点とするlongビットサイズに応じた配列のビット位置になります。従って配列0の0ビットから、selectするファイル数まで順に走査することになります。
selectできるFILE ID数は、デフォルトで1024で、これはコンパイル時__FD_SETSIZEを定義することで、任意に拡張できます。デフォルトでlongが64ビットなら、fds_bits[1024/8*8=16]です。__FD_SETSIZEはカーネルマターで、ユーザサイドのFD_SET()関数はシステムコールでなく、gccマターでFD_SET()の引数の上限のチェックは行なわれません。
なお、走査するファイル数が、2048を超えるカーネル下では、バイト単位のファイル数の6倍(in/out/ex/res_in/res_out/res_exの各領域分)したサイズでkmalloc()で動的に取得することになります。
ファイルIDのpollオペレーションコールバックでファイル状態変化を取得し、それがPOLLIN_SET/POLLOUT_SET/POLLEX_SETに応じて、結果としてそのファイルIDをfds->res_in/fds->res_out/fds->res_exにセットし、retval++を状態変化したファイル数でシステムコールの返り値とします。なお、状態変化取得時時、wait = NULLとすることで、pollオペレーションコールバックではwaitキューにリストされません。
一連のチェックを終えると、if (retval || timed_out || signal_pending(current))で、状態変化/タイムアウト/シグナル受信なら、復帰しそうでないなら、poll_schedule_timeout()を介してループし続けます。
ファイルIDがfd_set_bitsのビット位置となるため、selectするファイルとしないファイルの混在する実装では、selectするファイルは最初にオープンすべきのようです。
selectできるFILE ID数は、デフォルトで1024で、これはコンパイル時__FD_SETSIZEを定義することで、任意に拡張できます。デフォルトでlongが64ビットなら、fds_bits[1024/8*8=16]です。__FD_SETSIZEはカーネルマターで、ユーザサイドのFD_SET()関数はシステムコールでなく、gccマターでFD_SET()の引数の上限のチェックは行なわれません。
#undef __NFDBITS #define __NFDBITS (8 * sizeof(unsigned long)) #undef __FD_SETSIZE #define __FD_SETSIZE 1024 #undef __FDSET_LONGS #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) typedef struct { unsigned long fds_bits [__FDSET_LONGS]; } __kernel_fd_set; typedef __kernel_fd_set fd_set; #define FD_SET(fd,fdsetp) __FD_SET(fd,fdsetp) static inline void __FD_SET(unsigned long __fd, __kernel_fd_set *__fdsetp) { unsigned long __tmp = __fd / __NFDBITS; unsigned long __rem = __fd % __NFDBITS; __fdsetp->fds_bits[__tmp] |= (1UL<<__rem); }fd_setのinp/outp/expにファイル状態の入力/出力/例外のselectするファイルIDを、nはこの3つの中でファイル数の一番大きいファイル数が設定されます。
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp, fd_set __user *, exp, struct timeval __user *, tvp) { struct timespec end_time, *to = NULL; struct timeval tv; int ret; if (tvp) { if (copy_from_user(&tv, tvp, sizeof(tv))) return -EFAULT; to = &end_time; if (poll_select_set_timeout(to, tv.tv_sec + (tv.tv_usec / USEC_PER_SEC), (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) return -EINVAL; } ret = core_sys_select(n, inp, outp, exp, to); ret = poll_select_copy_remaining(&end_time, tvp, 1, ret); return ret; }ユーザ空間引数のfd_set inp/outp/expをカーネル空間のlong stack_fds[SELECT_STACK_ALLOC/sizeof(long)]に設定します。SELECT_STACK_ALLOC=256バイトの最大ファイル数256×8=2048とするstack_fds[]の配列となります。
なお、走査するファイル数が、2048を超えるカーネル下では、バイト単位のファイル数の6倍(in/out/ex/res_in/res_out/res_exの各領域分)したサイズでkmalloc()で動的に取得することになります。
#define MAX_STACK_ALLOC 832 #define FRONTEND_STACK_ALLOC 256 #define SELECT_STACK_ALLOC FRONTEND_STACK_ALLOC typedef struct { unsigned long *in, *out, *ex; unsigned long *res_in, *res_out, *res_ex; } fd_set_bits; #define FDS_BITPERLONG (8*sizeof(long)) #define FDS_LONGS(nr) (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG) #define FDS_BYTES(nr) (FDS_LONGS(nr)*sizeof(long)) int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec *end_time) { fd_set_bits fds; void *bits; int ret, max_fds; unsigned int size; struct fdtable *fdt; long stack_fds[SELECT_STACK_ALLOC/sizeof(long)]; ret = -EINVAL; if (n < 0) goto out_nofds; rcu_read_lock(); fdt = files_fdtable(current->files); max_fds = fdt->max_fds; rcu_read_unlock(); if (n > max_fds) n = max_fds; size = FDS_BYTES(n); bits = stack_fds; if (size > sizeof(stack_fds) / 6) { ret = -ENOMEM; bits = kmalloc(6 * size, GFP_KERNEL); if (!bits) goto out_nofds; } fds.in = bits; fds.out = bits + size; fds.ex = bits + 2*size; fds.res_in = bits + 3*size; fds.res_out = bits + 4*size; fds.res_ex = bits + 5*size; if ((ret = get_fd_set(n, inp, fds.in)) || (ret = get_fd_set(n, outp, fds.out)) || (ret = get_fd_set(n, exp, fds.ex))) goto out; zero_fd_set(n, fds.res_in); zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_ex); ret = do_select(n, &fds, end_time); if (ret < 0) goto out; if (!ret) { ret = -ERESTARTNOHAND; if (signal_pending(current)) goto out; ret = 0; } if (set_fd_set(n, inp, fds.res_in) || set_fd_set(n, outp, fds.res_out) || set_fd_set(n, exp, fds.res_ex)) ret = -EFAULT; out: if (bits != stack_fds) kfree(bits); out_nofds: return ret; }for (i = 0; i < n; ++rinp, ++routp, ++rexp)で、ファイルIDの最大値に対応するfds_bits[]インデックスのnまで、long単位のfor (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1)でビット単位で走査するファイルIDを取得します。
ファイルIDのpollオペレーションコールバックでファイル状態変化を取得し、それがPOLLIN_SET/POLLOUT_SET/POLLEX_SETに応じて、結果としてそのファイルIDをfds->res_in/fds->res_out/fds->res_exにセットし、retval++を状態変化したファイル数でシステムコールの返り値とします。なお、状態変化取得時時、wait = NULLとすることで、pollオペレーションコールバックではwaitキューにリストされません。
一連のチェックを終えると、if (retval || timed_out || signal_pending(current))で、状態変化/タイムアウト/シグナル受信なら、復帰しそうでないなら、poll_schedule_timeout()を介してループし続けます。
int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; poll_table *wait; int retval, i, timed_out = 0; unsigned long slack = 0; rcu_read_lock(); retval = max_select_fd(n, fds); rcu_read_unlock(); if (retval < 0) return retval; n = retval; poll_initwait(&table); wait = &table.pt; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; } if (end_time && !timed_out) slack = select_estimate_accuracy(end_time); retval = 0; for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; inp = fds->in; outp = fds->out; exp = fds->ex; rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex; for (i = 0; i < n; ++rinp, ++routp, ++rexp) { unsigned long in, out, ex, all_bits, bit = 1, mask, j; unsigned long res_in = 0, res_out = 0, res_ex = 0; const struct file_operations *f_op = NULL; struct file *file = NULL; in = *inp++; out = *outp++; ex = *exp++; all_bits = in | out | ex; if (all_bits == 0) { i += __NFDBITS; continue; } for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { int fput_needed; if (i >= n) break; if (!(bit & all_bits)) continue; file = fget_light(i, &fput_needed); if (file) { f_op = file->f_op; mask = DEFAULT_POLLMASK; if (f_op && f_op->poll) { wait_key_set(wait, in, out, bit); mask = (*f_op->poll)(file, wait); } fput_light(file, fput_needed); if ((mask & POLLIN_SET) && (in & bit)) { res_in |= bit; retval++; wait = NULL; } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; retval++; wait = NULL; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; wait = NULL; } } } if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; cond_resched(); } wait = NULL; if (retval || timed_out || signal_pending(current)) break; if (table.error) { retval = table.error; break; } if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } poll_freewait(&table); return retval; }
備考
シグナルでselectを脱する時、返値は状態変化したファイルID数で、pollと異なりEINTとなりません。呼び出し元のcore_sys_selec()では、do_select()でファイル状態変化なしでシグナルにより復帰した時(ret=0)は、ret = -ERESTARTNOHANDとしていますが、一つでもファイル状態変化があってシグナルで復帰すれば、その数となります。ファイルIDがfd_set_bitsのビット位置となるため、selectするファイルとしないファイルの混在する実装では、selectするファイルは最初にオープンすべきのようです。