UP | HOME

进程环境

Table of Contents

本章主要说明:

当执行程序时其main函数是如何被调用的
进程终止的不同方式
命令行参数是如何传送给执行程序的
典型的存储器布局是什么样式
如何分配另外的存储空间进程
如何使用环境变量
longjmp 和 setjmp 函数以及它们与栈的交互作用
进程的资源限制

main函数

C程序总是 main 函数开始执行

  • argc : 命令行参数的数目
  • argv : 指向参数的各个指针所构成的数组
/**
 * 主函数
 *
 * argc: 命令行参数的数目
 * argv: 指针数组,其元素是指向各个命令行参数的字符串指针
 *
 * return: 返回值
 *
 */
int main(int argc, char *argv[]);

当内核使用一个 exec 函数 起动C程序 时,在 调用main函数 前先调用一个 特殊的起动例程

  1. 内核 取得 命令行参数环境变量值
  2. 为调用main函数作好准备
C语言编译的时候会调用链接器(ld),而链接器会设置这个“特殊的启动例程”为“可执行文件的起始地址”

进程终止

图7-1显示了一个C程序是如何起动的,以及它终止的各种方式

process-lifecycle.png

有五种方式使进程终止:

  • 正常 终止:
    • 从main返回
    • 调用 exit
    • 调用 _exit
  • 异常 终止:
    • 调用 abort
    • 由一个 信号终止
    上节提及的起动例程使得从 main函数 返回后立即调用 exit 函数:
exit(main(argc,argv)); 
    这里将终止例程以C代码形式表示,实际上该例程常常用汇编语言编写

exit和_exit

exit_exit 函数用于 正常终止 一个程序:

  • _exit : 立刻进入内核
  • exit : 先执行一些 清除处理 (包括调用 执行各终止处理程序关闭所有标准I/O流 等),然后进入内核
#include <stdlib.h>

/**
 *
 * 先执行一些清除处理,然后进入内核终止程序
 *
 * status: 程序终止返回状态
 *
 */
void exit(int status);

#include <unistd.h>

/**
 * 立刻进入内核终止程序
 *
 * status: 程序返回状态
 *
 */
void _exit(int status);
exit函数总是执行一个标准I/O库的清除关闭操作:对于所有打开流调用fclose函数,这使得缓存中的所有数据都被刷新(写到文件上)

终止状态

exit和_exit 都带一个整型参数,称之为 终止状态 。如果出现下述状况,则该进程的 终止状态是末定义 的:

  1. 调用这些函数时 不带终止状态
  2. main执行了一个 无返回值的return语句
  3. main执行 隐式返回

下列经典性的C语言程序是不完整的,其返回状态未定义

#include <stdio.h>

main ()
{
    printf ("hello, world \n");
}

大多数UNIX shell都提供 检查一个进程终止状态 的方法:

$ ./src/environ/hello 
hello, world 

# 返回状态未定义
$ echo $?
14

向执行此程序的进程 返回终止状态 0

return 0;
或者
exit(0);

atexit

终止处理程序 :这些函数将在 调用后exit被自动调用

atexit登记 注册终止处理程序

#include <stdlib.h>

/**
 * 注册程序终止函数
 *
 * func: 终止处理函数的函数指针(地址),无参数,无返回值
 * 
 * return: 若成功则为 0, 若出错则为非0
 *
 */
int atexit(void (*func)(void));

其中 atexit 的参数是一个 函数地址 :当调用此函数时 无需向它传送任何参数也不期望它返回一个值

      照ANSIC的规定一个进程可以登记多至32个函数
示例

以下示例说明了如何使用atexit函数

       注意:main函数使用了return来隐式调用exit函数
#include "apue.h"

static void my_exit1(void);
static void my_exit2(void);

int main(void)
{
    if (atexit(my_exit2) != 0)
        err_sys("can't register my_exit2");

    if (atexit(my_exit1) != 0)
        err_sys("can't register my_exit1");
    if (atexit(my_exit1) != 0)
        err_sys("can't register my_exit1");

    printf("main is done\n");
    return(0);
}

static void my_exit1(void)
{
    printf("first exit handler\n");
}

static void my_exit2(void)
{
    printf("second exit handler\n");
}

exit以 登记相反的顺序 调用终止函数

