UNIX进程环境

Posted 吉吉boy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UNIX进程环境相关的知识,希望对你有一定的参考价值。

UNIX编程第七章

C程序总是从main函数开始执行。main的原型是:

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

argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。

当内核执行C程序时(使用exec系列函数),在调用main之前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址--这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

 

进程终止:

5种正常终止

-从main返回;

-调用exit;

-调用_exit或_Exit;

-最后一个线程从其启动例程返回;

-从最后一个线程调用pthread_exit;

3种异常终止:

-调用abort;

-接到一个信号;

-最后一个线程对取消请求做出响应;

 

调用main的启动例程,在main返回后立即调用exit函数。如果将启动例程以C代码形式表示,则调用main的形式可能是:

exit(main(argc, argv));

 

退出函数:_exit和_Exit立即进入内核,exit则先执行一些清理处理,然后返回内核。

#include<stdlib.h>

void exit(int status);

void _Exit(int status);

#include<unistd.h>

void _exit(int status);

exit函数总是执行一个标准I/O库的清理关闭操作:对于所有打开流调用fclose函数。这造成输出缓冲中的所有数据都被冲洗(写到文件上)。

3个退出函数都带一个整型参数,称为终止状态(exit status)。

如果调用return或exit系列函数时不带终止状态或 main执行了一个无返回值的return语句或 main没有声明返回类型为整型,则该进程的终止状态是未定义的。但是,若main的返回类型是整型,并且main执行到最后一条语句时返回,那么该进程的终止状态是0.

main函数返回的整型值与用该值调用exit是等价的。

 

函数atexit:

按照ISO C规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exit handler),并调用atexit函数来登记这些函数。

#include<stdlib.h>

int atexit(void (*func)(void));

成功时返回0,出错返回非0.

atexit函数的参数是一个函数地址,当调用此函数时无须向它传递任何参数,也不期望他返回一个值。atexit调用这些函数的顺序与它们登记时侯的顺序相反,同一函数如果登记多次也会被调用多次。

exit首先调用各终止处理程序,然后关闭(通过fclose)所有打开流。

如若程序调用exec函数中任一函数,则将清除已安装的终止处理程序。

内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显示或隐式地(通过调用exit)调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。

 

argv[argc]是一个空指针。

 

环境表:

每个程序都有一张环境表。环境表也是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址:

extern char **environ;

其中每个字符串的结尾处都显示地有一个null字节。我们称environ为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串。

环境由:name=value这样的字符串组成(Linux中的环境变量格式)。

通常用getenv和setenv函数来访问特定的环境变量,而不是用environ变量。但是如果要查看整个环境,还是要使用environ指针。

 

C程序的存储空间布局:

-正文段。这是由CPU执行的机器指令部分。通常正文段是共享的,且正文段是只读的,防止程序由于意外而修改其指令。

-初始化数据段。通常将此段声明为数据段,它包含了程序中需明确地赋初值的变量。

-未初始化数据段。bss段(block start by symbol),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。

-栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动变量分配存储空间。通过这种方式使用栈,C递归函数可以工作。递归函数每次调用自身时,就用一个新的栈帧,因此一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。

-堆。通常在堆中进行动态存储分配。堆位于未初始化数据段和栈之间。

 

未初始化数据段的内容并不存放在磁盘程序文件中,其原因是,内核在程序开始运行前将它们都设置为0.需要存放在磁盘程序文件中的段是正文段和初始化数据段。

size命令报告程序的正文段、初始化数据段、未初始化数据段的长度。

从高地址到低地址依次是:命令行参数和环境变量、栈(往低地址增长)、堆(向上往高地址增长)、bss段、已初始化数据段、正文段。

 

共享库:

共享库使得可执行文件中不再需要包含公用的库函数,而只需要在所有进程都可引用的存储区中保存这种库例程的一个副本。程序第一次执行或第一次调用某个库函数时,用动态连接的方法将程序与共享库函数相链接。这减少了每个可执行文件的长度,但增加了一些运行时间开销。这种时间开销发生在该程序第一次被执行时,或者每个共享库函数第一次被调用时。共享库的另一个优点是可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑(假定参数的数目和类型都未改变)。

 

存储空间分配:

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

-malloc,分配指定字节数的存储区。此存储区的初始值不确定。

-calloc,为指定数量指定长度的对象分配存储空间。该空间中的每一位都初始化为0.

-realloc,增加或减少以前分配区的长度。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。

#include<stdlib.h>

void *malloc(size_t size);

void *calloc(size_t nobj, size_t size);

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

成功时返回非空指针,失败返回NULL。

void free(void*ptr);

这三个分配函数所返回的指针一定是适当对齐的,使其可用于任何数据对象。如int和double对存储区对齐的要求不一样。

函数free释放ptr指向的存储空间,被释放的存储空间通常被送入可用存储区池。

realloc函数可增减以前分配存储区的长度。如果在原存储区后有足够的空间可扩充,则在原存储区位置上向高地址扩充,无须移动任何原先的内容,并返回与传给它相同的指针值。如果在原存储区后没有足够的空间,则realloc分配另一个足够大的存储区,将现有存储区的内容复制到新存储区,释放现有存储区,返回新存储区的指针。

realloc第2个参数是存储区的新长度,当ptr为NULL时,realloc的作用与malloc的相同。

可能造成的错误:

释放一个已经释放了的块;调用free时所用的指针不是3个alloc函数的返回值等。如果一个进程调用malloc函数,但却忘记调用free函数,那么该进程占用的存储空间会连续增加,这称为内存泄漏。(leakage)

 

环境变量接口:

#include<stdlib.h>

char*getenv(const char*name);

成功时返回指向与name关联的value的指针,若未找到name则返回NULL。

常用的环境变量有:HOME,home起始目录;LANG,本地名;LOGNAME,登录名;PATH,搜索可执行文件的路径前缀列表;PWD,当前工作目录的绝对路径名;SHELL,用户首选的shell名;

 

#include<stdlib.h>

int putenv(char*str);

成功返回0,出错返回非0;

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

int unsetenv(const char*name);

成功返回0,出错返回-1。

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

-setenv将name设置为value。如果在环境中name已存在,那么,若rewrite非0则首先删除其现有的定义,若rewrite为0则不删除其现有定义(name不设置为新的value而且也不出错)。

-unsetenv删除name的定义。即使不存在这种定义也不算出错。

putenv和setenv参数类型有差异,setenv必须分配储存空间,putenv可以自由地将传递给他的参数字符串直接放到环境中。将存放在栈中的字符串作为putenv的参数会导致出错,原栈地址空间可能会被重写。

环境表(指向name=value字符串的指针数组)和环境字符串通常存放在进程存储空间的顶部。删除一个字符串很简单--只要先在环境表中找到该指针,然后将所有后续指针都指向环境表首部顺次移动一个位置。但是增加一个字符串或修改一个现有的字符串就困难得多。环境表和环境字符串通常占据进程地址空间的顶部,所以他不能再向高地址方向扩展:同时也不能移动在它之下的各栈帧,所以它也不能向低地址方向扩展。

(1)如果要修改一个现有的name:

-如果新value的长度少于或等于现有value的长度,则只要将新字符串复制到原字符串所用的空间中;

-如果新value的长度大于原长度,则必须调用malloc为新字符串分配空间,然后将新字符串复制到该空间中,接着使环境表中针对name的指针指向新分配区。

(2)如果要增加一个新的name,则操作就更复杂。首先,必须调用malloc为name=value字符串分配空间,然后将该字符串复制到此空间中:

-如果这是第一次增加一个新的name,则必须调用malloc为新的指针表分配空间。接着,将原来的环境表复制到新分配区,并将指向新name=value字符串的指针存放在该指针表的表尾,然后将一个空指针存放在其后。最后使environ指向新指针表。将环境表从地址空间顶部移到堆中,但环境表中的指针大多数仍指向地址空间顶部的环境字符串。

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

 

函数setjmp和longjmp:

