20
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

select(2)のfd_setは1024以上のfdをセットしようとすると落ちる

Last updated at Posted at 2017-06-24

はじめに

ネット上の記述によってはselectは引数のfd_setが1024個のfdしか受け付けないから、1024個までのFDしか監視できないよ、と書いてあることが多いんですが、実際には 1024以上のfdを受け付けません。たとえ一個であれ1024以上の数字をFD_SETしようとすると落ちます。
結論は以上ですが、中身を追ってみたいと思います。

よく見るとselectのmanページにも載ってます。

fd_set は固定サイズのバッファーである。 負や FD_SETSIZE 以上の値を持つ fd に対して FD_CLR() や FD_SET() を実行した場合、 どのような動作をするかは定義されていない。

恐怖の未定義動作宣言がされています。

select(2)

ファイルディスクリプタやソケットディスクリプタを監視し、典型的にはサーバプログラムなどいつ着信があるかわからないプログラムが、着信を待ち受けるのによく使われるシステムコールです。

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

細かいことを置いておいて、大雑把に言ってしまうと、fd_setに登録されたfd(ファイルディスクリプタ)を監視してくれるシステムコールです。select(2)からread(2)を連携させることで、readをビジーwaitするより効率よくイベント駆動的にデータの読み込みができます。boost::asio::async_read の元祖みたいなやつですね。

fd_setとFD_SET

fd_setは名前の通りfdの集合を表す構造体です。私の手元のsys/select.hを見ると(かなり省略してます)

typedef long int __fd_mask;
# define __FD_SETSIZE 1024
# define __NFDBITS (8 * (int)sizeof(__fd_mask));

typedef struct
  {
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
  } fd_set;

読み解いて行くと、Linuxの64bit版だとすると、longは64bitなので、

typedef struct {
    long int fds_bits[16];
} fd_set;

ですので、結局、1024bit(64bit * 16個)の領域をもつ構造体になります。
そして、ここに含まれるfdを設定するのがFD_SETマクロです。

#define	__FD_ELT(d) ((d) / __NFDBITS)
#define	__FD_MASK(d) ((__fd_mask) (1UL << ((d) % __NFDBITS)))
#define __FD_SET(d, set) ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
#define	FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)

ちょっとわかりにくいのでマクロを解いていくと、

fdsetp->fds_bits[fd/64] |= (__fd_mask) (1UL << (fd % 64));

となり、要するにfdsetp(fd_set型変数のポインタ)のfd桁目のbitを立てる処理をしているわけです。

お分かりでしょうか?

そうです。fdが1024を超えた瞬間、buffer overflowで落ちます。
しかも、FD_SETSIZEはdefineでハードコードされており変更するのも恐ろしくてできません。redhatのQAページでもFD_SETSIZEを書き換えちゃダメだよと書いてあります。
ulimitの上限は今どきのメジャーディストリビューションでも1024が多いですが、ulimitで上限外すって言うのはサーバプログラム書く方からすると当たり前のチューニングだと思います。特に、dockerとか使っているとオプションで簡単に制限を外せます。
しかし、もしあなたのプログラムで使っているライブラリがselect(2)で実装されていたら、予期せず落ちることになります!

まとめ

select(2)を使ったプログラムでulimitの制限を外すぐらいディスクリプタを使うと、FD_SETで落ちます。
実際、某NWプロトコルのライブラリを使ってプログラムを書いてたらこの問題に遭遇しました。
これから低レイヤのNWプログラム書く方はselect(2)を使わないようにしてほしいものです。
お願いだからpoll(2)を使ってくれ!

20
11
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?