zoukankan      html  css  js  c++  java
  • 【Linux操作系统分析】进程的创建与可执行程序的加载

    进程的创建与可执行程序的加载

    一 进程的创建



            进程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()系统调用之后,就开始了可执行程序的加载。

    .
    上面绿色的步骤为装载文件的主要函数,其主要步骤为:
    1. 检查ELF可执行文件格式的有效性,比如摩数、程序头表中段的数量。
    2. 需找动态链接的“.interp”段,设置动态连接器路径
    3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码,数据,只读数据
    4. 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
    5. 将系统调用的返回地址修改成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.
    


    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的各个段。


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

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


    动态链接的实现步骤:

  • 相关阅读:
    4.22 每日一题题解
    4.21 每日一题题解
    4.20 每日一题题解
    【HDU2825】Wireless Password【AC自动机,状态压缩DP】
    【POJ2778】DNA Sequence 【AC自动机,dp,矩阵快速幂】
    【ZOJ 3228】Searching the String 【AC自动机】
    【LA5135 训练指南】井下矿工 【双连通分量】
    【LA3523 训练指南】圆桌骑士 【双连通分量】
    【LA3713 训练指南】宇航员分组 【2-sat】
    【LA3211 训练指南】飞机调度 【2-sat】
  • 原文地址:https://www.cnblogs.com/suzhou/p/3638993.html
Copyright © 2011-2022 走看看