您的位置:首页 > 其它

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系统调用的封装函数,用于结束进程。系统调用的封装请参考上一章

到这里,进程初始化已经完成。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: