源碼解讀Linux的limits.conf文件

1. 前言

本文不一定適合比較老版本的Linux,如果只關心使用,請直接看“總結”,本文主要針對CentOS,其它Linux發行版本類似,但細節可能有出入,比如重啓服務可能不是用systemctl,而是service等。

當需要調整一個進程可打開的最多文件數或SOCKET連接數等,以CentOS爲例,通常的做法是修改文件/etc/security/limits.conf,比如將最多可打開數調整爲10萬:

# vi /etc/security/limits.conf

* soft nofile 100000

* hard nofile 100000

 

讀取limit.conf文件的並不是Linux內核,而是一個內核模塊PAM,對應的模塊文件爲:

/usr/lib64/security/pam_limits.so

/usr/lib/security/pam_limits.so

 

/etc/pam.d目錄下的配置文件,則由libpam.so讀取,實際上所有的模塊均由libpam.so加載,可將libpam.so看成是所有PAM模塊的框架或容器,而且libpam.so本身也不是內核的組成部分。

多個不同Linux版本上查看,並沒有叫libpam.so的文件名,均是libpam.so.0(不清楚是否所有都這樣),但是編譯Linux-PAM-1.3.1源代碼有名爲libpam.so軟鏈接,指向libpam.so.0.84.2

/usr/lib64/libpam.so.0 -> libpam.so.0.83.1

/usr/lib64/libpam.so.0.83.1

/usr/lib64/libpam_misc.so.0.82.0

 

/usr/lib/libpam.so.0 -> libpam.so.0.83.1

/usr/lib/libpam.so.0.83.1

/usr/lib/libpam_misc.so.0.82.0

 

libpam.so會被加載到crond等進程空間(那當然也可以不加載),如果沒有加載libpam.so,則limits.conf不會生效。crond等不會主動加載libpam.so,那麼是誰讓libpam.so進入crond等進程空間的了?(執行“grep libpam /proc/`pidof crond`/maps”可查看libpam是否在crond的進程空間)。

CentOS,可用service來啓動或重啓crond,所以跟它應當是相關的,而service實際調用的是systemctl這一系統工具(非Shell腳本,service爲老版本使用方式,使用systemctl啓動和重啓服務,使用方式和service相同)。

# service crond restart

Redirecting to /bin/systemctl restart  crond.service

 

# file /bin/systemctl    

/bin/systemctl: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV)

 

# systemctl crond restart # 重啓crontab服務進程crond

2. PAM

PAM的全稱爲“Pluggable Authentication Modules”,即可插入認證模塊。最初由太陽微系統公司(Sun Microsystems,已於2009年被甲骨文收購)於1995年在Solaris開發。PAM代碼不包含在Linux內核中,並有專門的網站:http://linux-pam.org/,源代碼託管在Github上(https://github.com/linux-pam/linux-pam/releases)。

3. pam_limits

pam_limits是PAM其中的一個模塊(模塊文件名爲pam_limits.so),也是程序員接觸較多的模型之一,對應的源代碼文件爲pam_limits.c,代碼規模爲幾百行,加上所有註釋和空格有1100多行:

#if !defined(linux) && !defined(__linux)

#warning THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!

#endif

 

源代碼提供autoconf編譯,嘗試在Linux-3.10上可編譯成功:

~/Linux-PAM-1.3.1]$ ./configure --prefix=/usr/local/Linux-PAM-1.3.1

make

4. limits.conf的由來

確定模塊pam_limits的配置文件,由宏CONF_FILE決定:

// pam_limits.c

#define CONF_FILE (pl->conf_file != NULL)?pl->conf_file:LIMITS_FILE

 

使用的地方:

// pam_limits.c

static int

parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,

     int ctrl, struct pam_limit_s *pl)