C中goto不能跨越函数,由setjmp和longjmp执行跳转功能。

自动变量的存储单元在每个函数的栈帧中。非局部跳转是在栈上跳过若干个栈帧,返回到当前函数调用路径上的某一个函数中。

#include<setjmp.h>

int setjmp(jmp_buf env);

若直接调用返回0,若从longjmp调用返回非0.

void longjmp(jmp_buf env, int val);

在希望返回的位置调用setjmp,直接调用setjmp返回为0,setjmp的参数env的类型是一个特殊类型jmp_buf。这一数据类型是某个形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。因为需要在longjmp中引用env变量,所以通常将env变量定义为全局变量。

longjmp第一个参数即setjmp中的env值,第二个参数是具有非0值的val,它是跳转到setjmp的返回值。使用第二个参数可以使一个setjmp拥有多个longjmp,通过测试返回值来判断。

在main函数中,自动变量和寄存器变量的状态如何?大多数实现并不回滚这些自动变量和寄存器变量的值,而所有标准则称它们的值是不确定的。如果你有一个自动变量,而又不想使其值回滚,则可定义其为具有volatile属性。声明为全局变量或静态变量的值在执行longjmp时保持不变。

自动变量存在的问题:当在栈中申请标准I/O的缓冲时,跳转之后栈帧被忽略,但是这段空间仍被作为标准I/O的缓冲区。解决是可以使申请的标准I/O缓冲区为静态全局变量或动态分配空间。

 

函数getrlimit和setrlimit:

每个进程都有一组资源闲置,其中一些可用getrlimit和setrlimit函数查询和更改:

#include<sys/resource.h>

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

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

成功返回0,出错返回非0.

struct rlimit{

  rlim_t rlim_cur;  /*软限制·,当前限制*/

  rlim_t rlim_max;    /*硬限制,rlim_cur可取的最大值*/

};

在更改资源限制时,需要遵守3条规则:

-任何一个进程都可将一个软限制更改为小于或等于其硬件限制值;

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

-只有超级用户进程可以提高硬件限制值。

常量RLIM_INFINITY指定了一个无限量地限制。

参数resource可取以下值:

RLIMIT_AS,进程总的可用存储空间的最大长度(字节)。这影响到sbrk函数和mmap函数。

RLIMIT_CORE,core文件的最大字节数,若其值为0则阻止创建core文件。

RLIMIT_CPU,CPU事件的最大量值(秒),当超过此软限制时,向该进程发送SIGXCPU信号。

RLIMIT_DATA,数据段的最大字节数。是初始化数据、bss数据以及堆的总和。

RLIMIT_FSIZE,可以创建的文件的最大字节长度。当超过此限制时,则向该进程发送SIGXFZE信号。

RLIMIT_MEMLOCK,一个进程使用mlock能够锁定在存储空间中的最大字节长度。

RLIMIT_MSGQUEUE,进程为POSIX消息队列可分配的最大存储字节数。

RLIMIT_NICE,为了影响进程的调度优先级,nice值可设置的最大限制。

RLIMIT_NOFILE,每个进程能打开的最多文件数。更改此限制将影响到sysconf函数在_SC_OPEN_MAX中返回的值。

RLIMIT_NPROC,每个师及用户ID可拥有的最大子进程数。更改此限制将影响sysconf函数在参数_SC_CHILD_MAX中返回的值。

RLIMIT_RSS,最大驻内存集字节长度(resident set size in bytes)。如果可用的物理存储器非常少,则内核将从进程处取回超过RSS的部分。

RLIMIT_SIGPENDING,一个进程可排队的信号最大数量。这个限制是sigqueue函数实施的。

RLIMIT_STACK,栈的最大字节长度。

 

以上是关于UNIX进程环境的主要内容,如果未能解决你的问题,请参考以下文章

Unix环境编程:fork()简述

Unix环境高级编程进程控制

Unix环境——进程管理小结

Unix环境高级编程进程关系

Unix环境高级编程(十三)守护进程

有没有办法改变 Unix 中另一个进程的环境变量?