Linuxシステムの下でfd分配の方法

14320 ワード

ここ数日、会社でネット通信のコードを書くことが多く、IOイベントのモニタリング方法の問題に触れるのは自然です.私はselect輪訓の方法がそこで意外にも大いに行われていることに驚いた.Linuxシステムでもwindowsシステムでもselectは廃棄されるべきだと彼らに言った.その理由は、2つのプラットフォームでselectのシステム呼び出しに致命的な穴があるからだ.
Windows上で単一fd_setに格納されているsocket handle個数はFD_を超えてはならないSETSIZE(win 32 winsock 2.hでは64と定義されており、VS 2010バージョンに準じて)、fd_set構造は、これらのsocket handleを格納するために配列を使用し、FD_SETマクロはこの配列にsocket handleを入れ、その過程でFD_を超えてはいけないことを限定している.SETSIZE,具体的にはwinsock 2をご自身でご覧ください.h中FD_SETマクロの定義.ここでの問題は
自分ならfd_setのsocket handleはFD_に達しましたSETSIZE個、それでは後続のFD_SET操作は実際には効果がなく、socket handleに対応するIOイベントが漏れてしまいます!!!Linuxシステムの下では、この問題もfd_にある.setの構造とFD_SETマクロ上.このときfd_set構造はbitビットシーケンスを用いて各検出対象IOイベントのfdを記録する.記録の仕方は少し複雑で、以下の通りです.
/usr/include/sys/select.h中
 1 typedef long int __fd_mask;

 2 #define __NFDBITS    (8 * sizeof (__fd_mask))

 3 #define    __FDELT(d)    ((d) / __NFDBITS)

 4 

 5 #define    __FDMASK(d)    ((__fd_mask) 1 << ((d) % __NFDBITS))

 6 

 7 typedef struct

 8   {

 9     /* XPG4.2 requires this member name.  Otherwise avoid the name

10        from the global namespace.  */

11 #ifdef __USE_XOPEN

12     __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];

13 # define __FDS_BITS(set) ((set)->fds_bits)

14 #else

15     __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];

16 # define __FDS_BITS(set) ((set)->__fds_bits)

17 #endif

18   } fd_set;

19 

20 #define    FD_SET(fd, fdsetp)    __FD_SET (fd, fdsetp)

/usr/include/bits/select.h中
1 # define __FD_SET(d, set)    (__FDS_BITS (set)[__FDELT (d)] |= __FDMASK (d))

上記の手順では、実際には各bitがfd_にあることがわかります.setのbitシーケンスの位置はfdの値に対応します.そしてfd_set構造におけるbitビット数は_FD_SETSIZE定義、_FD_SETSIZEは/usr/include/bits/typesize.h(以下sys/socket.h->bits/types.h->bits/typesizes.hを含む関係)は1024として定義される.
現在の問題は、fd>=1024の場合、FD_SETマクロは実際にはメモリ書き込みの境界を越えます.実際にman selectでは、以下のように明確に説明されています.
NOTES
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
この点は以前の私を含めて、多くの人が気づかなかったことであり、雲風大神の博文『共にselectによる崩壊』もこの問題を説明している.
Linuxシステムではselectも安全ではないことがわかりますが、使用するにはfdが1024に達しているかどうかを慎重に確認しなければなりませんが、これは難しいです.そうしないと、pollやepollを正直に使いましょう.
少し話が遠くなりましたが、Linuxシステムの下でfd値がどのように分配されて確定されているのか、fdがintタイプであることはよく知られていますが、その値がどのように増加しているのか、以下の内容で少し分析しました.2.6.30バージョンのkernelを例に、レンガを撮ることを歓迎します.
まずどの関数がfd分配を行うかを知らなければならない.これに対してpipeを例に挙げると、fs/pipeでfdを分配する典型的なsyscallである.cではpipeとpipe 2のsyscall実装を定義し,以下のようにする.
 1 SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)

 2 {

 3     int fd[2];

 4     int error;

 5 

 6     error = do_pipe_flags(fd, flags);

 7     if (!error) {

 8         if (copy_to_user(fildes, fd, sizeof(fd))) {

 9             sys_close(fd[0]);

10             sys_close(fd[1]);

11             error = -EFAULT;

12         }

13     }

14     return error;

15 }