{

    FILE *fil;

    char buf[LINE_LENGTH];

 

    /* check for the LIMITS_FILE */

    if (ctrl & PAM_DEBUG_ARG)

        pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", CONF_FILE);

    fil = fopen(CONF_FILE, "r"); // 打開配置文件,跟參數“pl”有關係

    if (fil == NULL) {

        pam_syslog (pamh, LOG_WARNING,

    "cannot read settings from %s: %m", CONF_FILE);

        return PAM_SERVICE_ERR;

    }

 

如果函數parse_config_file的參數“pl”值爲NULL,則配置文件名在編譯時決定,這種情況下,配置文件名被固定爲limits.conf

# Makefile.am

modules/pam_limits/Makefile.am: -DLIMITS_FILE_DIR=\"$(limits_conf_dir)/*.conf\" \

modules/pam_limits/Makefile.am: -DLIMITS_FILE=\"$(SCONFIGDIR)/limits.conf\"

 

只是limits.conf所在目錄可由編譯時決定,也就是看SCONFIGDIR,決定在automakeconfigure.ac文件:

# configure.ac

AC_ARG_ENABLE(sconfigdir,

        AS_HELP_STRING([--enable-sconfigdir=DIR],[path to module conf files @<:@default=$sysconfdir/security@:>@]),

        SCONFIGDIR=$enableval, SCONFIGDIR=$sysconfdir/security)

AC_SUBST(SCONFIGDIR)

 

dnl and some hacks to use /etc and /lib

test "${prefix}" = "NONE" && prefix="/usr"

if test ${prefix} = '/usr'

then

dnl If we use /usr as prefix, use /etc for config files

        if test ${sysconfdir} = '${prefix}/etc'

        then

                sysconfdir="/etc"

        fi

 

推導出默認爲“/etc/security/limits.conf”,但從前面的分析,可看到實際還可參數動態指定,這個參數怎麼來?可進入Linux/etc/pam.d目錄,找一個看一看:

# vi /etc/pam.d/login

session    required     pam_selinux.so close

session    required     pam_selinux.so open

 

上述最後一個配置項即爲模型的參數值,參數值可有0、一個或多個。通常pam_limits.so使用默認參數值,因此它的配置文件limits.conf完整路徑爲:/etc/security/limits.conf。

5. 模塊入口函數

會話(Session)類的PAM模塊的入口函數均爲pam_sm_open_session(授權類的爲pam_sm_authenticate,密碼類的爲pam_sm_chauthtok),意爲創建(打開)一個會話:

int

pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv);

// libpam/pam_handlers.c:  sym = "pam_sm_open_session";

 

加載模塊在pam_handlers.c中完成,實際上一個模塊可加載多次(可在/etc/security下看到有些配置文件中同一模型有多行)。類似於iptables,每加載一次創建一個handler,依次組成一個handler調用鏈(實際由配置文件中的每一行配置組成鏈):

// pam_handlers.c

// 被_pam_parse_conf_file直接調用,

// 和被_pam_init_handlers、_pam_load_conf_file一級間接調用

int _pam_add_handler(pam_handle_t *pamh

     , int handler_type, int other, int stack_level, int type

     , int *actions, const char *mod_path

     , int argc, char **argv, int argvlen)

{

    struct loaded_module *mod = NULL;

    。。。。。。

    if ((handler_type == PAM_HT_MODULE ||

         handler_type == PAM_HT_SILENT_MODULE) &&

        mod_path != NULL) {

if (mod_path[0] == '/') {

    mod = _pam_load_module(pamh, mod_path, handler_type);

} else if (asprintf(&mod_full_path, "%s%s",

     DEFAULT_MODULE_PATH, mod_path) >= 0) {

    mod = _pam_load_module(pamh, mod_full_path, handler_type);

    _pam_drop(mod_full_path);

} else {

    pam_syslog(pamh, LOG_CRIT, "cannot malloc full mod path");

    return PAM_ABORT;

}

if (mod == NULL) {

    /* if we get here with NULL it means allocation error */

    return PAM_ABORT;

}

    。。。。。。

        /* point handler_p's at the root addresses of the function stacks */

    switch (type) {

        。。。。。。

        case PAM_T_SESS:

            handler_p = &the_handlers->open_session;

            sym = "pam_sm_open_session";

            handler_p2 = &the_handlers->close_session;

            sym2 = "pam_sm_close_session";

            break;

        。。。。。。

    }

    

    if ((mod_type == PAM_MT_DYNAMIC_MOD) &&

        !(func = _pam_dlsym(mod->dl_handle, sym)) ) {

        pam_syslog(pamh, LOG_ERR, "unable to resolve symbol: %s", sym);

    }

    。。。。。。

}

 

每個模塊的結果可能是成功PAM_SUCCESS(0),全定義在文件libpam/include/security/_pam_types.h中,下列展示小部分:

/* ----------------- The Linux-PAM return values ------------------ */

#define PAM_SUCCESS 0 /* Successful function return */

#define PAM_OPEN_ERR 1 /* dlopen() failure when dynamically */

/* loading a service module */

#define PAM_SYMBOL_ERR 2 /* Symbol not found */

#define PAM_SERVICE_ERR 3 /* Error in service module */

#define PAM_SYSTEM_ERR 4 /* System error */

6. 解析limits.conf

重聚焦到pam_limits模塊,看看它的配置文件解析,這發生在函數pam_limits.c中的parse_config_file函數。

// pam_limits.c

static int

parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,

     int ctrl, struct pam_limit_s *pl)

{

    FILE *fil;

    char buf[LINE_LENGTH]; // #define LINE_LENGTH 1024

 

    // 以只讀方式打開limits.conf

    fil = fopen(CONF_FILE, "r");

    if (fil == NULL) {

        pam_syslog (pamh, LOG_WARNING,

    "cannot read settings from %s: %m", CONF_FILE);

        return PAM_SERVICE_ERR;

    }

    

    /* start the show */

    // 一行行遍歷limits.conf

    while (fgets(buf, LINE_LENGTH, fil) != NULL) {

        line = buf;

        /* skip the leading white space */

        while (*line && isspace(*line)) // 跳過空行

            line++;

            

        /* Rip off the comments */

        tptr = strchr(line,'#'); // 去掉註釋

        if (tptr)

            *tptr = '\0';

        /* Rip off the newline char */

        tptr = strchr(line,'\n'); // 刪除換行符,注意並不包括回車符

        if (tptr)

            *tptr = '\0';

        /* Anything left ? */

        if (!strlen(line)) // 經過上面幾步折騰,可能成了空行

            continue;

        

        // 直接調用sscanf解析配置項

        //

        // 配置行示例:

        // * soft nofile 100000

        //

        // domain:作用域名,“*”表示對所有用戶有效

        i = sscanf(line,"%s%s%s%s", domain, ltype, item, value);

        。。。。。。

        // 下面只看兩個常用配置:domain配置爲“*”或指定的用戶名

        // 可以看到在加載limits.conf,主要是設置輸出參數pl的值。

        // 而parse_config_file由pam_sm_open_session調用,亦即模塊被加載時被調用。

        //

        // 也因此修改limits.conf是不能立即生效的,

        // 除非重啓該進程,而子進程又繼承父進程的設置。

        //

        // 假設程序跑在crontab中,則應重啓crond進程,

        // 比如CentOS中重啓crond:service crond restart

        // 雖然crontab中的進程是由crond拉起來的,但它並加載PAM模塊,

        // 原因是crond在拉起子進程時,對子進程關閉了所有描述符。

        //

        // process_limit針對當前調用進程進行limit設置

        if (strcmp(domain, "*") == 0)

            // limit was set by a default entry

            process_limit(pamh, LIMITS_DEF_DEFAULT, ltype, item, value, ctrl, pl);

        。。。。。。        

        if (strcmp(uname, domain) == 0) /* this user have a limit */

            // limit was set by an user entry

            process_limit(pamh, LIMITS_DEF_USER, ltype, item, value, ctrl, pl);

    }

}

7. 生效limits.conf

加載PAM模塊時,即會生效limits.conf,因爲這個在pam_sm_open_session就已執行了:

/* now the session stuff */

int

pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,

     int argc, const char **argv)

{

    struct pam_limit_s plstruct;

    struct pam_limit_s *pl = &plstruct;

    。。。。。。

    // 調用parse_config_file解析limits.conf,

    // 配置行解析結果存儲在pl中(亦即plstruct)

    retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);

    。。。。。。

    // 使配置立即生效(setup_limits調用系統函數setrlimit)

    retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, pl);

    。。。。。。

    return PAM_SUCCESS;

}

 

