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するファイルは最初にオープンすべきのようです。






