unix系统之进程初始化

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]保存了命令行参数字符串n的起始地址。envp[0]保存了环境变量字符串0的起始地址。envp[n]保存了环境变量字符串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系统调用的封装函数,用于结束进程。系统调用的封装请参考上一章

到这里,进程初始化已经完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值