16 

17 SYSCALL_DEFINE1(pipe, int __user *, fildes)

18 {

19     return sys_pipe2(fildes, 0);

20 }

さらに分析do_pipe_flags()実装、get_の使用を発見unused_fd_flags(flags)はfdを割り当てるマクロdefine get_であるunused_fd_flags(flags) alloc_fd(0,(flags))はinclude/linux/fsに位置する.h中
主役を見つけたんだallocfd()は、カーネル章が実際にfd割り当てを実行する関数です.fs/file.c,実現も簡単で,以下のようにする.
 1 int alloc_fd(unsigned start, unsigned flags)

 2 {

 3     struct files_struct *files = current->files;

 4     unsigned int fd;

 5     int error;

 6     struct fdtable *fdt;

 7 

 8     spin_lock(&files->file_lock);

 9 repeat:

10     fdt = files_fdtable(files);

11     fd = start;

12     if (fd < files->next_fd)

13         fd = files->next_fd;

14 

15     if (fd < fdt->max_fds)

16         fd = find_next_zero_bit(fdt->open_fds->fds_bits,

17                        fdt->max_fds, fd);

18 

19     error = expand_files(files, fd);

20     if (error < 0)

21         goto out;

22 

23     /*

24      * If we needed to expand the fs array we

25      * might have blocked - try again.

26      */

27     if (error)

28         goto repeat;

29 

30     if (start <= files->next_fd)

31         files->next_fd = fd + 1;

32 

33     FD_SET(fd, fdt->open_fds);

34     if (flags & O_CLOEXEC)

35         FD_SET(fd, fdt->close_on_exec);

36     else

37         FD_CLR(fd, fdt->close_on_exec);

38     error = fd;

39 #if 1

40     /* Sanity check */

41     if (rcu_dereference(fdt->fd[fd]) != NULL) {

42         printk(KERN_WARNING "alloc_fd: slot %d not NULL!
", fd); 43 rcu_assign_pointer(fdt->fd[fd], NULL); 44 } 45 #endif 46 47 out: 48 spin_unlock(&files->file_lock); 49 return error; 50 }

pipeのシステム呼び出しではstart値は常に0であり、中間比較キーのexpand_files()関数は、与えられたfd値に基づいて、プロセスのオープンファイルテーブルを拡張する必要があるか否かを判断し、その関数ヘッダ注釈は以下の通りである.
/*

 * Expand files.

 * This function will expand the file structures, if the requested size exceeds

 * the current capacity and there is room for expansion.

 * Return <0 error code on error; 0 when nothing done; 1 when files were

 * expanded and execution may have blocked.

 * The files->file_lock should be held on entry, and will be held on exit.

 */

 
ここではその実現について深く考えず、allocに戻ります.fd()は,fdを割り当てる原則が
fd値が最も小さい空きfdを優先的に割り当てるたびに、割り当てが成功しない場合、EMFILEのエラーコードが返されます.これは、現在のプロセスでfdが多すぎることを示します.
ここでも,会社が書いたサービス側プログラム(kernelは2.6.18)では,クライアントリンクに対応するfdを印刷するたびに変化する法則があり,新しい接続に割り当てられたfd値が8であれば,閉じた直後の新しいリンクに割り当てられたfdも8であり,さらに新しいリンクのfd値は徐々に1を加えていることを実証した.
そのため、socket対応fd割り当て方法を探し続けたところ、最終的にはalloc_fd(0,(flags)、呼び出しシーケンスは以下のsocket(sys_call)->sock_map_fd() -> sock_alloc_fd() -> get_unused_fd_flags()openシステム呼び出しもget_unused_fd_flags()は、ここでは挙げません.
今、冒頭のselectの問題を振り返って話したいと思います.Linuxシステムfdの分配規則のため、実際には毎回のfd値ができるだけ小さいことを保証して、一般的に非IO頻繁なシステムで、確かに1つのプロセスの中でfd値が1024に達する確率は比較的に小さいです.したがって,selectを捨てるべきかどうかは,完全に絶対的な結論を下すことはできない.設計されたシステムがfd値が1024未満であることを保証する他の措置がある場合、selectを用いることは間違いない.
しかし、ネット通信プログラムの场合は决して仮定すべきではないので、できるだけselectを使わないようにしましょう!!