zoukankan      html  css  js  c++  java
  • 源码解读Linux的limits.conf文件

    目录

     

    目录 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. systemctlsystemd 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,决定在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 = '';

            /* Rip off the newline char */

            tptr = strchr(line,' '); // 删除换行符,注意并不包括回车符

            if (tptr)

                *tptr = '';

            /* 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) 完成。

     

  • 相关阅读:
    详解javascript中的闭包
    Cookie/Session的机制与安全
    session详解
    linux常用目录简介
    对比cp和scp命令 将数据从一台linux服务器复制到另一台linux服务器
    webpack打包速度和性能再次优化
    pc浏览器css和js计算浏览器宽度的差异以及和滚动条的关系
    chrome浏览器Timing内各字段解析
    深入理解-CLI与PHP-FPM
    swool教程链接汇总
  • 原文地址:https://www.cnblogs.com/aquester/p/10335628.html
Copyright © 2011-2022 走看看