$ ./src/environ/doatexit 

main is done
first exit handler
first exit handler
second exit handler

同一函数如果被登记多次,则也会被调用多次

小结

  • 内核使程序执行:调用 exec 函数
  • 进程 自愿 终止: 显式隐式 地调用 exit_exit 函数
    • exit函数:首先调用各终止处理程序,然后按需多次调用fclose关闭所有打开流
  • 进程 非自愿 终止:通过 信号量

命令行参数

调用 exec 函数可将 命令行参数 传递给被 执行的新程序

打印所有命令行参数

ANSIC和POSIX.1都要求 argv[argc] 是一个空指针结尾,所以 循环可以通过空指针判断来中断

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;
    //echo all command line args
    for(i = 0; argv[i] != NULL; i++)
        printf("argv[%d]: %s\n", i, argv[i]);

    return 0;

}
argv[i]  != NULL 等价于 i < argc 

argv的第一个字符串:被执行的程序路径名

$ ./src/environ/arg arg1 TEST foo

argv[0]: ./src/environ/arg
argv[1]: arg1
argv[2]: TEST
argv[3]: foo

C程序内存模型

存储器布局

C程序一直由下列几部分组成:

正文段

CPU执行的 机器指令 部分,也被称为 代码段

      通常正文段是可共享的,所以即使是经常执行的程序(如文本编辑程序、C编译程序、shell等)在存储器中也只需有一个副本

      另外正文段常常是只读的,以防止程序由于意外事故而修改其自身的指令

初始化数据段

此段也被称为 数据段 ,它包含了程序中 需赋初值的变量任何函数之外声明会把变量的初值 存放在初始化数据段中:

int maxcount=99;

非初始化数据段

通常将此段称为 bss段 ,在程序开始执行之前, 内核将此段初始化为0

例如: 在函数外声明位初始化的数组

long sum[1000];

自动变量 以及每次 函数调用时所需保存的信息 都存放在此段中

      每次函数调用时其 返回地址、以及调用者的 环境信息(例如某些机器寄存器)都存放在栈中

      然后新被调用的函数在栈上为其 自动 和 临时变量分配存储空间

      通过以这种方式使用栈C函数可以递归调用

在堆中进行 动态存储 分配

      由于历史上形成的惯例,堆位于 非初始化数据段顶(bss) 和栈底(stack bottom)之间

存储器总结

图7-3显示了C程序在内存中的一种典型安排方式:

memory.png

对于VAX上的4.3+BSD而言

1. 正文段从0位置开始,栈顶则在0x7fffffff之下开始,堆顶和栈底之间未用的虚地址空间很大
2. 栈一般是从高地址往低地址增长
3. 堆一般是从低地址往高地址增长
4. 末初始化数据段的内容并不存放在磁盘程序文件中,需要存放在磁盘程序文件中的段只有正文段和初始化数据段
#size(1)命令报告正文段、数据段和bss段的长度(单位:字节):
$ size /usr/bin/cc /bin/sh
   text    data     bss     dec     hex filename
   7537     800       0    8337    2091 /usr/bin/cc
 712531   37360   18656  768547   bba23 /bin/sh

共享库

现在很多UNIX系统支持 共享库 。共享库使得 可执行文件中不再需要包含常用的库函数 ,而只需在 所有进程都可存取的存储区中保存这种库例程的一个副本

  • 程序第一次执行或者第一次调用某个库函数时,用 动态连接 方法将程序与共享库函数相链接。这 减少了每个可执行文件的长度 ,但 增加了一些运行时间开销
  • 可以用库函数的新版本代替老版本无需对使用该库的程序重新链接编辑 (假定参数的数目和类型都没有发生改变)

动态内存管理

分配内存

