從EMFILE和ENFILE說起,fd limit的問題(一)

下面的描述,統一用“fd”來表示通常所說的“文件句柄”。UNIX/Linux系,稱爲“文件描述符(file descriptor)”,也因此纔有“fd”這個縮寫。“文件句柄”,貌似是Windows系的說法。

從問題入手。

與“Too many open files”這個錯誤相關的errno有EMFILE和ENFILE,查看open、socket、accept、socketpair、pipe等系統調用的手冊,可以看到對EMFILE和ENFILE的解釋:

EMFILE The process already has the maximum number of files open.
EMFILE Process file table overflow.
EMFILE The per-process limit of open file descriptors has been reached.
EMFILE Too many descriptors are in use by this process.
EMFILE Too many file descriptors are in use by the process.

ENFILE The system limit on the total number of open files has been reached.

其中,對於EMFILE的解釋有幾種,雖然說法不同,但都表達了一樣的意思,就是進程的fd已用盡。

爲了解決這個問題,最straightfoward的辦法,就是用ulimit命令或者setrlimit系統調用。

ulimit的作用是,顯示或修改“當前shell”的resource limits,或者在當前shell中啓動的進程的resource limits。

對於ulimit,有兩點需要說明:
一是,ulimit對使用者的價值,是顯示當前的各種limits,在“修改”方面,它幾乎沒什麼用。當前shell的resource limits,在用戶login shell時,就已經確定了,特別是hard limit。具體說,對於soft limit,ulimit做增加操作時,只能增加到和hard limit一樣多;對於hard limit,ulimit只能做減少操作,不能做增加操作,且是不可逆的。
二是,ulimit只能顯示或修改“當前shell”的resource limits,它對system-wide resource limits(聯想一下開頭提到的EMFILE和ENFILE的區別),沒有任何感知,也不能做任何操作。

在用戶login shell的過程中,Linux PAM的一些modules會執行,其中pam_limits module會設置當前user session的resource limits,根據/etc/security/limits.conf(默認路徑)文件和/etc/security/limits.d目錄下的文件(如果有的話)。所以,如果想真正修改“當前shell”的resource limits,就直接修改/etc/security/limits.conf文件,再重新login。

setrlimit的使用,則更加tricky。因爲setrlimit是個系統調用(ulimit是一個shell built-in command),它由一個進程的實現代碼調用,而一個進程有多重身份標識(real user/groud ID, effective user/groud ID),同時,如果有privilege,進程還可以動態的切換身份(setuid/setgid,seteuid/setegid),而最終,這個進程的resource limits,又取決於進程的身份。同時,無論進程是什麼身份,它的resource limits都由/etc/security/limits.conf決定了。

綜目前所述,ulimit和setrlimit都不能很好的解決“Too many open files”的問題。那怎麼更好的解決這個問題呢?

ENFILE這個errno的存在,表明一定存在system-wide的resource limits,而不僅僅有process-specific的resource limits。按照常識,process-specific的resource limits,一定受限於system-wide的resource limits。

有一個與/etc/security/limits.conf類似的pseudo file,可以用來控制system-wide fd limit:/proc/sys/fs/file-max。這個文件是可讀寫的,在我們系統中,是644,owner是root。那是不是說,只要取得superuser權限,是不是就可以任意修改這個文件呢?顯然不可能。且不說一個fd佔一個sizeof(int)的空間,在kernel中,每個打開的文件,都關聯一個struct file,通常在定義在kernel source的include/linux/fs.h中:

struct file {
        union {
                struct llist_node       fu_llist;
                struct rcu_head         fu_rcuhead;
        } f_u;
        struct path             f_path;
#define f_dentry        f_path.dentry
        struct inode            *f_inode;       /* cached value */
        const struct file_operations    *f_op;


        /*
         * Protects f_ep_links, f_flags.
         * Must not be taken from IRQ context.
         */
        spinlock_t              f_lock;
        atomic_long_t           f_count;
        unsigned int            f_flags;
        fmode_t                 f_mode;
        struct mutex            f_pos_lock;
        loff_t                  f_pos;
        struct fown_struct      f_owner;
        const struct cred       *f_cred;
        struct file_ra_state    f_ra;


        u64                     f_version;
#ifdef CONFIG_SECURITY
        void                    *f_security;
#endif
        /* needed for tty driver, and maybe others */
        void                    *private_data;


#ifdef CONFIG_EPOLL
        /* Used by fs/eventpoll.c to link all the hooks to this file */
        struct list_head        f_ep_links;
        struct list_head        f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
        struct address_space    *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
        unsigned long f_mnt_write_state;
#endif
} __attribute__((aligned(4)));  /* lest something weird decides that 2 is OK */


所以,即使有superuser權限,也不可能任意修改/proc/sys/fs/file-max文件,會受限於內存資源。這個文件是在編譯kernel時,根據一個kernel constant NR_OPEN(可以理解爲NumbeR of OPEN files)決定的。

在絕大多數情況下,NR_OPEN都是夠用的。如果程序確實出現ENFILE的錯誤(注意,是“確實”,因爲在一些開源程序中,ENFILE被濫用,直接errno = ENFILE),那麼可以echo XXXXX > /proc/sys/fs/file-max解決。如果file-max設置爲了最大值(NR_OPEN)還不能解決,根據不同kernel版本,有兩種進一步的解決辦法。

2.6.25之前,只能重新編譯,手動指定NR_OPEN的值。2.6.25之後,kernel提供了/proc/sys/fs/nr_open,可以由程序修改,比如調用sysctl。

最後,對於線上“Too many open files”問題,比較簡單並且比較徹底的解決方法,應該是根據需要修改/proc/sys/fs/file-max和/etc/security/limits.conf,而且,只修改limits.conf,一般就夠了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章