APUE读书笔记-07进程环境(1)
1、简介
在下一章讲述进程控制相关的内容之前,我们需要先看一下单个进程的情况。本章,我们将会看到 main
函数在执行程序的时候是如何被调用到的,命令行参数是如何传递给新的程序的,典型的内存布局是什么样的,以及如何申请额外的内存,进程如何使用环境变量,以及各种终止进程的方法。另外,我们也会看到 longjmp
函数和 setjmp
函数,以及它们和堆栈的交互情况。最后我们通过讲述一个进程的资源限制来结束本章。
译者注
原文参考
2、可执行程序的 main
函数
当一个c程序通过 exec
被执行的时候,会在 main
函数之前首先调用一个 start-up routine
.根据编译时候链接阶段的设置,可执行文件会把这个 start-up routine
作为程序的起始地址。这个 start-up routine
会从内核中获取命令参数(应该对应 main
函数的参数)以及环境变量然后调用 main
函数。
main
函数的声明如下:
int main(int argc, char *argv[]);
这里,参数 argc
是命令行中参数的数目, argv
是指向命令行参数的数组。这个函数的内容是我们自己实现的,依据我们自己的程序功能而有所不同。例如假设 mytest.c
编译生成 mytest
程序,那么输入 mytest arg1 arg2
之后, mytest.c
中的 main
函数的 argc
就是3,其中 argv[0]
为 mytest
, argv[1]
为 arg1
, argv[2]
为 arg2
。具体我们会在7.4中对这些参数进行讲述。
译者注
原文参考
3、进程终止
有8种结束进程的方式,其中:
(1)正常结束的方式有5种:
- 从
main
函数中return
- 调用
exit
函数 - 调用
_exit = 或者 =_Exit
- 最后一个线程中的
start routine
中return
。 - 从最后一个线程中调用
pthread_exit
.
(2)非正常结束的方式有3种:
- 调用
abort
. - 接收到一个信号(
signal
). - 响应最后一个线程的
cancellation
请求。
这里,我们只考虑和线程无关的方法。
当 main
函数 return
的时候会调用到 exit
函数。效果类似在前面提到的 start-up routine
中执行了 exit(main(argc,argv));
实际真正正常结束程序的只有三个函数: exit
, _exit
和 _Exit
.
_exit
和 _Exit
会直接立即返回到内核,而 exit
会首先做一些清理工作(例如关闭打开的 stream
)然后返回到内核(例如可通过调用其他的两个 exit
函数)。
三种 exit
声明如下:
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
这三种函数都返回一个整数表示进程的状态,大多数unix系统都有处理这个返回状态的方式。
如果
- 这三种函数没有指定
status
而被调用 -
main
函数调用了return
却没有在后面指定其返回值 -
main
函数没有指定要返回一个整数进程的exit
状态是不确定的。
如果 main
函数被指定返回的是整数,但是是隐式退出的(没有调用 return
而是自然地到达了函数的结尾),那么返回值是0(这个特性是 c99
新加的特性,以前返回是不确定的)。在 main
函数中调用 exit(0)
和 return 0
的效果是等价的。
进程结束前可以调用用户注册的指定函数:
#include <stdlib.h>
int atexit(void (*func)(void));
我们把自己定义的函数的地址传递到这个函数中去,就会将我们自定的函数注册到进程退出过程中去。当调用 exit
函数退出进程的时候,会按照我们注册的反顺序一次调用我们注册的自定义函数。一个函数被注册了几次,那么 exit
的时候就会被调用几次。 ISO C
允许注册至少 32
个函数( sysconf
函数可以用来确定一个系统最多可以注册多少个退出函数)。
这些退出函数( exit
注册的函数)在 1989 ANSI
之前的系统中(例如 SVR3
和 4.3BSD
)是没有的。
ISO C
和 POSIX.1
首先调用注册的 exit
退出函数,然后(通过 fclose
)关闭所有的 stream.POSIX.1
扩展了 ISO C
的一个地方是:当调用 exec
函数的时候会清除所有的退出注册函数。下图给出了程序启动和终止时候调用的这些函数的关系。
C程序的启动和终止
_exit+---------------------------------------------------------------------+
or | |
_Exit| +----------+ call +-------+ |
<---------| user | ---------->| exit | |
| | |functions |\ / /--------|handler| |
| | +--|----^--+ \ exit / / return +-------+ |
| | return | -------------\ / / |
| | | | (doesn't return)\ / / ...... |
| _exit| | call v / / |
| or | +--v----|--+ exit +--------+< call +-------+ |
| _Exit| | main |--------------->| exit |------------->| exit | |
| <-------|functions |(doesn't return)|function|<-------------|handler| |
| | | +--|----^--+ +----|---+\ return +-------+ |
| | | return | ^ | ^ \ |
| | | | | / | \ \ |
| | | | call exit / | \ \ |
| | | +--v----|--+ ------------/ | \ \ call +-----------+ |
| | | |C start-up| /(doesn't return) |_exit \ \------->| standard | |
| | | | routine |/ | or \----------|I/O cleanup| |
| | | +-----^----+ |_Exit return +-----------+ |
| | | | | |
| | +--------|--------------------------|---------------------------------+
| | | |
| | |exec |
v v | |
+-----------------|--------------------------v------------------------------------------+
| kernel |
+---------------------------------------------------------------------------------------+
总结起来,程序大致退出的情况是:
- 用户函数最终会返回到main
- main返回到start-routine
- start-routine最后会调用exit函数;
- main函数和用户函数都能够调用exit或_exit或_Exit直接退出程序;
- exit最终会调用_exit或_Exit退出程序。