ANSI C说明了三个用于存储空间动态分配的函数:

  1. malloc分配指定字节数的存储区 ,此存储区中的初始值不确定
  2. calloc为指定长度的对象分配能容纳其指定个数的存储空间 ,该空间中的每一位(bit)都 初始化为0
  3. realloc更改以前分配区的长度(增加或减少)

    • 当增加长度时可能需 将以前分配区的内容移到另一个足够大的区域 ,而新增区域内的初始值则不确定
    #include <stdlib.h>
    /**
     * 根据所指定的size分配空间
     *
     * size: 分配空间大小
     *
     * return: 成功时返回 空间的首址,失败时返回 NULL
     *
     */
    void *malloc(size_t size);
    
    /**
     * 分配nobj 个 size大小的连续空间
     *
     * nobj: object数量
     * size: object大小
     *
     * return: 成功时将该段内存 全部清零并返回 其首址,失败时返回 NULL
     *
     */
    void *calloc(size_t nobj, size_t size);
    
    /**
     * 为已分配的ptr重新分配一块大小为newsize的空间
     *
     * ptr: 已经分配的空间指针
     * newsize: 新的空间大小
     *
     * return: 成功时返回空间的首址,失败时返回NULL;
     *
     */
    void *realloc(void *ptr, size_t newsize);
    

释放内存

free释放 ptr指向的存储空间

#include <stdlib.h>
/**
 * 释放指定的ptr对应的地址空间
 *
 * ptr: 已经分配的地址空间指针
 *
 * return: 无返回
 *
 */
void free(void *ptr)

实现细节

分配函数通常通过系统调用 sbrk(2) 实现来实现

  • 分配函数所返回的指针一定是 适当对齐 的,遵守 最苛刻的对齐要求 ,使其可用于任何数据对象
  • 分配函数所返回的指针可 用于任何指针 ,不需要强制转换
  • 大多数实现所分配的 存储空间比所要求的要稍大一些 ,额外的空间用来记录管理信息:
    • 分配块的长度
    • 指向下一个分配块的指针等等
  • free函数 被释放的空间通常被送入可用存储区池 ,以后再调用分配函数时再分配,但是这 并不会减小进程的存储开销
      也就是说free释放的空间并不会返回给内核,供其他程序使用

注意事项

  • 分配函数返回的指针 不应该参与任何指针运算
  • realloc可能会 移动存储区 ,任何指向原来分配区内部的指针都可能失效
  • realloc的最后一个参数是 存储区的newsize(新长度) ,而不是新、旧长度之差
  • free调用完毕 不会设置对应的ptr指针为NULL ,若再次直接访问ptr指向的地址是不安全的操作
  • 释放一个已经释放了的块调用free时所用的指针不是三个alloc函数的返回值 都会发生段异常
  • 分配而不再使用的堆空间,应尽快通过free回收 ,否则会出现 内存泄漏

alloca函数

alloca :类似于malloc,但是不是在堆上分配空间,而是在函数对应的 栈内分配空间

  • 优点:函数调用结束, 自动释放
  • 缺点:并 不是所有系统都支持

环境变量表

每个程序都接收到一张环境表。与参数表类似, 环境表 也是一个 字符指针数组 ,其中每个指针包含一个字符串的地址。全局变量 environ包含了该指针数组的地址

extern char **environ;

如果该环境包含五个字符串,那么它看起来可能如图7-2中所示:

env.png

  • 环境指针environ
  • 环境表 :environ指向的 指针数组 ,以 NULL 结尾
  • 环境变量 :各指针指向的 字符串 ,以 '\0' 结尾

按照惯例环境由:

name=value

这样的字符串组成,这与图7-2中所示相同。 大多数环境变量名完全由大写字母组成 ,但这也只是一个惯例

常见环境变量

POSIX.1和XPG3定义了某些环境变量。表7-1列出了由这两个标准定义并受到SVR4和4.3+BSD支持的环境变量:

           
  变量     
           
        标准                   实现                     
   说明     
            
  POSIX.1     XPG3       SVR4      4.3+BSD 
  HOME          •          •          •          •        初始目录  
  LANG          •          •          •                   本地语言  
  LC_ALL        •          •          •                   本地编码  
LC_COLLATE      •          •          •                   排序编码  
LC_CTYPE        •          •          •                   输入编码  
LC_MONETARY     •          •          •                   货币编码  
LC_NUMERIC      •          •          •                   数字编码  
  LC_TIME       •          •          •                   日期编码  
  LOGNAME       •          •          •          •         登录名   
   PATH    
           
    •     
          
    •     
          
    •     
          
    •     
          
可执行文件搜
索路径      
   TERM         •          •          •          •        终端类型  
    TZ          •          •          •          •        本地时区  

环境变量函数

