qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Linux系統(tǒng)下fd分配的方法

           最近幾天在公司里寫網(wǎng)絡(luò)通訊的代碼比較多,自然就會(huì)涉及到IO事件監(jiān)測(cè)方法的問題。我驚奇的發(fā)現(xiàn)select輪訓(xùn)的方法在那里居然還大行其道。我告訴他們現(xiàn)在無(wú)論在Linux系統(tǒng)下,還是windows系統(tǒng)下,select都應(yīng)該被廢棄不用了,其原因是在兩個(gè)平臺(tái)上select的系統(tǒng)調(diào)用都有一個(gè)可以說(shuō)是致命的坑。
            在windows上面單個(gè)fd_set中容納的socket handle個(gè)數(shù)不能超過FD_SETSIZE(在win32 winsock2.h里其定義為64,以VS2010版本為準(zhǔn)),并且fd_set結(jié)構(gòu)使用一個(gè)數(shù)組來(lái)容納這些socket handle的,每次FD_SET宏都是向這個(gè)數(shù)組中放入一個(gè)socket handle,并且此過程中是限定了不能超過FD_SETSIZE,具體請(qǐng)自己查看winsock2.h中FD_SET宏的定義。
            此處的問題是
            若本身fd_set中的socket handle已經(jīng)達(dá)到FD_SETSIZE個(gè),那么后續(xù)的FD_SET操作實(shí)際上是沒有效果的,對(duì)應(yīng)socket handle的IO事件將被遺漏!!!
            而在Linux系統(tǒng)下面,該問題其實(shí)也是處在fd_set的結(jié)構(gòu)和FD_SET宏上。此時(shí)fd_set結(jié)構(gòu)是使用bit位序列來(lái)記錄每一個(gè)待檢測(cè)IO事件的fd。記錄的方式稍微復(fù)雜,如下
            /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))
            可以看出,在上面的過程,實(shí)際上每個(gè)bit在fd_set的bit序列中的位置對(duì)應(yīng)于fd的值。而fd_set結(jié)構(gòu)中bit位個(gè)數(shù)是__FD_SETSIZE定義的,__FD_SETSIZE在/usr/include/bits/typesize.h(包含關(guān)系如下sys/socket.h -> bits/types.h -> bits/typesizes.h)中被定義為1024。
            現(xiàn)在的問題是,當(dāng)fd>=1024時(shí),F(xiàn)D_SET宏實(shí)際上會(huì)引起內(nèi)存寫越界。而實(shí)際上在man select中對(duì)已也有明確的說(shuō)明,如下
            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.
            這一點(diǎn)包括之前的我,是很多人沒有注意到的,并且云風(fēng)大神有篇博文《一起 select 引起的崩潰》也描述了這個(gè)問題。
            可以看出在Linux系統(tǒng)select也是不安全的,若想使用,得小心翼翼的確認(rèn)fd是否達(dá)到1024,但這很難做到,不然還是老老實(shí)實(shí)的用poll或epoll吧。
            扯得有點(diǎn)遠(yuǎn)了,但也引出了本片文章要敘述的主題,就是Linux系統(tǒng)下fd值是怎么分配確定,大家都知道fd是int類型,但其值是怎么增長(zhǎng)的,在下面的內(nèi)容中我對(duì)此進(jìn)行了一點(diǎn)分析,以2.6.30版本的kernel為例,歡迎拍磚。 首先得知道是哪個(gè)函數(shù)進(jìn)行fd分配,對(duì)此我以pipe為例,它是分配fd的一個(gè)典型的syscall,在fs/pipe.c中定義了pipe和pipe2的syscall實(shí)現(xiàn),如下
          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 }
            進(jìn)一步分析do_pipe_flags()實(shí)現(xiàn),發(fā)現(xiàn)其使用get_unused_fd_flags(flags)來(lái)分配fd的,它是一個(gè)宏
            #define get_unused_fd_flags(flags) alloc_fd(0, (flags)),位于include/linux/fs.h中
            好了咱們找到了主角了,就是alloc_fd(),它就是內(nèi)核章實(shí)際執(zhí)行fd分配的函數(shù)。其位于fs/file.c,實(shí)現(xiàn)也很簡(jiǎn)單,如下
          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!\n", 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的系統(tǒng)調(diào)用中start值始終為0,而中間比較關(guān)鍵的expand_files()函數(shù)是根據(jù)所給的fd值,判斷是否需要對(duì)進(jìn)程的打開文件表進(jìn)行擴(kuò)容,其函數(shù)頭注釋如下
            /*
            * 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.
            */
            此處對(duì)其實(shí)現(xiàn)就不做深究了,回到alloc_fd(),現(xiàn)在可以看出,其分配fd的原則是
            每次優(yōu)先分配fd值最小的空閑fd,當(dāng)分配不成功,即返回EMFILE的錯(cuò)誤碼,這表示當(dāng)前進(jìn)程中fd太多。
            到此也印證了在公司寫的服務(wù)端程序(kernel是2.6.18)中,每次打印client鏈接對(duì)應(yīng)的fd值得變化規(guī)律了,假如給一個(gè)新連接分配的fd值為8,那么其關(guān)閉之后,緊接著的新的鏈接分配到的fd也是8,再新的鏈接的fd值是逐漸加1的。
            為此,我繼續(xù)找了一下socket對(duì)應(yīng)fd分配方法,發(fā)現(xiàn)最終也是 alloc_fd(0, (flags),調(diào)用序列如下
            socket(sys_call) -> sock_map_fd() -> sock_alloc_fd() -> get_unused_fd_flags()
            open系統(tǒng)調(diào)用也是用get_unused_fd_flags(),這里就不列舉了。
            現(xiàn)在想回頭說(shuō)說(shuō)開篇的select的問題。由于Linux系統(tǒng)fd的分配規(guī)則,實(shí)際上是已經(jīng)保證每次的fd值盡量的小,一般非IO頻繁的系統(tǒng),的確一個(gè)進(jìn)程中fd值達(dá)到1024的概率比較小。因而對(duì)此到底是否該棄用select,還不能完全地做絕對(duì)的結(jié)論。如果設(shè)計(jì)的系統(tǒng)的確有其他措施保證fd值小于1024,那么用select無(wú)可厚非。
            但在網(wǎng)絡(luò)通訊程序這種場(chǎng)合是絕不應(yīng)該作此假設(shè)的,所以還是盡量的不用select吧!!

          posted on 2014-09-02 09:52 順其自然EVO 閱讀(970) 評(píng)論(0)  編輯  收藏 所屬分類: 測(cè)試學(xué)習(xí)專欄

          <2014年9月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 将乐县| 浑源县| 靖边县| 盘锦市| 岚皋县| 准格尔旗| 铜鼓县| 桑日县| 龙口市| 安化县| 普定县| 新竹市| 伊宁县| 怀仁县| 嘉黎县| 东兴市| 南充市| 横峰县| 阿鲁科尔沁旗| 定远县| 嘉黎县| 石城县| 磐石市| 黄龙县| 常山县| 白水县| 长宁区| 双桥区| 巨鹿县| 赣州市| 东源县| 平罗县| 蒙城县| 德安县| 个旧市| 江孜县| 温宿县| 诏安县| 南靖县| 大洼县| 肇源县|