共計 4581 個字符,預計需要花費 12 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 Linux 系統下 fd 分配的方法是什么,文章內容質量較高,因此丸趣 TV 小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
最近幾天在公司里寫網絡通訊的代碼比較多,自然就會涉及到 IO 事件監測方法的問題。我驚奇的發現 select 輪訓的方法在那里居然還大行其道。我告訴他們現在無論在 Linux 系統下,還是 windows 系統下,select 都應該被廢棄不用了,其原因是在兩個平臺上 select 的系統調用都有一個可以說是致命的坑。
在 windows 上面單個 fd_set 中容納的 socket handle 個數不能超過 FD_SETSIZE(在 win32 winsock2.h 里其定義為 64,以 VS2010 版本為準),并且 fd_set 結構使用一個數組來容納這些 socket handle 的,每次 FD_SET 宏都是向這個數組中放入一個 socket handle,并且此過程中是限定了不能超過 FD_SETSIZE,具體請自己查看 winsock2.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 中
typedef long int __fd_mask; #define __NFDBITS (8 * sizeof (__fd_mask)) #define __FDELT(d) ((d) / __NFDBITS) #define __FDMASK(d) ((__fd_mask) 1 ((d) % __NFDBITS)) typedef struct { /* XPG4.2 requires this member name. Otherwise avoid the name from the global namespace. */ #ifdef __USE_XOPEN __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)- fds_bits) #else __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)- __fds_bits) #endif } fd_set; #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 為例,它是分配 fd 的一個典型的 syscall,在 fs/pipe.c 中定義了 pipe 和 pipe2 的 syscall 實現,如下
SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags) { int fd[2]; int error; error = do_pipe_flags(fd, flags); if (!error) { if (copy_to_user(fildes, fd, sizeof(fd))) { sys_close(fd[0]); sys_close(fd[1]); error = -EFAULT; } } return error; } SYSCALL_DEFINE1(pipe, int __user *, fildes) { return sys_pipe2(fildes, 0); }
進一步分析 do_pipe_flags()實現,發現其使用 get_unused_fd_flags(flags)來分配 fd 的,它是一個宏
#define get_unused_fd_flags(flags) alloc_fd(0, (flags)),位于 include/linux/fs.h 中
好了咱們找到了主角了,就是 alloc_fd(),它就是內核章實際執行 fd 分配的函數。其位于 fs/file.c,實現也很簡單,如下
int alloc_fd(unsigned start, unsigned flags) { struct files_struct *files = current- files; unsigned int fd; int error; struct fdtable *fdt; spin_lock( files- file_lock); repeat: fdt = files_fdtable(files); fd = start; if (fd files- next_fd) fd = files- next_fd; if (fd fdt- max_fds) fd = find_next_zero_bit(fdt- open_fds- fds_bits, fdt- max_fds, fd); error = expand_files(files, fd); if (error 0) goto out; /* * If we needed to expand the fs array we * might have blocked - try again. */ if (error) goto repeat; if (start = files- next_fd) files- next_fd = fd + 1; FD_SET(fd, fdt- open_fds); if (flags O_CLOEXEC) FD_SET(fd, fdt- close_on_exec); else FD_CLR(fd, fdt- close_on_exec); error = fd; #if 1 /* Sanity check */ if (rcu_dereference(fdt- fd[fd]) != NULL) { printk(KERN_WARNING alloc_fd: slot %d not NULL!\n , fd); rcu_assign_pointer(fdt- fd[fd], NULL); } #endif out: spin_unlock(files- file_lock); return error; }
在 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) 中,每次打印 client 鏈接對應的 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 頻繁的系統,的確一個進程中 fd 值達到 1024 的概率比較小。因而對此到底是否該棄用 select,還不能完全地做絕對的結論。如果設計的系統的確有其他措施保證 fd 值小于 1024,那么用 select 無可厚非。
但在網絡通訊程序這種場合是絕不應該作此假設的,所以還是盡量的不用 select 吧!!
關于 Linux 系統下 fd 分配的方法是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。