您的位置:首页 > 运维架构 > Linux

【Linux操作系统分析】进程的创建与可执行程序的加载

2013-05-25 18:17 609 查看
进程的创建与可执行程序的加载

一 进程的创建



进程0是所有进程的祖先。进程1被创建并选择后调用execve()系统调用转入可执行程序init,init进程一直存活,创建和监控在操作系统外层执行的所有进程的活动。
当fork()被调用时,主要由函数do_fork()函数来处理。do_fork()函数的执行流程如下:







do_fork()的主要作用是为子进程分配PID,检查各个标志位,以决定新创建的子进程的被创建后所处的状态和执行队列,以及调用辅助函数copy_process()来创建进程描述符以及子进程执行所需要的所有其他内核数据结构。
do_fork()结束后,创建了可运行的完整的子进程,调用程序把子进程描述符thread字段的值装入CPU寄存器,特别是把thread.esp(子进程内核态对战的地址)装入esp寄存器,把函数ret_from_fork()的地址装入eip寄存器,这个汇编语言函数调用schedule_tail()函数,用存放在栈中的值再装载所有的寄存器,并强迫CPU返回到用户态。然后在fork()系统调用结束时,新进程将开始执行。系统调用的返回值放在eax寄存器中:返回给子进程的值是0,返回给父进程的值是子进程的PID。
至此,fork()系统调用结束,父进程和子进程暂时共享同一个用户态对战,但是当父子进程中有一个试图去改变栈,则写时复制复制机制将拷贝出一份新的用户态堆栈给父进程。



二 可执行程序的加载

前面讲到fork()系统调用创建出了一个新的进程,然后紧接着,这个新的进程一般会用来调用execve()系统调用执行指定的ELF文件,即当进入execve()系统调用之后,就开始了可执行程序的加载。


.



上面绿色的步骤为装载文件的主要函数,其主要步骤为:

检查ELF可执行文件格式的有效性,比如摩数、程序头表中段的数量。
需找动态链接的“.interp”段,设置动态连接器路径
根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码,数据,只读数据
初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的连接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址:对于动态链接的ELF可执行文件,程序入口点是动态连接器

当load_elf_binary()执行完毕,返回至do_execve()再返回至sys_execve()时,上面第5步已经把系统调用的返回地址改成了被装载的ELF程序的入口地址。所以当sys_+execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。

三 附录

1 fork()和exec()族函数

fork():创建一个新进程,该进程几乎是当前进程的完全拷贝。
exec()族函数:启动另外的进程以取代当前运行的进程。

1.1 fork()函数

forkTest.c

int main()
{
pid_t pid;

pid = fork();

if(pid == 0)
{
printf("Child process!\n");
}
else if(pid > 0)
{
sleep(1);
printf("Parent process!\n");
}
else printf("fork failure!\n");

exit(0);
}


运行截图:



由运行结果可知,fork()创建了一个子进程,父进程和子进程各打印了一条信息。

1.2 exec()族函数

execTest.c

例子中用execl系统调用
在相同的文件夹中已经编译好一个helloworld可执行文件。
execTest.c文件,在上例中fork()函数创建的子进程分支中增加了一个execl()系统调用,调用同文件夹下的helloworld可执行文件。

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
pid_t pid;

pid = fork();

if(pid == 0)
{
execl("./helloworld", "helloworld", NULL);
printf("Child process!\n");
}
else if(pid > 0)
{
sleep(1);
printf("Parent process!\n");
}
else printf("fork failure!\n");

exit(0);
}


运行截图:



由运行结果可以看到,execl()函数调用了一个新的进程,完全取代当前调用该函数的进程。上例中,fork出来的子进程并没有打印出“Child process!”,正说明了这一点。

2 fork和exec系统调用在内核中的执行过程

2.1 C代码中嵌入汇编代码

asmTest.c
#include <stdio.h>

