7.2 main函数
int main(int argc, char *argv[]);
其中,argc是命令行参数的数目,argv是指向参数的各个指针所构
成的数组。
当内核执行C程序时,在调用main前先调用一个特殊的启动例程。
可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。
7.3 进程终止
有8种方式使进程终止(termination),其中 5种为正常终止:
(1)从main返回;
(2)调用exit;
(3)调用_exit或_Exit;
(4)最后一个线程从其启动例程返回(11.5节);
(5)从最后一个线程调用pthread_exit(11.5节)。
异常终止有3种方式,它们是:
(6)调用abort(10.17节);
(7)接到一个信号(10.2节);
(8)最后一个线程对取消请求做出响应
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个退出函数都带一个整型参数,称为终止状态(或退出状态,exit status)。
2.函数atexit
一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exit handler),并调
用atexit函数来登记这些函数。
#include <stdlib.h>
int atexit(void (*func)(void));
返回值:若成功,返回0;若出错,返回非0
7.4 命令行参数
ISO C和POSIX.1都要求argv[argc]是一个空指针。
7.5 环境表
每个程序都接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址:
extern char **environ;
7.6 C程序的存储空间布局
•正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell 等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防 止程序由于意外而修改其指令。
•初始化数据段。通常将此段称为数据段,它包含了程序中需明确 地赋初值的变量。例如, C程序中任何函数之外的声明: int maxcount = 99; 使此变量以其初值存放在初始化数据段中。
•未初始化数据段。通常将此段称为bss段,这一名称来源于早期汇 编程序一个操作符,意思是“由符号开始的块”(block started by symbol),在程序开始执行之前,内核将此段中的数据初始化为0或空 指针。函数外的声明long sum[1000]; 使此变量存放在非初始化数据段中。
•栈。自动变量以及每次函数调用时所需保存的信息都存放在此段 中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器 寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动 和临时变量分配存储空间。通过以这种方式使用栈,C递归函数可以工 作。递归函数每次调用自身时,就用一个新的栈帧,因此一次函数调用 实例中的变量集不会影响另一次函数调用实例中的变量。
•堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于未初始化数据段和栈之间。
7.7 共享库
7.8 存储空间分配
(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);
3个函数返回值:若成功,返回非空指针;若出错,返回NULL
void free(void *ptr);
如若一个进程调用malloc函数,但却忘记调用free函数,那么该进程占用的存储空间就会连 续增加,这被称为泄(leakage)。
7.9 环境变量
ISO C定义了一个函数getenv,可以用其取环境变量值,但是该标准
又称环境的内容是由实现定义的。
#include <stdlib.h>
char *getenv(const char *name);
返回值:指向与name关联的value的指针;若未找到,返回NULL
clearenv不是Single UNIX Specification的组成部分。它被用来删除环境表中的所有项。中间3个函数的原型是:
#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已经存在,那么(a)若rewrite非0,则首先删除其现有的定义;(b)若rewrite为0,则不删除其现有定义(name不设置为新的value,而且也不出错)。
•unsetenv删除name的定义。即使不存在这种定义也不算出错。
7.10 函数setjmp和longjmp。。
在C中,goto语句是不能跨越函数的,而执行这种类型跳转功能的是函数setjmp和longjmp。这两个函数对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的。
解决这种问题的方法就是使用非局部goto——setjmp和longjmp函数。非局部指的是,这不是由普通的C语言goto语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include <setjmp.h>
int setjmp(jmp_buf env);
返回值:若直接调用,返回0;若从longjmp返回,则为非0
void longjmp(jmp_buf env, int val);
执行main时,调用setjmp,它将所需的信息记入变量jmpbuffer中并 返回0。然后调用do_line,它又调用cmd_add,假定在其中检测到一个错 误。在 cmd_add 中调用 longjmp 之前。但是 longjmp使栈反绕到执行main函数时的情况,也就是抛弃了cmd_add和 do_line的栈帧。调用 longjmp 造成 main 中setjmp 的返回, 但是,这一次的返回值是 1 (longjmp的第二个参数)。
1.自动变量、寄存器变量和易失变量
我们已经了解在调用 longjmp 后栈帧的基本结构,下一个问题 是:“在main函数中,自动变量和寄存器变量的状态如何?”
当longjmp 返回到main 函数时,这些变量的值是否能恢复到以前调用setjmp时的值 (即回滚到原先值),或者这些变量的值保持为调用do_line时的值 (do_line调用cmd_add,cmd_add 又调用longjmp)?
遗憾的是,对此问 题的回答是“看情况”。大多数实现并不回滚这些自动变量和寄存器变 量的值,而所有标准则称它们的值是不确定的。如果你有一个自动变 量,而又不想使其值回滚,则可定义其为具有volatile属性。声明为全局 变量或静态变量的值在执行longjmp时保持不变。
7.11 函数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
进程的资源限制通常是在系统初始化时由0进程建立的,然后由后续进程继承。可以用自己的方法对资源限制做出调整。
(1)任何一个进程都可将一个软限制值更改为小于或等于其硬限制值。
(2)任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对普通用户而言是不可逆的。
(3)只有超级用户进程可以提高硬限制值。
...
...