unix系统之进程初始化
2017-03-18 17:43
232 查看
main函数之前
熟悉c语言的人都应该了解一条规则——程序从main函数开始执行。但main函数的指令真的是第一条执行的指令吗?其实不然。在main函数执行之前,程序执行了一段初始化代码。初始化代码初始化进程环境,然后调用main函数。到这里main函数才开始执行。初始化代码的第一条指令才是程序第一条执行的指令。那么初始化代码到底做了什么?本文将详细讨论此问题。进程环境
要了解初始化代码做了什么,需要先了解程序运行前进程环境是怎样的。在执行execve系统调用后,内存被划分为四个区——代码区,数据区,堆区和栈区。如图一。代码区存放了程序的代码,数据区存放了程序的数据,堆区是一大块未使用的内存,等待进程使用。栈区存放了进程的命令行参数和环境变量。命令行参数和环境变量是通过execve系统调用的argv和envp传入的。程序运行前,进程栈的内容如图二所示。long execve(char *pathname,char **argv,char **envp);
argc保存了命令行参数个数。argv[0]保存了命令行参数字符串0的起始地址。argv
保存了命令行参数字符串n的起始地址。envp[0]保存了环境变量字符串0的起始地址。envp
保存了环境变量字符串n的起始地址。
初始化
了解完进程环境,便可以开始初始化进程了。进程初始化代码需要计算机语言编写,那我们又要用到什么语言呢?
汇编到C语言
初始化操作的起始部分必然用到汇编语言。因为程序运行前的进程环境不适合高级语言,只能使用汇编逐个指令的操作。然而汇编操作又太过繁杂困难,需要高级语言完成初始化操作的其余部分。所以汇编既要完成部分初始化操作,又要初始化高级语言运行环境,使高级语言可以运行。在这里高级语言指C语言。
下面为汇编代码。(start.S)
.text .globl _start .type _start,@function _start: xorl %ebp, %ebp popl %esi movl %esp, %ecx andl $0xfffffff0, %esp pushl $0 pushl $0 pushl $0 pushl $0 pushl $0 pushl %ecx pushl %esi pushl $main call __libc_start_main hlt
汇编代码中
xorl %ebp, %ebp 清理了ebp寄存器。
popl %esi 从栈顶中取出argc放入esi寄存器中。esp寄存器指向argv[0],指向命令行参数指针数组的地址。esi寄存器中存放着命令行参数个数。
movl %esp, %ecx 将esp寄存器的值放入ecx寄存器中。ecx寄存器中存放着命令行参数指针数组的地址。
andl $0xfffffff0, %esp 调整esp寄存器的值,使其位于16的倍数之上。
pushl $0 将零压入栈中。
pushl %ecx 将命令行参数指针数组的地址压入栈中。
pushl %esi 将命令行参数的个数压入栈中。
pushl $main 将main函数的地址压入栈中。为后面调用main函数做准备。
call __libc_start_main调用__libc_start_main函数。到这里程序正式从汇编切入C语言运行。
hlt 停机指令。程序一般不会执行到此。
C语言
下面是C语言代码。(libc-start.c)
extern int exit(int status); int __libc_start_main(int (*main)(int,char **,char **), int argc, char **argv) { int result; char **ev=&argv[argc+1]; result=main(argc,argv,ev); exit(result); }
__libc_start_main函数调用前栈中压入了命令行参数指针数组地址,命令行参数个数,main函数的地址。所以__libc_start_main的参数从右往左为char **argv,int argc,int (*main)(int,char**,char **)。
int result; 作为main函数的返回值。
char **ev=&argv[argc+1]; 计算环境变量指针数组的地址。由于命令行参数有argc个且命令行参数指针数组与环境变量指针数组之间以0隔开,所以环境变量指针数组的地址为&argv[argc+1]。
result=main(argc,argv,ev); 正式调用main函数。main函数的声明为int main(int argc,char **argv,char **envp);
exit(result);正式结束进程。exit函数是exit系统调用的封装函数,用于结束进程。系统调用的封装请参考上一章。
到这里,进程初始化已经完成。
相关文章推荐
- unix环境多进程编程----用到的32个系统调用祥解
- Unix系统启动时进程介绍
- UNIX系统编程小结(三)----进程相关
- UNIX环境高级编程——初始化一个守护进程
- 系统提示“登录进程初始化失败”或反复蓝屏重启的解决方案
- 在Unix系统中杀死相关终端的进程
- Upstart 1.10 发布,系统初始化守护进程
- 怎么查看Unix系统下某个进程的详细信息及端口对应的进程信息
- Unix/Linux 那些系统启动后的进程
- UNIX环境高级编程——初始化一个守护进程
- UNIX系统中的进程通信之共享内存
- 在Unix系统中查看应用进程的端口号
- UNIX系统编程 第2章 程序、进程和线程
- PostgreSQL服务过程中的那些事一:启动postgres服务进程一.六:初始化系统表缓存catcache
- 在unix系统中创建守护进程
- 在unix系统中创建守护进程
- UNIX环境高级编程学习之第十三章守护进程 - 初始化一个守护进程
- 系统初始化,系统扩充,进程
- 系统提示"登陆进程初始化失败" 无法正常进入操作系统的解决方案
- Unix原理与应用(第四版)学习笔记3--系统调用之进程篇