7.1引言
下一章将介绍进程控制原语,在此之前需先了解进程的环境。在本章中我们将说明:当执行一通程序时,其main函数是如何被调用的,命令行参数是如何传送给执行程序的;典型的存储器布局是什么样式;如何分配另外的存储空间;进程如何使用环境变量;进程终止的不同方式等。另外,我们也将说明longjmp和setjmp函数以及它们与栈的交互作用。在本章结束之前,我们将查看进程的资源限制。
7.2main函数
一道C程序总是从main函数开始执行。main函数的原型是:
int main (int argc,char * argv[]);
其中,argc是命令行参数数,argv是指向参数的各个指针所构成的数组。在7.4节中我们将对命令行参数进行说明。在系统核起动一道C程序时(用一个exec函数,我们将在8.9节中说明exec函数),在调用main前先调用一个特殊的起动例程。可执行程序文件将此起动例程指定为程序的起始地址这是由连接编辑程序设置的,而连接编辑程序则由C编译程序(通常是cc)调用。
起动例程从系统核取得命令行参数和环境变量值,然后为调用main函数作好安排。
7.3进程终止
有五种方式使进程终止:
1.正常终止
(a)从main返回
(b)调用exit
(c)调用 xit
2.异常终止
a)调用abort(第十章)
(b)由一个信号终止(第十章)
上节提及的起动例程是这样编写的,使得从main返回后即调用exit函数。如果将起动例程以C代码工表示(实际上该例程常常用汇编语言编写),则它调用main函数的形式可能是:
exit(main(argc,argv));
exit和 xit函数
exit和 xit函数正常终止一个程序:xit立即进入系统核,exit则先执行一些清除处理(包括的用执行各终止处P2程序,关闭所有标准I/O流等)
#include
void exit(int status);
#include > void xit (int status);
我们将在8.5节讨论这两个函数对其它进程,例如终止进程的父、子进程的影响。使用不同头文件的原因是:exit是由ANSIC说明的,而exit则是由Posix.1说明的。
由于历史原因,exit函数总是执行一个标准I/O库的清除关闭操作:对于所有打开流调用fclose函数。回忆5.5节,这造成所有在缓存中的数据都被刷新(写到文件上)。
exit和 xit都带一个整型参数,我们称此为终止状态。大多数Unix Shell都提供检查一个进程终止状态的方法。如果(a)若调用这些函数时不带终止状态,或(b)main执行了一个无返回值的return语句,或(c)main执行隐式返回,则该进程的终止状态是末定义的。这就意味着,下列经典性的C语言程序:
#indude
main ()
{
printf (hello,world \n);
}
是不完整的,因为main函数没有使用return语句返回(隐式返回),它在返回到C的起动例程时并没有返回一个值(终止状态)。另外,若使用
return(0);
或者
exit(0);
则向启动执行此程序的进程(常常是一个shell进程)返回终止状态0。另外,main函数的说明实际上应当是:
int main(void)
在下一章,我们针会了解到一个进程如何一道程序执行,如何等待执行该程序的进程完成,然后取得其终止状态。
将main说明为返回一个整型以及用exit代替return,对某些C编译程序和Unix Lint(1)程序而言会产生不必要的警告信息。问题是这些编译程序并不了解在main中的exit与return语句的作用相同。警告信息可能是control reaches end of nonvoid function。(控制到达非void函数的结束处),避开这种警告信息的一种方法是:在main中使用return语句而不是exit。但是这样做的结果是使我们不能用Unix。
grep公用程序来找出一道程序中的所有exit调用。另外一个解决方法是将main说明为返回void而不是int,然后的旧调用exit。这也避开了编译程序的警告,但从程序设计角度看却并不正确。在本章中,我们将main表示为返回一个整型,因为这是ANSI C和POSIX.1所定义的。我们将不理会编译程序不必要的警告。
atexit函数
按照ANSIC的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,并用atexit函数来登记这些函数。
#include
int atexit(void (func)(void));
Returns:0 if OK,nonzero on error
返回:若成功为0,出错为非0
其中,参数(*func)(void)是一个函数地址,当调用此函数时无需向它传送任何参数,也不期望它返回一个值。exit以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次。
终止处理程序这一机制是由ANSIC新引进的。SVR4和4.3+BSD都提供这种机制。系统V的早期版本和4.3BSD则都不提供此机制。
按照ANSIC和POSIX.1,exit首先调用各终止处理程序,然后按需多次调用fclose,关闭所有打开流。图7.1摘要显示了一个C程序是如何起动的,以及它终止的各种方式。P164图7.1一个C程序是如何起动和终止的。
注意,系统核使一道程序执行的唯一方法是调用一种exec函数。一个进程自愿终止的唯一方法是显示或隐式地(调用exit)调用exit。一个进程也可非自愿地由一个信号使其终止(在图7.1中没有显示)。
实例
程序7.1说明了如何使用atexit函数。执行程序7.1产生:
$ aout
main is done
first exit handler
first exit handler
second exit handler
注意,在main中我们没有调用exit,而是用了return语句。P165程序7.1终止处理程序的实例。
7.4命令行参数
当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。这是Unixshell的一部分常规操作。在前几章的很多实例中,我们已经看到了这一点。
实例
程序7.2将其所有命令行参数都回送到标准输出上(Unix echo(1)程序不回送第0个参数。)编译此程序,并将其可执行代码文件定名为echoarg,则:
$ /echoarg arg1 TEST foo
argv[0]: /echoarg
argv[1]: arg1
argv[2]: TEST
argv[3]: foo
P166
程序7.2将所有命令行参数回送到标准输出ANSIC和POIX.1都要求argv[argc]是一个空指针。这就使我们可以将参数处理循环改写为:
for(i=0;arg[i]!=NULL;i++)
关注此文的读者还看过: