目錄
目錄 1
1. 前言 1
2. PAM 2
3. pam_limits 2
4. limits.conf的由來 3
5. 模塊入口函數 4
6. 解析limits.conf 6
7. 生效limits.conf 7
8. systemctl和systemd 8
9. 總結 10
附1:資源 11
附2:編譯ninja 11
附3:使用meson編譯systemd 11
附4:安裝Python-3.7.2 12
附5:安裝libcap 12
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,決定在automake的configure.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. systemctl和systemd
CentOS上的systemctl(CentOS-7.X之前為service腳本)類似於Windows平台的服務管理器,替代老版本中的service腳本來管理服務。Systemctl功能非常多,有關systemctl的功能不在本文過多描述。
sytemctl的工作原理是通過與服務systemd交互,來完成各項工作,比如重啟crond進程。在CentOS,systemctl替代了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) |
實際上,systemd為Linux系統(CentOS如此,像Ubuntu未必)的第一個進程,取代了以前的init進程,可以看到systemd進程和init進程不會同時存在,低版本為init,高版本為systemd。Ubuntu使用的是upstart,但也可能用systemd替代upstart。
在systemd源代碼的編譯文件meson.build(類似於CMake的CMakeLists.txt文件,或bazel的BUILD文件)中可以看到systemd對libpam的依賴。
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不會立即生效,除非重啟相關的父進程,比如crontab的crond,而有些老版本的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官方
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源代碼
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或更高版本的Python(https://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) 將Python的bin目錄加入到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) 完成。