模塊pam_limits.so是由PAM模塊libpam.so加載的,crond加載的只是libpam.so。“/etc/pam.d”目錄下的文件什麼時候生效?加載libpam.so時生效:

// pam_start.c

int pam_start (

    const char *service_name,

    const char *user,

    const struct pam_conv *pam_conversation,

    pam_handle_t **pamh)

{

    。。。。。。

    if ( _pam_init_handlers(*pamh) != PAM_SUCCESS ) {

    。。。。。。

}

 

// pam_handlers.c

int _pam_init_handlers(pam_handle_t *pamh)

{

    。。。。。。

    // 函數_pam_parse_conf_file負責解析libpam.so的配置文件,

    // 這些配置文件一般位於目錄/etc/pam.d下,如:

    // # ls -l /etc/pam.d/pass*

    // -rw-r--r-- 1 root root 188 6月  10 2014 /etc/pam.d/passwd

    // -rw-r--r-- 1 root root 974 12月 29 2016 /etc/pam.d/password-auth

    retval = _pam_parse_conf_file(pamh, f, NULL, PAM_T_ANY, 0);

    。。。。。。

}

8. systemctlsystemd

CentOS上的systemctlCentOS-7.X之前爲service腳本)類似於Windows平臺的服務管理器,替代老版本中的service腳本來管理服務。Systemctl功能非常多,有關systemctl的功能不在本文過多描述。

sytemctl的工作原理是通過與服務systemd交互,來完成各項工作,比如重啓crond進程。在CentOSsystemctl替代了inittab

可以看到正是systemd加載了pam,從ldd結果可以看出systemd也不是動態加載pam模塊,而是編譯時就綁定了,因此libpam.so成了系統的必須部分(但pam_limits.so仍然不是,總是可插拔):

# ldd /usr/lib/systemd/systemd 

        linux-vdso.so.1 =>  (0x00007ffce5b72000)

        /$LIB/libonion.so => /lib64/libonion.so (0x00007f2430f56000)

        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f2430d31000)

        libcap.so.2 => /lib64/libcap.so.2 (0x00007f2430b2c000)

        libpam.so.0 => /lib64/libpam.so.0 (0x00007f243091d000)

        libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f24306f5000)

        libkmod.so.2 => /lib64/libkmod.so.2 (0x00007f24304df000)

        libmount.so.1 => /lib64/libmount.so.1 (0x00007f24302a0000)

        librt.so.1 => /lib64/librt.so.1 (0x00007f2430098000)

        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f242fe82000)

        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f242fc66000)

        libc.so.6 => /lib64/libc.so.6 (0x00007f242f8a2000)

        /lib64/ld-linux-x86-64.so.2 (0x00007f243105c000)

        libdl.so.2 => /lib64/libdl.so.2 (0x00007f242f69e000)

        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f242f43d000)

        liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f242f218000)

        libattr.so.1 => /lib64/libattr.so.1 (0x00007f242f013000)

        libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007f242ee0d000)

        libz.so.1 => /lib64/libz.so.1 (0x00007f242ebf7000)

        libblkid.so.1 => /lib64/libblkid.so.1 (0x00007f242e9ba000)

        libuuid.so.1 => /lib64/libuuid.so.1 (0x00007f242e7b5000)

 

# ldd /usr/sbin/crond

        linux-vdso.so.1 =>  (0x00007ffef31a5000)

        /$LIB/libonion.so => /lib64/libonion.so (0x00007f87b89e5000)

        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f87b8416000)

        libpam.so.0 => /lib64/libpam.so.0 (0x00007f87b8207000)

        libdl.so.2 => /lib64/libdl.so.2 (0x00007f87b8003000)

        libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f87b7ddb000)

        libc.so.6 => /lib64/libc.so.6 (0x00007f87b7a17000)

        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f87b77b6000)

        liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f87b7591000)

        /lib64/ld-linux-x86-64.so.2 (0x00007f87b88cc000)

        libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007f87b738b000)

        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f87b716f000)

 

實際上,systemdLinux系統(CentOS如此,像Ubuntu未必)的第一個進程,取代了以前的init進程,可以看到systemd進程和init進程不會同時存在,低版本爲init,高版本爲systemdUbuntu使用的是upstart,但也可能用systemd替代upstart