除了取环境变量值,有时也需要设置环境变量,或者是改变现有变量的值,或者是增加新的环境变量。但并不是所有系统都支持这种能力。表7-2列出了由不同的标准及实现支持的各种函数:

          
   变量   
          
              标准                        实现        
  ANSI C     POSIX.1     XPG3       SVR4     4.3+BSD  
  getenv       •          •          •          •          •     
  putenv                 可能        •          •          •     
  setenv                                                   •     
 unsetenv                                                  •     
 clearenv                可能                                    

getenv

getenv读取 环境变量

#include <stdlib.h>
/**
 * 根据给定的环境变量名字返回其值
 *
 * name: 环境变量名
 *
 * return: 指向 与name关联的value的指针,若未找到则为 NULL
 *
 */
char *getenv(const char *name);
      注意:应当使用 getenv 从环境中取一个环境变量的值,而不是直接存取environ指针!

putsenv, setenv, unsetenv

putenv :使用形式为 name=value字符串将其放到环境表 中。如果name 已经存在 ,则 先删除其原来的定义

#include <stdlib.h>

/**
 * 把变量放进环境表中
 *
 * str: 形式为name=value的字符串
 *
 * return: 若成功则为 0,若出错则为 非0
 *
 */
int putenv(char *str);

setenv :设置名字为 name的环境变量值为value 。如果在环境中name已经存在,那么:

  • rewrite非0 ,则首先 删除其现存的定义
  • rewrite为0 ,则 不删除其现存定义 (name不设置为新的value,而且也不出错)

    #include <stdlib.h>
    
    /**
     * 设置名字为name的环境变量其值为value
     *
     * name: 环境变量名字
     * value: 要设置的环境变量值
     * rewrite: 是否覆盖
     *
     * return: 若成功则为 0,若出错则为 非0
     *
     */
    int setenv(const char *name, const char *value, int rewrite);
    

    unsetenv删除环境变量名为name的定义

    • 即使环境表中不存在对应的变量也不会出错
    #include <stdlib.h>
    
    /**
     * 解除环境变量定义
     *
     * name: 环境变量名
     *
     * return: 无返回
     *
     */
    void unsetenv(const char *name);
    

实现细节

环境表 (指向实际name=value字符串的指针数组)和环境字符串典型地存放在 进程存储空间的顶部 (栈之上)

  1. 删除: 找到对应的字符串指针 ,然后将 所有环境表中后面元素 指向原来的 下移一个位置
  2. 替换:
    • 新value的长度 少于或等于 现存value的长度 :只要 在原字符串所用空间中写入新字符串
    • 如果 新value的长度 大于 原长度 :必须调用 malloc新字符串分配空间 ,然后将新字符写入该空间中,最后使 环境表中针对name的指针指向新分配区
  3. 增加:首先调用 mallocname=value 分配空间 ,然后将 该字符串写入此空间 中,然后:
    • 第一次增加一个新name:必须调用 malloc新的指针表分配空间 。将 原来的环境表复制到新分配区 ,并将指向新 name=value指针存在该指针表的表尾 ,接着将一个 NULL指针存在其后 ,最后使 environ 指向新指针表
    • 非第一次增加一个新name:则可知以前已调用malloc在堆中为环境表分配了空间,所以只要调用 realloc 分配 比原空间多存放一个指针的空间 。然后将该指向新 name=value 字符串的指针存放在该表表尾 ,最后在 后面跟着一个NULL指针
      这意味着:如果第一次在环境表中增加一个frame,环境表会被移至堆中

      但是此表中的大多数指针仍指向栈顶之上的各name=value字符串

栈间跳转

cmd_add

先看以下程序的骨干部分:

  1. 主循环是从标准输入读一行,然后调用 do_line 处理
  2. do_line该函数调用 get_token 从该输入行中取下一个记号
    • 假设每一行中的第一个记号对应于某条命令,比如记号是'5'对应加法,就调用cmd_add函数
    • cmd_add函数继续调用get_token获得其余参数,最后执行的加法逻辑
#include "apue.h"

#define TOK_ADD    5

void    do_line(char *);
void    cmd_add(void);
int     get_token(void);

int main(void)
{
    char    line[MAXLINE];

    while (fgets(line, MAXLINE, stdin) != NULL)
        do_line(line);
    exit(0);
}

