进程环境
-
main函数
C程序总是从main函数开始执行,main函数的原型:
int main(int argc, char *argv[])
内核在执行C程序(exec函数调用)时,在调用main函数前先调用一个特殊的启动程序,启动程序从内核取得命令行参数和环境变量,为调用main函数做好准备。
-
进程终止
正常终止:
(1)从main返回
启动程序是这样编写的,从main返回后立即调用exit函数。启动程序调用main函数的形式可能是(实际上是用汇编编写的):
exit(main(argc, argv));
(2)调用exit函数
exit函数先调用各终止处理程序,然后关闭所有打开流(fclose)。终止处理函数,可用atexit来进行登记。
exit函数最后调用_exit或_Exit函数。
(3)调用_exit或_Exit
这两个函数立即进入内核。
#include <stdlib.h> void exit(int status); void _Exit(int status); //上述是ISO C说明的 #include <unistd.h> void _exit(int status); //由POSIX.1说明的
status是终止状态。
(4)最后一个线程从其启动历程返回
(5)从最后一个线程调用pthread_exit
异常终止:
(6)调用abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应
函数atexit:
#include <stdlib.h> int atexit(void (*func)(void));
atexit登记终止处理程序(exit handler),exit调用这些函数的顺序与登记时的顺序相反,同一函数登记多次会被调用多次。
函数启动和终止的流程如图1所示:
图1 一个C程序启动和终止的流程
启动程序的唯一方法是调用一个exec函数,进程自愿终止的唯一方式是显示或隐式(exit)地调用_exit或_Exit,进程也可非自愿地由一个信号终止。
-
命令行参数
当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。
main函数中,argc参数是命令行参数的个数,argv参数指向一个字符指针数组,字符指针指向各个命令行参数。
ISO C和POSIX.1都要求argv[argc]是一个空指针。
-
环境表
每个程序都将收到一张环境表,与argv一样,指向一个字符指针数组,示意图如图2所示。
图2 环境表示意图
environ是环境指针,指向环境表(字符指针数组),环境表的各个指针指向环境字符串,环境字符串由name=value这样的字符串组成。
-
C程序的存储空间布局
C程序由下列几部分组成:正文段、初始化数据段、未初始化数据段(bss段)、栈、堆。
这些段的典型安排方式,如图3所示。
图3 典型的存储空间安排
正文段,是CPU执行的机器指令部分。正文段通常是只读的,以防止程序被修改。
初始化数据段,通常称此段为数据段,包含了程序已赋初值的变量。
未初始化数据段,通常称为bss段(block started by symbol)。
栈,自动变量及函数调用所需保存的信息都存放在此段中。
堆,动态存储分配在堆进行。
-
共享库
共享库使得可执行文件不需要包含公用的库函数,而只需在所有进程中都可引用的存储区中保存这种库函数的一个副本。
程序第一次执行时,用动态链接方法将程序与共享库函数相链接。
图4中展示了典型的helloworld程序,使用共享库和静态库的区别。
可看到用静态库编译出来的可执行文件static,比用共享库编译出来的可执行文件sharedlib,长度要大。
图4 Hello World用静态库和动态库编译的区别
-
存储空间分配
ISO C说明了3个用于存储空间动态分配的函数:这些存储空间分配函数通常用sbrk系统调用实现。
#include <stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); All three return: non-null pointer if OK, NULL on error void free(void *ptr);
(1)malloc,分配指定字节数的存储区,初始值不确定。
(2)calloc,为指定数量指定长度的对象分配存储空间。该空间的每个bit都初始化为0。
(3)realloc,增加或减少以前分配区的长度。增加长度时,可能需要移动到另一个足够大的区域,新增区域内的初始值不确定。
这三个函数所返回的指针一定是适当对齐的。
(4)函数free释放ptr指向的存储空间,被释放的空间通常被送入可用存储区池,后续可用上述3个分配函数再分配。
注:realloc,存储区可能会移动位置,所以不应当使任何指针指在该区域中。
在动态分配的缓冲区前或后进行写操作,破坏的可能不仅仅是该区的管理记录信息,很可能破坏其他动态分配的对象。这些错误很难被发现。
其他可能产生致命性错误的是:
(1)释放一个已经释放了的块
(2)调用free所用的指针不是3个alloc函数的返回值
(3)若一个进程调用malloc函数,却忘记调用free函数,会发生内存泄露。
其他的存储空间分配程序:
libmalloc/vmalloc/quick-fit/jemalloc/TCMalloc/alloca等
-
环境变量
环境字符串的形式:
name=value
UNIX内核并不查看这些字符串,它们的解释完全取决于各个应用程序。
可用getenv获取环境变量值:
#include <stdlib.h> char *getenv(const char *name); Returns: pointer to value associated with name, NULL if not found
应当使用getenv从环境中取一个指定环境变量的值,而不是直接访问environ。
除了获取环境变量值,还需要设置环境变量(改变或新增环境变量),当前进程只能影响当前进程及其后生成和调用的任何子进程的环境,不能影响父进程的环境。
#include <stdlib.h> int putenv(char *str); Returns: 0 if OK, nonzero on error int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); Both return: 0 if OK, −1 on error
putenv默认删除原来的定义,setenv由变量rewrite控制是否删除原来的定义。unsetenv删除name的定义,即使不存在这种定义也不出错。
setenv必须分配存储空间,putenv可将传递给它的参数字符串直接放到环境中。
这些函数在修改环境表时底层是怎么操作的?
1、删除一个字符串,只要先找到该指针,然后将所有后续指针都向环境表首部顺次移动一个位置。
2、修改一个现有的name
a、新value长度小于现有value的长度,则只要将新字符串复制到原字符串所用的空间中。
b、新value长度大于现有value的长度,则必须调用malloc为新字符串分配空间,新字符串放到该空间,环境表针对name的指针指向新分配空间。
3、新增一个新的name
需要调用malloc为新字符串分配新空间,字符串放到该空间。但还需要为环境表分配新空间。
a、第一个次新增一个name
需要为新的指针表调用malloc分配空间,将原来的指针表复制到新的分配区,将新的字符串指针存放在表尾,NULL指针存放在其后,最后environ指向新环境表(指针表)。
b、不是第一个新增一个name
以前已调用malloc在堆中为环境表分配了空间,所以只需要realloc,以分配多一个指针的空间,将新的字符串指针存放在表尾,NULL指针存放在其后。
-
函数setjmp和longjmp
goto语句是不能跨越函数跳转的,setjmp和longjmp这两个函数可以实现非局部goto(在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中),这对发生在很深层嵌套函数调用中的出错情况非常有用。
#include <setjmp.h> int setjmp(jmp_buf env); Returns: 0 if called directly, nonzero if returning from a call to longjmp void longjmp(jmp_buf env, int val);
在希望返回到的位置调用setjmp,然后在其他地方【当前函数调用路径上的某一个函数中】调用longjmp则会返回到此位置,实现跨函数跳转。
setjmp函数,参数env是一个特殊类型jmp_buf,是某种形式的数组,用于存放调用longjmp时能用来恢复栈状态的所有信息。通常将env变量定义为全局变量。
longjmp函数,第一个参数就是在调用setjmp时所用的env,第二个参数是setjmp将返回的值。
jmp_buffer jmpbuffer; int fun(void) { ... if(setjmp(jmpbuffer) != 0) printf("error"); ... } int fun2(void) { ... longjmp(jmpbuffer, 1); ... }
在fun中直接调用setjmp函数,其返回0,设置longjmp返回的位置。
在fun2中调用longjmp,将返回到fun1调用setjmp的地方继续运行,此时setjmp的返回值是longjmp设置的val值。通过val值可区分不同的错误。
longjmp跳转后自动变量和寄存器变量的状态如何变化?
a、回滚到setjmp时的值,回滚到原先值
b、保持longjmp时的值,即最新值
这个问题是不确定的。
如果有一个自动变量,而又不想其值回滚,则可定义为volatile属性;全局变量或静态变量的值在执行longjmp时也保持不变。
-
函数getrlimit和setrlimit
每个进程都有一组资源限制,其中一些可用getrlimit和setrlimit函数查询和更改。
#include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struct rlimit *rlptr); Both return: 0 if OK, −1 on error