第七章 进程环境
各位观众,今天节目的主要内容有:
1、 当执行程序时,其main函数是如何被调用的?
2、 命令行参数是如何传送给执行程序的?
3、 典型的存储器而已是什么样式?
4、 如何分配另外的存储空间?
5、 进程如何使用环境变量?
6、 各种不同进程终止方式
另外,本期特别报导:
longjmp和setjmp函数以及它们与栈的交互作用;进程的资源限制。
请看详细报导:
1、 main函数
C程序总是从main函数开始执行,当内核执行C程序时,在调用main前先会调用一个特殊的启动例程,启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用 main函数做好安排。
那么一个程序启动了,又是如何终止的呢,请看继续报导:进程终止。
2、 进程终止
有8种方式可使进程终止,其中5种为正常终止,它们是:
1. 从main返回
2. 调用exit
3. 调用_exit或_Exit
4. 最后一个线程从其启动例程返回(第11.5期节目中,将会做详细报导)
5. 最后一个线程调用pthread_exit(第11.5期节目中,将会做详细报导)
异常有3种方式,它们是:
1. 调用abort(第10.17期节目中,将会做详细报导)
2. 接到一个信号并终止(第10.2期节目中,将会做详细报导)
3. 最后一个线程对取消请求做出响应(第11.5和12.7期节目中,将会做详细报导)
上一节提到到的启动例程会使main返回后立即调用exit函数,如果启动例程以C代码形式表示(事实上启动例程是常常使用汇编语言编写),那么它调用main函数的形式就可能是如下:
exit(main(argc,argv));
下面来看一看exit函数相关资料:
前面已经看到,exit 和他两个兄弟 _exit _Exit三种方式可以正常终止程序,那么这三个兄弟区别在哪里?请看下面:
_exit和_Exit立即进入内核,而exit则先执行一些清理处理(包括调用执行各终止处理程序,送亲所以有标准I/O流等),然后进入内核,所以exit要稍微勤快点,干的事稍微多一点。
exit对于标准I/O库的清理关闭操作主要是通过为所有打开流执行fclose函数,回忆5.5期节目中对fclose的介绍,这会造成所有缓冲的输出数据都被冲洗。
这三个兄弟,工作时都会带一个整型参数,这个参数将被用于标明程序的终止状态,也就是说,参数是什么值,那么,程序的终止状态就是什么值,对将对于我们判断一处程序问题出在哪里将非常有用。但是如果进程终止时出现以下三种情况:
1. 调用exit函数时没有带终止态
2. main执行了一个无返回值的return语句
3. main没有声明返回类型为整型
程序的终止态就是未定义的。
另外,若main返回类型是整型,且在执行到最后一条语句时返回或隐式返回,那么该进程终止状态将是0。main函数中,返回一整型什与该值调用exit是等价的,于是,在main函数中:
exit(0);
等价于
return(0);
2.1 atexit函数
进程结束前期我们还可以调用一些函数做一些处理,这些函数被称为终止处理程序( exit handler),一个进程最多可以登记32个函数,函数将由exit自动调用,登记是由 atexit完成。定义如下:
#include <stdlib.h>
int atexit(void (*func)(void));
需要说明的是,exit调用终止处理程序的顺序与它们登记的时候顺序相反,同一函数若被登记多次,则也会被多次调用。另,exit执行时,首选调用各终止程序,然后再调用fclose,如果程序调用了exec函数族中任一函数,则将清除所有已安装的终止处理程序。
注意,内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止的唯一方法是显式或隐式地(通过调用exit)调用_exit或_Exit。进程也可以非自愿地由一个信号使其终止。
3、 命令行参数
当一个程序执行时,调用exec的进程可将命令行参数传递给新程序,例子可以参照 main(int argc,char *argv[]),不再过多讨论,另外需要说明一点是argv[argc]是一个空指针。
4、 C程序存储空间布局
C程序典型存储器安排如下图:
说明:
正文段:由CPU执行的机器指令部份,通常是正文段可以共享的且通常是只读的,以防止程序由于意外而修改其自身指令。
初始化数据段:通常被称为数据段,包含了程序中需明确地赋初值的变量。
非初始化数据段:程序开始前,内核将此段中的数据初始化为0或空指针。如以下声明:long sum[100];将会被放于非初始化数据段。
堆与栈不再细说。
另外,未初始化数据段内容并不放在磁盘上的程序文件中,需要存放的只有正文段和初始化数据段。