char    *tok_ptr;       /* global pointer for get_token() */

void do_line(char *ptr)     /* process one line of input */
{
    int     cmd;

    tok_ptr = ptr;
    while ((cmd = get_token()) > 0) {
        switch (cmd) {  /* one case for each command */
        case TOK_ADD:
            cmd_add();
            break;
        }
    }
}

void cmd_add(void)
{
    int     token;

    token = get_token();
    /* rest of processing for this command */
}

int get_token(void)
{
    /* fetch next token from line pointed to by tok_ptr */
}

图7-4显示了调用了cmd_add之后栈的大致使用情况:

stack.png

自动变量的存储单元在每个函数的栈桢中

  • 数组linemain的栈帧
  • 整型cmddo_line的栈帧
  • 整型tokencmd_add的栈帧
     一个经常会遇到的问题是:如何处理非致命性的错误?

     例如cmd_add函数发现一个错误,比如说一个无效的数
     那么它可能先打印一个出错消息,然后希望忽略输入行的余下部分,返回main函数并读下一输入行

     如果不得不以检查返回值的方法逐层返回,那就会变得非常麻烦

解决这种问题的方法就是使用 非局部跳转 。这不是在一个函数内的普通的C语言goto语句,而是在 栈上跳过 若干调用帧 返回到当前函数调用路径上的一个函数内

setjmp, longjmp

  • setjmp栈定位信息 保存到 jmp_buf变量 env
    • jmp_buf:一种 特殊数据类型 ,某种形式的 数组 ,其中存放在调用 longjmp 时能用来 恢复栈状态的所有信息 ,其中包括 当初的寄存器上的值
  • longjmp跳转到保存好的变量 env ,然后返回 val

    • 多个longjmp 跳到 同一个setjmp处 时,就可以 通过返回的不同val值进行区别
    #include <setjmp.h>
    
    /**
     * 设置一个以 env 为标识的栈定位点
     *
     * env: 跳转点
     *
     * return: 若直接调用则为 0,若从longjmp返回则为 非0
     *
     */
    int setjmp(jmp_buf env);
    
    /**
     * 于跳转到env所定位的setjmp函数处
     *
     * env: 跳转点
     * val: 跳转回去后setjmp的返回值
     *
     * return: 无返回
     *
     */
    void longjmp(jmp_buf env, int val);
    

cmd_add2

main函数中 设定跳转点 jmpbuffer ,其 当前返回值是0

jmp_buf jmpbuffer;

int main(void)
{
    char    line[MAXLINE];

    if (setjmp(jmpbuffer) != 0)
        printf("error");
    while (fgets(line, MAXLINE, stdin) != NULL)
        do_line(line);
    exit(0);
}

在cmd_add2函数中如果出错,则使用 longjmp 跳转jmpbuffer 保存的 栈定位点 上,这时候 setjmp返回的值是1 ,也就是 cmd_add2中longjmp的第二个参数 。同样可以在 get_token 函数内调用 longjmp(jmpbuffer,2) ,这使得 main函数可以对 不同的longjmp调用点 进行区别对待

void cmd_add2(void)
{
    int     token;

    token = get_token();
    if (token < 0)      /* an error has occurred */
        longjmp(jmpbuffer, 1);
    /* rest of processing for this command */
}

volatile变量

全局和静态变量的值在执行longjmp后保持不变

     当发生跳转后,在main函数中的自动变量和寄存器变量的状态如何?

     当longjmp返回到main函数时,这些变量的值是否能恢复到以前调用setjmp时的值(即滚回原先值)
     或者这些变量的值保持为调用do_line时的值(do_line调用cmd_add,cmd_add又调用longjmp)?

存放在寄存器内的变量会回滚 到调用setjmp的时候,而 存放在内存中的变量并不会回滚

如果有一个自动变量,而又 不想使其值滚回 ,则必须定义其为 volatile

#include <setjmp.h>

static void f1(int, int, int);
static void f2(void);

static jmp_buf jmpbuffer;

int main(void) 
{
    int count;
    register int val;
    volatile int sum;

    count = 2;
    val = 3;
    sum = 4;

    if(setjmp(jmpbuffer) != 0){
        printf("atfer longjmp: count = %d, val = %d, sum = %d\n", count, val, sum);
        exit(0);
    }

    count = 97;
    val = 98;
    sum = 99;
    f1(count, val, sum);
}

