7 . 11 getrlimit和setrlimit函数

7 . 11 getrlimitsetrlimit函数

每个进程都有一组资源限制,其中某一些可以用getrlimitsetrlimit函数查询和更改。

#include <sys/time.h>

#include <sys/resource.h>

int getrlimit(int resource, struct rlimit * rlptr) ;

int setrlimit(int resource, const struct rlimit * rlptr) ;

//两个函数返回:若成功则为0,若出错则为非0

      这两个函数在Single UNIX Specification中定义为XSI扩展。进程的资源限制通常是在系统初始化时由进程0建立的,然后由每个后续进程继承,没有实现都可以用自己的方法对各种限制做出调整。

对这两个函数的每一次调用都指定一个资源以及一个指向下列结构的指针。

struct rlimit {

rlim_t rlim_cur; /* soft limit: current limit */

rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */

} ;

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

(1) 任何一个进程都可将一个软限制更改为小于或等于其硬限制

(2) 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对普通用户而言是不可逆反的。

(3) 只有超级用户可以提高硬限制。

一个无限量的限制由常数R L I M _ I N F I N I T Y指定。

这两个函数的resource参数取下列值之一。注意并非所有资源限制都受到SVR44 . 3 + BSD的支持。

• RLIMIT_AS   进程可用存储区的最大总长度(字节),这会影响sbrk函数和mmap函数;

• RLIMIT_CORE    c o r e文件的最大字节数,若其值为0则阻止创建c o r e文件。

• RLIMIT_CPU      CPU时间的最大量值(),当超过此软限制时,向该进程发送S I G X CPU信号。

• RLIMIT_DATA    数据段的最大字节长度。

• RLIMIT_FSIZE     可以创建的文件的最大字节长度。当超过此软限制时,则向该进程发送S I G X F S Z信号。

• RLIMIT_LOCKS 一个进程可持有的文件锁的最大数。

• RLIMIT_MEMLOCK 锁定在存储器地址空间(尚未实现)

• RLIMIT_NOFILE 每个进程能打开的最多文件数。更改此限制将影响到s y s c o n f函数在参数_ S C _ O P E N _ M A X中返回的值。

• RLIMIT_NPROC       每个实际用户I D所拥有的最大子进程数。更改此限制将影响到s y s c o n f函数在参数_ S C _ C H I L D _ M A X中返回的值。

• RLIMIT_OFILE SVR4R L I M I T _ N O F I L E相同。

• RLIMIT_RSS 最大驻内存集字节长度(R S S)。如果物理存储器供不应求,则内核将从进程处取回超过R S S的部分。

• RLIMIT_STACK 栈的最大字节长度。

• RLIMIT_VMEM 可映照地址空间的最大字节长度。这影响到m m a p函数。

资源限制影响到调用进程并由其子进程继承。这就意味着为了影响一个用户的所有后续进程,需将资源限制设置构造在shell之中。确实,Bourne shellKorn Shell具有内部ulimit命令,C shell具有内部l i m i t命令。( umaskchdir函数也必须是shell内部的。)

实例

程序7 – 8打印由系统支持的对所有资源的当前软限制和硬限制。为了在各种实现上编译该程序,我们已经有条件地包括了各种不同的资源名,另请注意,有些平台定义rlim_tunsigned long long而非unsigned long,对于此种平台,必须使用不同的printf格式。

#include <stdio.h>

#include <stdlib.h>



#if defined(BSD) || defined(MACOS)

#include <sys/time.h>

#define FMT "%10lld "

#else

#define FMT "%10ld "

#endif



#include <sys/resource.h>



#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)

perror("getrlimit error");

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');

}

注意,在doit宏中使用了新的ANSI C字符串创建算符( # ),以便为每个资源名产生字符串值。例如:

doit ( RLIMIT_CORE ) ;

这将由C预处理程序扩展为:

pr_limits(“RLIMIT_CORE”, RLIMIT_CORE);

Ubuntu上运行该程序得到:

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       1024

RLIMIT_NPROC    (infinite)  (infinite)

RLIMIT_RSS      (infinite)  (infinite)

RLIMIT_STACK       8388608 (infinite)

7.10 setjmp和longjmp函数

7.10 setjmplongjmp

C中,goto语句是不能跨越。而种跳功能的是函setjmplongjmp这两个数对生在很深的嵌套函数调用中的非常有用

一下程序7 – 4的骨干部分。其主循从标1行,然后do_line理每一入行。然后get_token从该输入行中取下一个记号。一行中的第一个记号假定是某种形式的一命令,于是switch句就实现命令选择。我的程序只理一命令,此命令cmd_add

程序7.4 进行命令处理的典型程序骨架

#include <stdio.h>

#include <stdlib.h>

#include <setjmp.h>



#define TOK_ADD 5

#define MAXLINE 128



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) {

case TOK_ADD:

cmd_add();

break;

}

}

}



