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中
/usr/include/bits/select.h中
上記の手順では、実際には各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実装を定義し,以下のようにする.
さらに分析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,実現も簡単で,以下のようにする.
pipeのシステム呼び出しではstart値は常に0であり、中間比較キーのexpand_files()関数は、与えられたfd値に基づいて、プロセスのオープンファイルテーブルを拡張する必要があるか否かを判断し、その関数ヘッダ注釈は以下の通りである.
ここではその実現について深く考えず、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を使わないようにしましょう!!
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を使わないようにしましょう!!