static void f1(int i, int j, int k)
{
    printf("in f1(): count = %d, val = %d, sum = %d\n", i, j, k);
    f2();
}

static void f2(void) 
{
    longjmp(jmpbuffer, 1);

}

从下面测试代码可以看出:

  • 不开启编译优化 ,哪怕寄存器变量都会被保存在内存中,跳转回来后这些变量 都不回滚
  • 而开启优化后 只有volatile变量才保存在内存中其他变量 都可能被 保存到寄存器 中,这些变量往往都会 回滚到原来的值

    #不适用编译器优化的时候
    $ cc -o volatileExample1 volatileExample.c
    $ ./volatileExample1 
    
    in f1(): count = 97, val = 98, sum = 99
    #不优化的变量保存在内存中,这些变量不会回滚,还是修改后的值
    atfer longjmp: count = 97, val = 98, sum = 99
    
    #使用编译器优化的时候
    $ cc -O -o volatileExample2 volatileExample.c
    $ ./volatileExample2
    
    in f1(): count = 97, val = 98, sum = 99
    #编译优化后变量会尽量保存到寄存器,自动、寄存器变量将回滚到原来,但是volatile变量还是使用修改后的,因为volaile变量永远保存在内存中
    atfer longjmp: count = 2, val = 3, sum = 99
    
     如果要编写可移植的非局部跳转,则必须使用volatile属性!

自动变量陷阱

open_data的函数:打开了一个标准I/O流,然后为该流设置缓存

#include    <stdio.h>

#define DATAFILE    "datafile"

FILE *open_data(void)
{
    FILE    *fp;
    char    databuf[BUFSIZ];  /* setvbuf makes this the stdio buffer */

    if ((fp = fopen(DATAFILE, "r")) == NULL)
        return(NULL);
    if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0)
        return(NULL);
    return(fp);     /* error */
}
     当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用

     但是标准I/O库函数仍将使用原先为databuf在栈上分配的存储空间作为该流的缓存

     这就产生了冲突和混乱

因此 自动变量的函数已经返回 后, 必须不再引用 这些自动变量。如果还需这些变量,那就:

  1. extern : 全局 变量
  2. static : 静态 变量
  3. malloc / calloc : 使用 堆上的动态分配内存

资源限制

  • getrlimit查询 进程的资源限制
  • setrlimit更改 进程的资源限制
#include <sys/resource.h>

/**
 * 查询资源限制
 *
 * resource: 代表资源的常量
 * rlptr: 资源限制结构指针
 *
 * return: 若成功则为0,若失败则为 非0
 *
 */
int getrlimit(int resource, struct rlimit *rlptr);

/**
 * 修改资源限制
 *
 * resource: 代表资源的常量
 * rlptr: 资源限制结构指针
 *
 * return: 若成功则为 0,若失败则为 非0
 *
 */
int setrlimit(int resource, const struct rlimit *rlptr);

resource常量

resource参数取下列值之一:

Table 2: 资源限制常量
资源常量名 支持系统 说明
RLIMIT_CORE SVR4及4.3+BSD core文件的最大字节数,若其值为0则阻止创建core文件
RLIMIT_CPU SVR4及4.3+BSD CPU时间的最大量值(秒),当超过此软限制时,向该进程发送SIGXCPU信号
RLIMIT_DATA SVR4及4.3+BSD 数据段的最大字节长度。初始化数据、非初始化数据以及堆的总和
RLIMIT_FSIZE SVR4及4.3+BSD 可以创建的文件的最大字节长度。当超过此软限制时,则向该进程发送SIGXFSZ信号
RLIMIT_MEMLOCK 4.3+BSD 锁定在存储器地址空间(尚未实现)
RLIMIT_NOFILE SVR4 每个进程能打开的最多文件数
RLIMIT_NPROC 4.3+BSD 每个实际用户ID所拥有的最大子进程数
RLIMIT_OFILE 4.3+BSD 与RLIMIT_NOFILE相同
RLIMIT_RSS 4.3+BSD 最大驻内存集字节长度(RSS)
RLIMIT_STACK SVR4及4.3+BSD 栈的最大字节长度
RLIMIT_VMEM SVR4 可映照地址空间的最大字节长度
     注意:并非所有资源限制都受到每个系统的支持