void cmd_add(void)

{

int token;



token = get_token();

//reset of processing for this command

}



int get_token(void)

{

//fetch next token from line pointer to by tok_ptr

}

程序7 – 4命令、确定命令的型、然后用相数处理每一命令这类程序中是非常典型的。7 – 4示了用了cmd_add之后的大致使用情

动变量的存储单元在每栈桢数组linemain栈帧中,整型cmddo_line栈帧中,整型tokencmd_add栈帧中。

如上所述,种形式的安排是非常典型的,但并不要求非如此不可。并不一定要向低地址方向充。某些系统对栈有提供特殊的硬件支持,此C实现可能要用接表实现栈帧

编写如程序7 – 4这样的程序中遇到的一个问题是,如何理非致命性的错误。例如,若cmd_add数发现个错误,譬如个无效的,那么它可能先打印一消息,然后希望忽略入行的余下部分,返回main下一入行。用C言比较难做到(在本例中, cmd_add只比main两个层次,在有些程序中往往低五或更多次。)如果我不得不以查返回值的方法逐返回,那就会变得很麻

决这问题的方法就是使用非局部goto—— setjmplongjmp。非局部表示不是在数内的普通的Cg o t o句,而是在上跳若干,返回到前函数调用路上的一

#include <setjmp.h>

int setjmp(jmp_buf env) ;

返回:若直接则为0,若longjmp返回则为0

void longjmp(jmp_buf env, int val) ;

希望返回到的位置setjmp,在本例中,此位置在main中。因直接,所以其返回值0setjmp参数env是一特殊j m p _ b u f型是某种形式数组,其中存放在longjmp能用恢复栈状态的所有信息。一般, env量是全局,因另一中引用它

当检查到一个错误时,例如在cmd_add中,两个参数调longjmp。第一是在setjmp所用的env;第二v a l,是0值,它成为从setjmp返回的值。使用第二个参数的原因是于一setjmp可以有多longjmp。例如,可以在cmd_add中以v a l1longjmp,也可在get_token中以v a l2longjmp。在main中,setjmp的返回值就12,通过测试返回值就可判cmd_addget_tokenlongjmp

再回到程序例中,程序7 – 5出了修改后的maincmd_add(其他两个do_lineget_token更改)

#include <stdio.h>

#include <stdlib.h>

#include <setjmp.h>



#define TOK_ADD 5

#define MAXLINE 128



jmp_buf jmpbuffer;



void do_line(char *);

void cmd_add(void);

int get_token(void);



int main(void)

{

char line[MAXLINE];



if (setjmp(jmpbuffer) != 0)

printf("error");



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) {

case TOK_ADD:

cmd_add();

break;

}

}

}



void cmd_add(void)

{

int token;



token = get_token();

//reset of processing for this command



if (token < 0) //an error has occurred

longjmp(jmpbuffer, 1);

//reset of processing for this command

}



int get_token(void)

{

//fetch next token from line pointer to by tok_ptr

}

mainsetjmp,它所需的信息j m p b u ff e r中并返回0。然后do_line,它又c m _ a d d,假定在其中检测到一个错误。在cmd_addlongjmp之前,的形式如7 – 4中所示。但是longjmp使main数时的情,也就是抛弃cmd_adddo_line栈帧longjmp造成mainsetjmp的返回,但是,一次的返回值是1 ( longjmp的第二个参数)

      那么此时的栈帧结构就是只有main的栈帧,其他函数的都已经被抛弃了。

7.10.1 、寄存器和易失

下一个问题是:“在main中,自动变量和寄存器量的状态如何?longjmp返回到main数时量的值是否能恢复到以前setjmp的值(即回原先值),或者量的值保持为调do_line的值( do_linecmd_addcmd_addlongjmp ) ?不幸的是,问题的回答是“看情”。大多数实现并不些自动变量和寄存器量的值,而所有则说的值是不确定的。如果你有一动变量,而又不想使其值回,可定具有volatile性。全局和静态变量的值在longjmp保持不

下面我程序7 – 6明在longjmp后,自动变量、寄存器量和易失量的不同情。如果以不优化和优化此程序分别进编译,然后行它得到的果是不同的:

$ cc testjmp.c 行任何优化的编译

$ a . o u t

in f1(): count = 97, val = 98, sum = 99

after longjmp: count = 97, val = 98, sum = 99

$ cc -O testjmp.c 行全部优化的编译

$ a . o u t

in f1(): count = 97, val = 98, sum = 99

after longjmp: count = 2, val = 3, sum = 99

注意,易失( s u m )不受优化的影,在longjmp之后的值,是它在f 1的值。在我所使用的setjmp ( 3 )册页明存放在存器中的具有longjmp的值,而在CPU和浮寄存器中的恢复为调setjmp的值。就是在行程序7 – 5察到的值。

行优化,所有个变量都存放在存器中(亦即v a l的寄存器存储类被忽略)。而优化c o u n tv a l都存放在寄存器中(即使c o u n t并末register ) , volatile仍存放在存器中。通过这一例子要理解的是,如果要编写使用非局部跳的可移植程序,使volatile

程序7-6 longjmp,寄存器和易失量的影

#include <stdio.h>

#include <stdlib.h>

#include <setjmp.h>



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

static void f2(void);



static jmp_buf jmpbuffer;

static int globval;





int main(void)

{



int autoval;

register int regival;

volatile int volaval;

static int statval;



globval = 1;

autoval = 2;

regival = 3;

volaval = 4;

statval = 5;



if (setjmp(jmpbuffer) != 0) {

printf("after longjmp : n");

printf("globval = %d, autoval = %d, regival = %d,"

"volaval = %d, statval = %dn", globval, autoval,

regival, volaval, statval);

exit(0);

}

//change varivables after setjmp ,but before longjmp

globval = 95;

autoval = 96;

regival = 97;

volaval = 98;

statval = 99;



f1(autoval, regival, volaval, statval); //never returns

exit(0);

}



static void f1(int i, int j, int k, int l)

{

printf("in f1():n");

printf("globval = %d, autoval = %d, regival = %d,"

"volaval = %d, statval = %dn", globval, i, j, k, l);

f2();

}



static void f2(void)

{

longjmp(jmpbuffer, 1);

}


 

注意,全局、静态和易失变量不受优化的影响。在调用longjmp后,他们的值是最近所呈现的值。存放在存储器中的变量将具有longjmp时的值,而在CPU和浮点数寄存器中的变量则恢复为调用setjmp时的值。

      在程序中,某些printf的格式字符串可能不适宜安排在程序文本的一行中,我们没有将其分成多个printf调用,而是使用了ISO C的字符串连接功能,于是两个字符串序列:

“string1” “string2”

等价于

string1string2

7.10.2 自动变量的潜在问题

前面已经说明了处理栈帧的一般方式,与此相关我们现在可以分析一下自动变量的一个潜在出错情况。基本规则是说明自动变量的函数已经返回后,就不能再引用这些自动变量。在整个UNIX手册中,关于这一点有很多警告。

程序7 – 7是一个名为open_data的函数,它打开了一个标准I / O流,然后为该流设置缓存。程序7-6 自动变量的不正确使用

#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;

}



int main(int argc, char *argv[])

{

FILE *fp;

fp = open_data();

return 0;

}

问题是:当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准I / O库函数仍将使用原先为databuf在栈上分配的存储空间作为该流的缓存。这就产生了冲突和混乱。为了改正这一问题,应在全局存储空间静态地(staticextern ),或者动态地(使用一种a l l o c函数)为数组databuf分配空间。

7.9 环境变量

7.9

如同前述,境字符串的形式是:

name = value

UNIX核并不种字符串的意,它的解完全取于各个应用程序。例如, shell使用了大量的量。其中某一些在登录时动设置(如HOMEUSER等),有些由用户设置。我通常在一shell文件中量以控制shell作。例如,若置了M A I L PAT H它告Bourne shellK o r n Shell到哪里去查看件。

ANSI C了一getenv,可以用其取量值,但是该标准又称环境的容是由实现的。

#include <stdlib.h>

char *getenv(const char *name) ;