int  main()
{
/* val1+val2=val3 */
unsigned int val1 = 1;
unsigned int val2 = 2;
unsigned int val3 = 0;
printf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);
asm volatile(
"movl $0,%%eax\n\t" /* clear %eax to 0*/
"addl %1,%%eax\n\t" /* %eax += val1 */
"addl %2,%%eax\n\t" /* %eax += val2 */
"movl %%eax,%0\n\t" /* val2 = %eax*/
: "=m" (val3) /* output =m mean only write output memory variable*/
: "c" (val1),"d" (val2) /* input c or d mean %ecx/%edx*/
);
printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);

return 0;
}
执行截图:



2.2 C代码中嵌入系统调用汇编代码

sys_asmTest.c

#include <stdio.h>
#include <time.h>

int  main()
{
time_t tt;
struct tm *t;
int ret;
/*
(gdb) disassemble time
Dump of assembler code for function time:
0x0804f800 <+0>:	push   %ebp
0x0804f801 <+1>:	mov    %esp,%ebp
0x0804f803 <+3>:	mov    0x8(%ebp),%edx
0x0804f806 <+6>:	push   %ebx
0x0804f807 <+7>:	xor    %ebx,%ebx
0x0804f809 <+9>:	mov    $0xd,%eax
0x0804f80e <+14>:	int    $0x80
0x0804f810 <+16>:	test   %edx,%edx
0x0804f812 <+18>:	je     0x804f816 <time+22>
0x0804f814 <+20>:	mov    %eax,(%edx)
0x0804f816 <+22>:	pop    %ebx
0x0804f817 <+23>:	pop    %ebp
0x0804f818 <+24>:	ret
End of assembler dump.

*/
#if 0
time(&tt);
printf("tt:%ld\n",tt);
#else
/* 没有使用常规寄存器传参的方法 */
asm volatile(
"mov $0,%%ebx\n\t" /* 不使用参数tt */
"mov $0xd,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=m" (tt)
);
printf("tt:%ld\n",tt);
t = localtime(&tt);
printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
/* 使用常规寄存器传参的方法 */
asm volatile(
"mov %1,%%ebx\n\t" /* 使用参数tt */
"mov $0xd,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=m" (ret)
: "b" (&tt)
);
printf("tt:%ld\n",tt);
t = localtime(&tt);
printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
#endif

return 0;
}
运行截图:



2.3 fork()系统调用的执行过程

查看fork()系统调用的汇编代码(部分):



2.4 exec()系统调用的执行过程

