Process Environment
在第八章介绍进程控制原语(process control primitives)之前,我们需要先研究一下进程执行的环境。
本章我们会关注:
1. main函数是如何在程序执行时被调用的
2. 命令行参数是如何传递给新进程的
3. 内存布局什么样?
4. 如何分配额外的内存
5. 进程如何使用环境变量
6. 终止进程的几种方法
7. longjmp
和setjmp
8. 进程的资源限制
对于本章节知识点进行了总结,链接如下:http://blog.csdn.net/feather_wch/article/details/50725828
可用于日后复习之用
C程序从main函数开始,其原型如下:
int main(int argc, char * argv[]);
C程序是有kernel调用exec程序之一来调用的。在main函数之前存在着特殊的启动函数(start-up)。启动函数被设置为程序的开始地址。这是由link editor
设置的。
用于从kernel(内核)获得一些值,如获得了命令行参数(command-line argument)和环境(environment)
进程终止的有几种方式:
一般的终止方式:
1. 从main return
2. 调用exit
3. 调用_Exit, _exit
4. 开始程序的最后一个线程 return
5. 在最后一个线程调用 pthread_exit
异常的终止方式:
6. 调用abort
7. 接收到信号
8. Response of the last thread to a cancellation request(11.5 and 12.7)
启动程序如果是用C编写的(大多是用汇编编写的),那么调用main
的部分如下:
exit(main(argc, argv));
通常终止进程的三个函数:_exit, _Exit和exit
原型如下:
#include
void exit(int status);
void _Exit(int status);
#include
void _exit(int status);
exit()和_Exit()是由ISO C规定的。
_exit()是由POSIX.1 规定的
exit会先进行清除工作,再返回到kernel(例如会调用fclose关系所有打开的流,所有缓冲的输出数据都会flush)
_exit, _Exit会立即返回到kernel
exit(0)
和return 0
是一样的
如果我们并不指定main
的返回值类型,如:
main()
{
printf("hello world\n");
}
在不同的系统上,main会返回给环境不同的值,我们可以进行如下测试:
$gcc hello.c
$./a.out
hello world
$echo $? '显示exit状态'
$0 '默认的c99标准,返回的值为0'
$gcc -std=c89 hello.c '采用c89标准来编译'
$./a.out
hello world
$echo $? '显示exit状态'
$12 '返回的值为随机值'
可知,明确指定main的返回值是很重要的,在有些系统中还有可能因此崩溃。
在一些编译器和
lint(1) 程序
(检查程序中潜在的错误)中,使用exit
代替return,会出现警告。然而exit和return效果是一样的。因此可以忽视。
ISO C中,一个进程可以注册32个由exit
自动调用的函数。这被称为exit handlers
,可以通过调用atexit
来注册。
详细讲解atexit链接:http://blog.csdn.net/feather_wch/article/details/50723255
exec可以将命令行参数传递给新程序,这也是shell
的一般操作
int main(int argc, char *argv[])
{
return 0;
}
每个程序也传递环境列表。类似于command-line argument
,environment list
是字符指针的数组。该指针数组的地址保存在全局变量environ
中
extern char ** environ;
我们称environ
为环境指针
环境是由如下内容组成:
name = value
大多数系统给main
提供了第三个参数,用于存放环境链表,原型如下:
int main(int argc, char * argv[], char *envp[]);
第三个参数相对于全局变量并没有提供什么优势。因此POSIX.1 规定environ
取代main
的第三个参数。
访问特定的环境变量一般通过这两个函数
compoents | 解释 | 具体说明 |
---|---|---|
Text segment | CPU执行的机器指令 | 文本段是共享的,only a single copy needs to be in memory for frequently executed programs, such as text editors, the C compiler, the shells, and so on.此外文本段是只读的 |
Initialized data segment | 也被成为data segment | 保存在程序初始化的变量, 如int maxcount = 99; |
Uninitialized data segment | 又称为“bss” segment | 在程序开始执行后,由内核初始化0和NULL |
Stack | 保存自动变量 | Each time a function is called, the address of where to return to and certain information about the caller’s environment, such as some of the machine registers , are saved on the stack.每次递归调用函数的时候,一个新的stack frame 就会被使用 |
Heap | 动态内存分配 | 处于为初始化数据段和stack之间 |
在
a.out
文件中存在更多地段类型,如debugging information, linkage table for dynamic shared libraries等等,但是这些额外的段并不是进程执行程序的镜像的部分
从图7.6我们可以知道bss
段是exec
执行时自动初始化为0的,那么仅仅有text段
和initialized data segment
是保存在程序文件中的
size /bin/sh
共享库移除了可执行文件中共同的库程序(library routines),在内存中存放着library routine的copy,任何进程都可以调用。
优点:1. 减少可执行文件的尺寸 2. 库程序可以直接用新版本替换,而不需要重新连接每个使用该库的程序(前提是:参数类型和数量不变)
缺点:程序第一次调用,或者该库程序第一次调用的时候,会有额外的时间消耗
$gcc hello.c '使用共享库'
$size a.out
text data bss dec hex filename
1214 560 8 1782 6f6 a.out
$gcc -static hello.c '不使用共享库'
$size a.out
text data bss dec hex filename
790784 7572 9016 807372 c51cc a.out
如上面的测试可知,使用共享库和不使用共享库,可执行程序的大小相差很多。
链接:http://blog.csdn.net/feather_wch/article/details/50725778
环境字符串例如name=value
的形式,例如环境变量HOME 和 USER
shell
的操作MAILPATH
能告知shell
哪里能找到mail链接7.9:http://blog.csdn.net/feather_wch/article/details/50740004
见链接7.9
见链接7.9
C中我们不能goto
一个其他函数离得label
,我们必须使用setjmp
和longimp
来完成这种类型的branching
链接7.10:http://blog.csdn.net/feather_wch/article/details/50740245
如果你正在编写可移植性的代码,并且在其中使用了非局部的跳转,你必须使用volatile
在第十章讲到信号时,我们会讲解他们的信号版本:sigsetjmp
和siglongjmp
最基本原则:自动变量在声明它的函数返回后永远不能被引用。
例如:函数中定义的数组,函数结束时却将其数组地址返回。这些空间已经被释放了,但是其他地方还引用就会产生错误。最好的办法是用static, extern
或者maalloc分配空间
每个进程都有一组资源的限制,通过getrlimit、setrlimit
能查寻和改变资源限制。
链接:http://blog.csdn.net/feather_wch/article/details/50749105