systemd源代碼的編譯文件meson.build(類似於CMakeCMakeLists.txt文件,或bazelBUILD文件)中可以看到systemdlibpam的依賴。

 

systemctl部分用法:

1) 重啓crond

# systemctl restart crond

 

2) 顯示系統狀態

# systemctl status

● Jian.mooon

    State: degraded

     Jobs: 0 queued

   Failed: 2 units

    Since: 二 2017-10-24 02:38:50 CST; 1 years 3 months ago

   CGroup: /

   。。。。。。

 

3) 重啓系統

# systemctl reboot

 

4) 關閉電源

# systemctl poweroff

 

5) 待機

# systemctl suspend

 

6) 休眠

# systemctl hibernate

 

有關systemctl的更多信息,可瀏覽:

https://wiki.archlinux.org/index.php/systemd_(簡體中文)

9. 總結

修改limits.conf不會立即生效,除非重啓相關的父進程,比如crontabcrond,而有些老版本的Linux可能只能重啓以生效。

1) 系統啓動 -> 啓動初始化進程systemd -> 進程sytemd加載libpam.so模塊

2) libpam.so根據/etc/pam.d決定是否加載pam_limits.so等

3) 在加載pam_limits.so時,會讀取/etc/security/limits.conf

4) 重啓crond等,實際是向systemd發重啓指令

5) 一句話:如果要使用limits.conf生效,一定要有加載pam_limits.so,如果修改limits.conf,至少要讓pam_limits.so重讀limits.conf。

1:資源

1) PAM官方

http://linux-pam.org/

2) PAM源代碼

https://github.com/linux-pam/linux-pam/releases

3) systemd源代碼

https://github.com/systemd/systemd(使用meson編譯,Meson is an open source build system,依賴ninja

4) Vixie-cron源代碼

http://ftp.isc.org/isc/cron/

https://github.com/svagner/vixie-cron

ftp://ftp.riken.jp/Linux/cern/updates/slc52/SRPMS/repoview/vixie-cron.html

2:編譯ninja

ninja類似於make,使用meson之前必須先準備好ninja

1) 從https://github.com/ninja-build/ninja下載ninja源代碼

2) 解壓源代碼包,然後進入解壓後的目錄

3) 執行“./configure.py --bootstrap

4) 成功後會在目錄下生成名爲ninja的可執行程序文件

5) 將可執行程序文件複製到PATH目錄下,比如:/usr/local/bin/usr/bin等目錄

6) 完成。

3:使用meson編譯systemd

Meson-0.49.1要求3.5或更高版本的Pythonhttps://www.python.org/),和1.5或更高版本的Ninja,還依賴gperf(簡單安裝:yum install -y gperf),還依賴libcap-dev(執行yum install -y libcap安裝,如果仍然不行,從https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git/下載源代碼安裝),除此之外還有一些其它的依賴,需逐個解決。

1) 從https://github.com/mesonbuild/meson下載meson源代碼

2) 解壓後,將meson目錄添加到PATH中,比如:export PATH=/root/X/meson-0.49.1:$PATH

3) 進入systemd源代碼目錄

4) 執行“meson.py build”(如果出錯,可能是Python版本不夠)

5) 成功後會生成build子目錄

6) 進入build目錄,執行ninja開始編譯(ninja類似於make

4:安裝Python-3.7.2

Python-3.7.2採用automake編譯:

1) 執行configure生成Makefile文件:./configure --prefix=/usr/local/Python-3.7.2

2) 執行make開始編譯Python(編譯時間會有點長)

3) 執行make install,安裝Python(安裝時間稍有點長)

4) 將Pythonbin目錄加入到PATH中,如:export PATH=/usr/local/Python-3.7.2/bin:$PATH

5) 可以開始使用Python-3.7.2了。

 

如果遇到錯誤“ModuleNotFoundError: No module named '_ctypes'”,是因爲依賴的libffi-devel版本不夠(可執行“yum install -y libffi-devel”安裝libffi,或源碼方式安裝libffi)。

5:安裝libcap

1) 從https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git/下載源代碼包

2) 解壓後進入解壓目錄

3) 執行make編譯

4) 執行make install安裝

5) 完成。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章