对exec()进行反汇编:
Dump of assembler code for function execl:
0xb7ed85f0 <+0>:	push   %ebp
0xb7ed85f1 <+1>:	push   %edi
0xb7ed85f2 <+2>:	push   %esi
0xb7ed85f3 <+3>:	push   %ebx
0xb7ed85f4 <+4>:	sub    $0x102c,%esp
0xb7ed85fa <+10>:	mov    0x1044(%esp),%edx
0xb7ed8601 <+17>:	lea    0x20(%esp),%ecx
0xb7ed8605 <+21>:	call   0xb7f4af83
0xb7ed860a <+26>:	add    $0xec9ea,%ebx
0xb7ed8610 <+32>:	lea    0x1048(%esp),%eax
0xb7ed8617 <+39>:	mov    %ecx,0x18(%esp)
0xb7ed861b <+43>:	test   %edx,%edx
0xb7ed861d <+45>:	mov    %edx,0x20(%esp)
0xb7ed8621 <+49>:	je     0xb7ed8724 <execl+308>
0xb7ed8627 <+55>:	lea    0x4(%eax),%ebp
0xb7ed862a <+58>:	mov    (%eax),%eax
0xb7ed862c <+60>:	mov    $0x1,%esi
0xb7ed8631 <+65>:	lea    0x20(%esp),%edi
0xb7ed8635 <+69>:	mov    $0x400,%edx
0xb7ed863a <+74>:	test   %eax,%eax
0xb7ed863c <+76>:	mov    %eax,(%edi,%esi,4)
0xb7ed863f <+79>:	je     0xb7ed865d <execl+109>
0xb7ed8641 <+81>:	lea    0x0(%esi,%eiz,1),%esi
0xb7ed8648 <+88>:	add    $0x1,%esi
0xb7ed864b <+91>:	cmp    %esi,%edx
0xb7ed864d <+93>:	je     0xb7ed86a0 <execl+176>
0xb7ed864f <+95>:	mov    %ebp,%eax
0xb7ed8651 <+97>:	lea    0x4(%eax),%ebp
---Type <return> to continue, or q <return> to quit---
0xb7ed8654 <+100>:	mov    (%eax),%eax
0xb7ed8656 <+102>:	test   %eax,%eax
0xb7ed8658 <+104>:	mov    %eax,(%edi,%esi,4)
0xb7ed865b <+107>:	jne    0xb7ed8648 <execl+88>
0xb7ed865d <+109>:	mov    -0xd4(%ebx),%eax
0xb7ed8663 <+115>:	mov    0x1040(%esp),%ecx
0xb7ed866a <+122>:	mov    (%eax),%eax
0xb7ed866c <+124>:	mov    %edi,0x4(%esp)
0xb7ed8670 <+128>:	mov    %ecx,(%esp)
0xb7ed8673 <+131>:	mov    %eax,0x8(%esp)
0xb7ed8677 <+135>:	call   0xb7ed82e0 <execve>
0xb7ed867c <+140>:	cmp    0x18(%esp),%edi
0xb7ed8680 <+144>:	mov    %eax,%esi
0xb7ed8682 <+146>:	je     0xb7ed868c <execl+156>
0xb7ed8684 <+148>:	mov    %edi,(%esp)
0xb7ed8687 <+151>:	call   0xb7e36ef0 <free@plt+48>
0xb7ed868c <+156>:	add    $0x102c,%esp
0xb7ed8692 <+162>:	mov    %esi,%eax
0xb7ed8694 <+164>:	pop    %ebx
0xb7ed8695 <+165>:	pop    %esi
0xb7ed8696 <+166>:	pop    %edi
0xb7ed8697 <+167>:	pop    %ebp
0xb7ed8698 <+168>:	ret
0xb7ed8699 <+169>:	lea    0x0(%esi,%eiz,1),%esi
0xb7ed86a0 <+176>:	cmp    0x18(%esp),%edi
0xb7ed86a4 <+180>:	mov    $0x0,%eax
0xb7ed86a9 <+185>:	lea    (%edx,%edx,1),%ecx
0xb7ed86ac <+188>:	mov    %ecx,0x1c(%esp)
0xb7ed86b0 <+192>:	lea    0x0(,%edx,8),%ecx
---Type <return> to continue, or q <return> to quit---
0xb7ed86b7 <+199>:	cmovne %edi,%eax
0xb7ed86ba <+202>:	mov    %edx,0x14(%esp)
0xb7ed86be <+206>:	mov    %ecx,0x4(%esp)
0xb7ed86c2 <+210>:	mov    %eax,(%esp)
0xb7ed86c5 <+213>:	call   0xb7e36e70 <realloc@plt>
0xb7ed86ca <+218>:	mov    0x14(%esp),%edx
0xb7ed86ce <+222>:	test   %eax,%eax
0xb7ed86d0 <+224>:	je     0xb7ed8710 <execl+288>
0xb7ed86d2 <+226>:	cmp    0x18(%esp),%edi
0xb7ed86d6 <+230>:	je     0xb7ed86e8 <execl+248>
0xb7ed86d8 <+232>:	mov    %eax,%edi
0xb7ed86da <+234>:	mov    0x1c(%esp),%edx
0xb7ed86de <+238>:	mov    %ebp,%eax
0xb7ed86e0 <+240>:	jmp    0xb7ed8651 <execl+97>
0xb7ed86e5 <+245>:	lea    0x0(%esi),%esi
0xb7ed86e8 <+248>:	shl    $0x2,%edx
0xb7ed86eb <+251>:	mov    %edx,0x8(%esp)
0xb7ed86ef <+255>:	mov    %edi,0x4(%esp)
0xb7ed86f3 <+259>:	mov    %eax,(%esp)
0xb7ed86f6 <+262>:	mov    %eax,0x14(%esp)
0xb7ed86fa <+266>:	call   0xb7e9f750
0xb7ed86ff <+271>:	mov    0x14(%esp),%ecx
0xb7ed8703 <+275>:	mov    %ebp,%eax
0xb7ed8705 <+277>:	mov    0x1c(%esp),%edx
0xb7ed8709 <+281>:	mov    %ecx,%edi
0xb7ed870b <+283>:	jmp    0xb7ed8651 <execl+97>
0xb7ed8710 <+288>:	cmp    0x18(%esp),%edi
0xb7ed8714 <+292>:	mov    $0xffffffff,%esi
0xb7ed8719 <+297>:	jne    0xb7ed8684 <execl+148>
---Type <return> to continue, or q <return> to quit---
0xb7ed871f <+303>:	jmp    0xb7ed868c <execl+156>
0xb7ed8724 <+308>:	mov    -0xd4(%ebx),%eax
0xb7ed872a <+314>:	mov    0x1040(%esp),%ecx
0xb7ed8731 <+321>:	mov    (%eax),%eax
0xb7ed8733 <+323>:	mov    %ecx,(%esp)
0xb7ed8736 <+326>:	mov    %eax,0x8(%esp)
0xb7ed873a <+330>:	lea    0x20(%esp),%eax
0xb7ed873e <+334>:	mov    %eax,0x4(%esp)
0xb7ed8742 <+338>:	call   0xb7ed82e0 <execve>
0xb7ed8747 <+343>:	mov    %eax,%esi
0xb7ed8749 <+345>:	jmp    0xb7ed868c <execl+156>
End of assembler dump.


