本文不必定適合比較老版本的Linux,若是隻關心使用,請直接看「總結」,本文主要針對CentOS,其它Linux發行版本相似,但細節可能有出入,好比重啓服務可能不是用systemctl,而是service等。php
當須要調整一個進程可打開的最多文件數或SOCKET鏈接數等,以CentOS爲例,一般的作法是修改文件/etc/security/limits.conf,好比將最多可打開數調整爲10萬:html
# vi /etc/security/limits.confpython * soft nofile 100000linux * hard nofile 100000git |
讀取limit.conf文件的並非Linux內核,而是一個內核模塊PAM,對應的模塊文件爲:程序員
/usr/lib64/security/pam_limits.sogithub /usr/lib/security/pam_limits.sobootstrap |
而/etc/pam.d目錄下的配置文件,則由libpam.so讀取,實際上全部的模塊均由libpam.so加載,可將libpam.so當作是全部PAM模塊的框架或容器,並且libpam.so自己也不是內核的組成部分。session
多個不一樣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 |
PAM的全稱爲「Pluggable Authentication Modules」,即可插入認證模塊。最初由太陽微系統公司(Sun Microsystems,已於2009年被甲骨文收購)於1995年在Solaris開發。PAM代碼不包含在Linux內核中,並有專門的網站:http://linux-pam.org/,源代碼託管在Github上(https://github.com/linux-pam/linux-pam/releases)。
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 |
肯定模塊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。
會話(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 */ |
重聚焦到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); } } |
加載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); 。。。。。。 } |
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_(簡體中文)。
修改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) 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
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) 完成。
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)
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)。
1) 從https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git/下載源代碼包
2) 解壓後進入解壓目錄
3) 執行make編譯
4) 執行make install安裝
5) 完成。