//返回:指向与name关联的value的指针,若未找到则为NULL

注意,此函返回一,它指向name = value字符串中的value。我们应当使用getenv从环中取一个环量的值,而不是直接存取environ

POSIX . 1X P G 3了某些量。表7 – 1列出了由这两个标准定并受到SVR44 . 3 + BSD支持的量。SVR44 . 3 + BSD使用了很多依实现量。FIPS 151-1要求登shell要定义环HOMELOGNAME

7-1

                       

HOME                起始目

L A N G                     本地名

L C _ A L L                 本地名

L C _ C O L L A T E      本地排序名

L C _ C T Y P E            本地字符分

L C _ M O N E T A R Y  本地货币编辑

L C _ N U M E R I C  本地编辑

L C _ T I M E         本地日期/时间格式名

LOGNAME         

N L S P A T H           消息模板序列

P A T H                     搜索可行文件的路

T E R M                   

T Z                 时区

除了取量值,有也需要量,或者是改变现量的值,或者是增加新量。(在下一章将会了解到,我能影的是程及其后生成的子程的境,但不能影程的境,通常是一shell程。管如此,修改境表的能力仍然是很有用的。)不幸的是,并不是所有系都支持种能力。

修改环境表三的原型是:

#include <stdlib.h>

int putenv(const char *str) ;

int setenv(const char *name, const char *value, int rewrite) ;

//两个函数返回:若成功则为0,若出错则为非0

void unsetenv(const char *name) ;

的操作是:

• putenv取形式name = value的字符串,其放到境表中。如果name存在,除其原的定

• setenvnamevalue。如果在境中name存在,那么( a )rewrite0首先除其存的定( b )rewrite0除其存定name新的value,而且也不出)。

• unsetenvname的定。即使不存在种定也不算出

些函在修改境表是如何行操作的呢?回一下7 – 3,其中,境表(指向实际name = value字符串的指针数组)和境字符串典型地存放在程存部(之上)。除一字符串很简单——只要先找到,然后所有后都向下移一位置。但是增加一字符串或修改一个现存的字符串就比以上的空程存

部,所以充,即法向上充,也法向充。

l  如果修改一个现存的name:

n  如果新value度少于或等于value度,只要在原字符串所用空

入新字符串。

n  如果新value度大于原度,须调malloc新字符串分配空,然后字符中,然后使境表中针对name的指指向新分配

l  如果要增加一新的name操作就更加复。首先,mallocname = value分配空,然后将该符串入此空

n  如果是第一次增加一name须调malloc新的指表分配空境表复制到新分配,并指向新name = value的指存在表的表尾,然后又空指存在其后。最后使environ指向新指表。再看一下7 – 3,如果原境表位于栈顶之上(是一种常),那么必须将此表移至堆中。但是,此表中的大多仍指向栈顶之上的各name = value字符串。

n  如果不是第一次增加一name可知以前已malloc在堆中为环境表分配了空,所以只要realloc,以分配比原空多存放一的空。然后将该指向新name = value字符串的指存放在表表尾,后面跟空指

7.8 存储器分配

7.8 器分配

ANSI C明了三用于存间动态分配的函

(1) malloc。分配指定字节数的存储区。此存储区中的初始值不确定

(2) calloc指定度的象,分配能容其指定个数的存中的每一位( b i t )都初始化0

(3) realloc。更改以前分配(增加或)增加,可能需以前分配容移到另一大的域,以便在尾端提供增加的存储区,而新增的初始值不确定

#include <stdlib.h>

void *malloc(size_t size) ;

void *calloc(size_t n o b j, size_t size) ;

void *realloc(void *ptr, size_t newsize) ;

//三个函数返回:若成功则为非空指针,若出错则为NULL

void free(void *ptr)

分配函所返回的指一定是当对齐的,使其可用于任何象。例如,在一定的系上,如果最苛刻的对齐要求是double则对齐8的倍的地址,那么返回的指应这样对齐

为这a l l o c都返回通用指针void *所以如果在程序中包括了< s t d l i b . h > (包含了函原型),那么们将这些函返回的指针赋与一不同型的指针时,不需要作转换

freeptr指向的存。被放的空通常被送入可用存储区池,以后可在分配函数时再分配。