rlimit结构

在更改资源限制时,须遵循下列三条规则:

  1. 任何一个进程都可将一个 软限制 更改为 小于或等于硬限制
  2. 任何一个进程都可 降低硬限制值 ,但它必须 大于或等于软限制值 ,这种降低对普通用户而言是 不可逆反
  3. 只有 超级用户 可以 提高 硬限制

    struct rlimit {
        rlim_t rlim_cur; /* soft limit: current limit */
        rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */
    };
    

一个 无限量的限制 由常数 RLIM_INFINITY 指定

实例

打印linux系统支持的某些进程资源的当前软限制和硬限制:

#include "apue.h"
#include <sys/resource.h>

#define FMT "%10ld  "
#define doit(name)  pr_limits(#name, name)

static void pr_limits(char *, int);

int main(void)
{
#ifdef  RLIMIT_AS
    doit(RLIMIT_AS);
#endif
    doit(RLIMIT_CORE);
    doit(RLIMIT_CPU);
    doit(RLIMIT_DATA);
    doit(RLIMIT_FSIZE);
#ifdef  RLIMIT_LOCKS
    doit(RLIMIT_LOCKS);
#endif
#ifdef  RLIMIT_MEMLOCK
    doit(RLIMIT_MEMLOCK);
#endif
    doit(RLIMIT_NOFILE);
#ifdef  RLIMIT_NPROC
    doit(RLIMIT_NPROC);
#endif
#ifdef  RLIMIT_RSS
    doit(RLIMIT_RSS);
#endif
#ifdef  RLIMIT_SBSIZE
    doit(RLIMIT_SBSIZE);
#endif
    doit(RLIMIT_STACK);
#ifdef  RLIMIT_VMEM
    doit(RLIMIT_VMEM);
#endif
    exit(0);
}

static void pr_limits(char *name, int resource)
{
    struct rlimit   limit;

    if (getrlimit(resource, &limit) < 0)
        err_sys("getrlimit error for %s", name);
    printf("%-14s  ", name);
    if (limit.rlim_cur == RLIM_INFINITY)
        printf("(infinite)  ");
    else
        printf(FMT, limit.rlim_cur);
    if (limit.rlim_max == RLIM_INFINITY)
        printf("(infinite)");
    else
        printf(FMT, limit.rlim_max);
    putchar((int)'\n');
}

运行结果:

$ ./src/environ/rlimitExample

# 资源名      软限制    硬限制
RLIMIT_AS       (infinite)  (infinite)
RLIMIT_CORE              0  (infinite)
RLIMIT_CPU      (infinite)  (infinite)
RLIMIT_DATA     (infinite)  (infinite)
RLIMIT_FSIZE    (infinite)  (infinite)
RLIMIT_LOCKS    (infinite)  (infinite)
RLIMIT_MEMLOCK       65536       65536  
RLIMIT_NOFILE         1024        4096  
RLIMIT_NPROC         15591       15591  
RLIMIT_RSS      (infinite)  (infinite)
RLIMIT_STACK       8388608  (infinite)
     资源限制影响到调用进程并由其子进程继承

     这就意味着为了影响一个用户的所有后续进程,需要将进程资源放在 shell 中进行 

事实上sh,bash也都具有 ulimit(1) 命令来查看和修改进程资源限制: 

$ ulimit -a #查看所有bash下的进程资源限制
core file size          (blocks, -c) 0 # RLIMIT_CORE
data seg size           (kbytes, -d) unlimited # RLIMIT_DATA
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited # RLIMIT_FSIZE
pending signals                 (-i) 15591 
max locked memory       (kbytes, -l) 64 # RLIMIT_MEMLOCK
max memory size         (kbytes, -m) unlimited # RLIMIT_RSS
open files                      (-n) 1024 # RLIMIT_NOFILE
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192 # RLIMIT_STACK
cpu time               (seconds, -t) unlimited # RLIMIT_CPU
max user processes              (-u) 15591 # RLIMIT_NPROC
virtual memory          (kbytes, -v) unlimited # RLIMIT_AS
file locks                      (-x) unlimited # RLIMIT_LOCKS

Next:进程控制

Previous:系统文件

Home:目录