3 task_struct进程控制块,ELF文件格式与进程地址空间的联系,注意Exec系统调用返回到用户态时EIP指向的位置。

3.1 task_struct进程控制块结构







3.2 ELF文件格式







3.3 ELF文件格式与进程地址空间的关系

ELF文件中,段的权限往往只有为数不多的几种组合,基本上是三种:

以代码段为代表的权限为可读可执行的段
以数据段和BSS段为代表的权限为可读可写的段
以只读数据段为代表的权限为只读的段

对于相同权限的段,把它们合并在一起当作一个段进行映射。
如.text和.init,它们包含的分别的是程序的可执行代码和初始化代码,并且它们的权限相同,都是可读并且可执行。假设.text为4097字节,.init为512字节,这两个段分别映射的话需要占用三个页面,因为一个页面的大小为4KB。如果把它们合并成一起映射的话只需占用两个页面。
ELF可执行文件中引入了一个概念叫做“Segment”,一个Segment包含一个或多个属性类似的Section。Segment实际上从装载的角度重新划分了ELF的各个段。







4 动态链接库在ELF文件格式中与进程地址空间中的表现形式

表现形式:动态连接器和动态链接重定位表。
在静态链接时,整个程序最终只有一个可执行文件,它是一个不可以分割的整体;但是在动态连接下,一个程序被分成了若干个文件,有程序的主要部分,即可执行文件和程序所依赖的共享对象(.so文件)。
动态链接器与普通共享对象一样被映射到了进程的地址空间,在系统开始运行程序之前,首先会把控制权交给动态链接器,由它完成所有的动态链接工作以后再把
控制权交给程序,然后开始执行。
动态连接器的位置是由ELF可执行文件决定的。在ELF可执行中,有一个专门的段叫做“.interp”段。



动态链接的实现步骤:





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: