7.1 引言
本章主要讲解了进程的环境。
7.2 main函数
C程序总是从main
函数开始执行,其函数原型为:
int main(int argc, char *argv[]);
- argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组;
- 当内核执行C程序时(使用一个
exec
函数),在调用main
函数前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main
函数做好安排。
7.3 进程终止
有8种方式使进程终止,其中5种为正常终止,它们是:
- 从
main
返回; - 调用
exit
; - 调用
_exit
或_Exit
; - 最后一个线程从其启动例程返回;
- 从最后一个线程调用
pthread_exit
。
异常终止有3种方式,它们是:
- 调用
abort
; - 接到一个信号;
- 最后一个线程对取消请求做出响应。
启动例程使得从main
返回后立即调用exit
函数。
退出函数
3个函数用于正常终止一个程序:_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个退出函数都带一个整型参数,称为终止状态(或退出状态);
main
函数返回一个整型值与用该值调用exit
是等价的。
atexit函数
一个进程可以登记多至32个函数,这些函数将由exit
自动调用,称为终止处理程序,用atexit
函数来登记这些函数:
#include <stdlib.h>
int atexit(void (*func)(void));
// 返回值:若成功,返回0;若出错,返回非0
atexit
的参数是一个函数地址,当调用此函数时无需向它传递任何参数,也不期望它返回一个值;exit
调用这些函数的顺序与它们登记时候的顺序相反,同一函数如若登记多次,也会被调用多次。
exit
首先调用各终止处理程序,然后关闭(通过fclose
)所有打开流;如若程序调用exec
函数族中的任一函数,则将清除所有已安装的终止处理程序。
- 内核使程序执行的唯一方法是调用一个
exec
函数; - 进程自愿终止的唯一方法是显式或隐式地(通过调用
exit
)调用_exit
或_Exit
; - 进程也可非自愿地由一个信号使其终止。
7.4 命令行参数
- 当执行一个程序时,调用
exec
的进程可将命令行参数传递给该新程序; argv[argc]
是一个空指针。
7.5 环境表
每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址,全局变量environ则包含了该指针数组的地址,称之为环境指针。
extern char **environ;
7.6 C程序的存储空间布局
C程序一直由下列几部分组成:
-
正文段
:这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是使用频繁执行的程序在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其指令; -
初始化数据段
:通常将此段称为数据段,它包含了程序中需明确地赋初值的变量,例如,C程序中任何函数之外的声明:int maxcount = 99;
此变量以其初值存放在初始化数据段中;
-
未初始化数据段
:通常将此段称为bss段(block started by symbol),在程序开始执行之前,内核将此段中的数据初始化为0或空指针,函数外的声明:long sum[1000];
此变量存放在非初始化数据段中;
-
栈
:自动变量以及每次函数调用时所需保存的信息都存放在此段中; -
堆
:通常在堆中进行动态存储分配。
由上图可以看出:
- 栈从高地址向低地址方向增长;
- 未初始化数据段的内容并不存放在磁盘程序文件中;
- 需要存放在磁盘程序文件中的段只有正文段和初始化数据段。
7.7 共享库
- 共享库使得可执行文件中不再需要包含公用的库函数,而只需在所有进程都可引用的存储区中保存这种库例程的一个副本;
- 程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接;
- 共享库减少了每个可执行程序的长度,但增加了一些运行时间开销;
- 共享库使得可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑。
7.8 存储空间分配
3个用于存储空间动态分配的函数:
malloc
:分配指定字节数的存储区,此存储区中的初始值不确定;calloc
:为指定数量指定长度的对象分配存储空间,该空间中的每一位(bit)都初始化为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);
// 3个函数返回值:若成功,返回非空指针;若出错,返回NULL
void free(void *ptr);
- 特别地,若
realloc
的参数ptr为空指针,则realloc
的功能与malloc
相同。
7.9 环境变量
getenv
函数用于获取环境变量值:
#include <stdlib.h>
char *getenv(const char *name);
// 返回值:指向与name关联的value的指针;若未找到,返回NULL
以下3个函数用于设置环境变量:
#include <stdlib.h>
int putenv(char *ptr);
// 返回值:若成功,返回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已经存在,那么:
(a)若rewrite非0,则首先删除其现有的定义;
(b)若rewrite为0,则不删除其现有定义(name不设置为新的value,而且也不出错);unsetenv
删除name的定义,即使不存在这种定义也不算出错。
7.10 函数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变量,所以通常将env变量定义为全局变量;- 当检查到一个错误时,以两个参数调用
longjmp
函数,第一个是在调用setjmp
时所用的env,第二个是具非0值的val,它将成为从setjmp
处返回的值。
自动变量、寄存器变量和易失变量
- 如果不想让自动变量值回滚,可定义其为具有volatile属性;
- 声明为全局变量或静态变量的值在执行
longjmp
时保持不变。
自动变量的潜在问题
- 声明自动变量的函数已经返回后,不能再引用这些自动变量。
7.11 函数getrlimit和setrlimit
每个进程都有一组资源限制,进程的资源限制通常是在系统初始化时由0进程建立的,然后由后续进程继承:
#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 */
};
在更改资源限制时,须遵守下列3条规则:
- 任何一个进程都可将一个软限制值更改为小于或等于其硬限制值;
- 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值,这种降低,对普通用户不可逆;
- 只有超级用户进程可以提高硬限制值。
这两个函数的resource参数取下列值之一:
7.12 实例代码
chapter7