前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >源码解读Linux的limits.conf文件

源码解读Linux的limits.conf文件

作者头像
一见
发布2019-03-14 15:15:56
1.9K0
发布2019-03-14 15:15:56
举报
文章被收录于专栏:蓝天蓝天

1. 前言

本文不一定适合比较老版本的Linux,如果只关心使用,请直接看“总结”,本文主要针对CentOS,其它Linux发行版本类似,但细节可能有出入,比如重启服务可能不是用systemctl,而是service等。

当需要调整一个进程可打开的最多文件数或SOCKET连接数等,以CentOS为例,通常的做法是修改文件/etc/security/limits.conf,比如将最多可打开数调整为10万:

代码语言:javascript
复制
# vi /etc/security/limits.conf
* soft nofile 100000
* hard nofile 100000

读取limit.conf文件的并不是Linux内核,而是一个内核模块PAM,对应的模块文件为:

代码语言:javascript
复制
/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。

代码语言:javascript
复制
/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相同)。

代码语言:javascript
复制
# 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多行:

代码语言:javascript
复制
#if !defined(linux) && !defined(__linux)
#warning THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
#endif

源代码提供autoconf编译,尝试在Linux-3.10上可编译成功:

代码语言:javascript
复制
~/Linux-PAM-1.3.1]$ ./configure --prefix=/usr/local/Linux-PAM-1.3.1
make

4. limits.conf的由来

确定模块pam_limits的配置文件,由宏CONF_FILE决定:

代码语言:javascript
复制
// pam_limits.c
#define CONF_FILE (pl->conf_file != NULL)?pl->conf_file:LIMITS_FILE

使用的地方:

代码语言:javascript
复制
// 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:

代码语言:javascript
复制
# 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文件:

代码语言:javascript
复制
# 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目录,找一个看一看:

代码语言:javascript
复制
# 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),意为创建(打开)一个会话:

代码语言:javascript
复制
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调用链(实际由配置文件中的每一行配置组成链):

代码语言:javascript
复制
// 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中,下列展示小部分:

代码语言:javascript
复制
/* ----------------- 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函数。

代码语言:javascript
复制
// 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就已执行了:

代码语言:javascript
复制
/* 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时生效:

代码语言:javascript
复制
// 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仍然不是,总是可插拔):

代码语言:javascript
复制
# 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

代码语言:javascript
复制
# systemctl restart crond

2) 显示系统状态

代码语言:javascript
复制
# 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) 重启系统

代码语言:javascript
复制
# systemctl reboot

4) 关闭电源

代码语言:javascript
复制
# systemctl poweroff

5) 待机

代码语言:javascript
复制
# systemctl suspend

6) 休眠

代码语言:javascript
复制
# systemctl hibernate

有关systemctl的更多信息,可浏览:

https://wiki.archlinux.org/index.php/systemd_(简体中文)

9. 总结

修改limits.conf不会立即生效,除非重启相关的父进程,比如crontab的crond,而有些老版本的Linux可能只能重启以生效。

代码语言:javascript
复制
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或更高版本的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) 完成。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-01-29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. PAM
  • 3. pam_limits
  • 4. limits.conf的由来
  • 5. 模块入口函数
  • 6. 解析limits.conf
  • 7. 生效limits.conf
  • 8. systemctl和systemd
  • 9. 总结
  • 附1:资源
  • 附2:编译ninja
  • 附3:使用meson编译systemd
  • 附4:安装Python-3.7.2
  • 附5:安装libcap
相关产品与服务
代码托管
CODING 代码托管(CODING Code Repositories,CODING-CR)是为开发者打造的云端便捷代码管理工具,旨在为更多的开发者带去便捷、高效的开发体验,全面支持 Git/SVN 代码托管,包括代码评审、分支管理、超大仓库等功能。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档