apue:UNIX进程的环境

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

main函数

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

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

    其中,argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。当内核起动C程序时,在调用main前先调用一个特殊的起动例程。可执行程序文件将此起动例程指定为程序的起始地址——这是由连接编辑程序设置的,而连接编辑程序则由C编译程序调用。起动例程从内核取得命令行参数和环境变量值,然后为调用main函数作好安排。

进程终止

    有五种方式使进程终止:
- 正常终止:
1.从main返回。
2.调用exit。
3.调用_exit。
- 异常终止:
1.调用abort(见第10章)。
2.由一个信号终止(见第10章)。

exit和_exit函数

    exit和_exit函数用于正常终止一个程序:_exit立即进入内核,exit则先执行一些清除处理(包括调用执行各终止处理程序,关闭所有标准I/O流等),然后进入内核。

#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);

    exit函数总是执行一个标准I/O库的清除关闭操作:对于所有打开流调用fclose函数。exit和_exit都带一个整型参数,称之为终止状态(exit status)。

atexit函数

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

#include <stdlib.h>
int atexit(void (*func)(void));
/*返回:若成功则为0,若出错则为非0*/

    其中,atexit的参数是一个函数地址,当调用此函数时无需向它传送任何参数,也不期望它返回一个值。exit以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次。
    一个C程序的启动和终止示意图如下:
一个C程序的启动和终止
    注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(调用exit)调用_exit。

命令行参数

    当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。这是UNIXshell的一部分常规操作。

环境表

    每个程序都接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的字符串的地址。

C程序的存储空间布局

    C程序由下列几部分组成:
存储器安排示意图
- 正文段,由CPU执行的机器指令部分。
- 初始化数据段。通常将此段称为数据段,它包含了程序中需赋初值的变量。
- 非初始化数据段。通常将此段称为bss段,意思是“block started by symbol(由符号开始的块)”,在程序开始执行之前,内核将此段初始化为0。
- 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址、以及调用者的环境信息都存放在栈中。然后,新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。
- 堆。通常在堆中进行动态存储分配。堆位于非初始化数据段顶和栈底之间。

共享库

    共享库使得可执行文件中不再需要包含常用的库函数,而只需在所有进程都可存取的存储区中保存这种库例程的一个副本。程序第一次执行或者第一次调用某个库函数时,用动态连接方法将程序与共享库函数相连接。这减少了每个可执行文件的长度,但增加了一些运行时间开销。共享库的另一个优点是可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑。不同的系统使用不同的方法使说明程序是否要使用共享库。

存储器分配

    ANSI C说明了三个用于存储空间动态分配的函数。
(1)malloc。分配指定字节数的存储区。此存储区中的初始值不确定。
(2)calloc。为指定长度的对象,分配能容纳其指定个数的存储空间。该空间中的每一位(bit)都初始化为0。
(3)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)

    这三个分配函数所返回的指针一定是适当对齐的,使其可用于任何数据对象。函数free释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用分配函数时再分配。realloc使我们可以增、减以前分配区的长度(最常见的用法是增加该区)。如果在该存储区后有足够的空间可供扩充,则可在原存储区位置上向高地址方向扩充,并返回传送给它的同样的指针值。如果在原存储区后没有足够的空间,则realloc分配另一个足够大的存储区,将现存的内容复制到新分配的存储区。因为这种存储区可能会移动位置,所以不应当使用任何指针指在该区中。

环境变量

    环境字符串的形式是:name=value。UNIX内核并不关心这种字符串的意义,它们的解释完全取决于各个应用程序。ANSI C定义了一个函数getenv,可以用其取环境变量值,但是该标准又称环境的内容是由实现定义的。

#include <stdlib.h>
char* getenv(const char* name);
/*返回:指向与name关联的value的指针,若未找到则为NULL*/

    除了取环境变量值,有时也需要设置环境变量,或者是改变现有变量的值,或者是增加新的环境变量。

#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已经存在,则先删除其原来的定义。
- setenv将name设置为value。如果在环境中name已经存在,若rewrite非0,则首先删除其现存的定义;若rewrite为0,则不删除其现存定义。
- unsetenv删除name的定义。即使不存在这种定义也不算出错。

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的参数env是一个特殊类型jmp_buf。这一数据类型是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。一般,env变量是个全局变量,因为需从另一个函数中引用它。

自动、寄存器和易失变量

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

自动变量的潜在问题

    自动变量的函数已经返回后,就不能再引用这些自动变量。

getrlimith和setrlimit函数

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

#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*/

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

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

    在更改资源限制时,须遵循下列三条规则:
- 任何一个进程都可将一个软限制更改为小于或等于其硬限制。
- 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对普通用户而言是不可逆反的。
- 只有超级用户可以提高硬限制。
    一个无限量的限制由常数RLIM_INFINITY指定。这两个函数的resource参数取下列值之一。
- RLIMIT_CORE,core文件的最大字节数,若其值为0则阻止创建core文件。
- RLIMIT_CPU,CPU时间的最大量值(秒),当超过此软限制时,向该进程发送SIGXCPU信号。
- RLIMIT_DATA,数据段的最大字节长度。这是图7-3中初始化数据、非初始化数据以及堆的总和。
- RLIMIT_FSIZE,可以创建的文件的最大字节长度。当超过此软限制时,则向该进程发送SIGXFSZ信号。
- RLIMIT_MEMLOCK,锁定在存储器地址空间(尚未实现)。
- RLIMIT_NOFILE,每个进程能打开的最多文件数。
- RLIMIT_NPROC,每个实际用户ID所拥有的最大子进程数。
- RLIMIT_O,FILE与SVR4的RLIMIT_NOFILE相同。
- RLIMIT_RSS,最大驻内存集字节长度(RSS)。如果物理存储器供不应求,则内核将从进程处取回超过RSS的部分。
- RLIMIT_STACK,栈的最大字节长度。
- RLIMIT_VMEM,可映照地址空间的最大字节长度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值