realloc使我可以增、以前分配(最常的用法是增加该区)。例如,如果先分配一可容纳长5 1 2数组的空,并在填充它,但又发现realloc如果在储区后有足的空可供可在原存储区位置上向高地址方向充,并返回它的同的指值。如果在原存储区有足的空realloc分配另一大的存储区将现存的5 1 2元素数组容复制到新分配的存储区为这种存储区可能位置,所以不应当使用任何指指在该区习题4 . 1 8示了在getcwd中如何使用realloc,以理任何度的路名。程序17-28是使用realloc的另一例子,用其可以避免使用编译时固定度的数组

注意,realloc的最后一个参数是存储区newsize(),不是新、旧长度之差。作特例,ptr是一空指realloc的功能与malloc相同,用于分配一指定newsize的存储区

此功能是由ANSI C新引的。如果送一NULLrealloc的早期版本。早期版本允realloc自上次malloc, reallocc a l l o c放的种技巧可回溯到V 7,它利用malloc实现紧缩的搜索策略。4 . 3 + BSD仍支持功能,而SVR4不支持。种功能不再使用。

些分配例程通常通s b r k ( 2 )统调实现统调()程的堆(见图7 – 3 )mallocfree的一个样实现请见K e r n i g h a nRitchie [1988] 8 . 7

s b r k可以充或小一个进程的存,但是大多mallocfree实现都不程的存放的空可供以后再分配,但保持在malloc池中而不返回给内

应当注意的是,大多数实现所分配的存比所要求的要稍大一些,外的空来记录管理信息——分配度,指向下一分配的指等等。就意味如果写过已分的尾端,则会后一的管理信息。型的错误灾难性的,但是因为这错误很快就暴露出,所以也就很难发现指向分配的指向后移也可能的管理信息。

在动态分配的缓冲区前或后进行写操作,破坏的可能不仅仅是该区的管理记录信息,在动态分配的缓冲区前后的存储区很可能用于其他动态分配的对象。这些对象与破坏他们的代码可能无关,这造成寻求信息破坏的源头更加困难。

其他可能生的致命性的错误是:放一经释放了的free所用的指不是

a l l o c的返回值等。如若一个进程调用malloc函数,但却忘记调用free函数,那么该进程占用的存储器就会连续增加,这被称为泄露leakage。不调用free函数以释放不使用的空间,那么进程地址空间长度就会慢慢增加,直至不再有空闲空间,此时,由于过度的分页开销,因而使得性能下降。

器分配出,所以某些系提供了些函的另一种实现方法。每次分配函中的任意一free行附加的出错检验。在编辑程序指定个专在程序中就可使用种版本的函。此外有公共可用的源(例如由4 . 3 + BSD所提供的),在编译时使用一特殊志就使附加的时间检查生效。

分配程序的操作某些用程序的时间性能非常重要,所以某些系供了附加能力。例如, SVR4提供了名m a l l o p t的函,它使程可以置一些量,并用它们来控制存分配程序的操作。可使用另一m a l l i n f o的函,以分配程序的操作统计查看所使用系malloc ( 3 )册页,弄清楚些功能是否可用。

可以替代存储器分配程序的函数和库。

1.    libmalloc

2.    vmalloc

3.    快速分配quick-fit

4.    alloca函数。

可以查查相关的信息。

7.7 共享库

7.7 共享

在,很多UNIX支持共享Arnold [1986] 明了系V上共享的一早期实现G i n g e l l[1987] 则说明了S u n O S上的另一个实现。共享使得可行文件中不再需要包含常用的,而只需在所有程都可存取的存储区中保存例程的一副本。程序第一次者第一次用某个库数时,用动态连接方法程序与共享接。少了每文件的度,但增加了一时间开销。共享的另一是可以的新版本代替老版本而使用该库的程序重新编辑(假定参数目和型都生改)

不同的系使用不同的方法使明程序是否要使用共享。比典型的有c c ( 1 )l d ( 1 )命令的可选项。作为长度方面化的例子,下列可行文件(典型的h e l l o . c程序)先用共享方式建:

$ ls -1 a.out

-rwxrwxr-x 1 stevens 104859 Aug :2 52 a1.4out

$ size a.out

text data bss dec hex

49152 49152 0 98304 18000

如果我编译此程序使其使用共享行文件的正文和据段的度都小:

$ ls -1 a.out

-rwxrwxr-x 1 stevens 24576 Aug : 226 1a4.out

$ size a.out

text data bss dec hex

8192 8192 0 16384 4000