进程的创建与可执行文件的加载

总结:

fork()和exec()系统调用是通过int 0x80软中断陷入内核的,在内核中fork和exec的执行是通过调用sys_fork()/do_fork()和sys_exec()/do_exec()执行的,当一个进程执行fork()的时候是调用一次返回两个进程,并使返回的子进程的pid=0,父、子进程在执行的使时候共享堆栈段数据段和代码段的直到其中一个进程需要执行写操作的时候,才拷贝一个副本另外执行,这就是著名的copy-on-write(写时复制)。还会把进程插入到相关的队列中。exec()函数是六个函数的总称,称为exec()函数簇分别是
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数根据传入的参数不同决定执行哪一个具体的函数比如路径名和环境变量但最终会都是调用do_exec()。do_exec()将可执行程序加载到当前进程中来。但其返回 用户态时返回的是可执行程序的入口地址。可执行程序的加载分为两种情况:静态加载和动态加载
静态加载是指把可执行文件执行时所需要的资源全部加载带文件中,当他执行时就不用调用外面的库了
动态加载是把链接所需要的依赖关系加载进入可执行文件中,当程序执行时才动态的寻找和加载依赖文件,其依赖文件也可能依赖于他的动态加载的文件,如动态链接库,这样就形成一个错综复杂的关系,这样与静态链接相比可以节省很多宝贵的内存资源。
可执行文件执行结束后返回用户空间的一个可执行进程的入口处或是不返回直接等待schdule()函数的调度;


1.进程的创建

1.1进程的概念

进程的概念:操作系统最核心的概念就是进程。进程就是在操作系统中运行的程序,它是操作系统资源管理的最小单位。进程是一个动态的实体,它是程序的一次执行过程。进程和程序的区别在于:进程是动态的,程序是静态的,进程是运行中的程序,而程序是一些保存在硬盘上的可执行代码。

在Linux下面,可以通过命令ps     

   或命令pstree查看当前系统中的进程 

 

 

从组成上看,进程可划分为三个部分:进程控制块(PCB)、代码段(CODE)与数据段(DATA)。

从动态执行的角度来看,进程可视为程序的指令在操作系统根据PCB进行调度而分配的若干时间片内对程序中数据的操作过程。进程控制块(PCB)是操作系统进程管理的依据和对象。

进程控制块(PCB)是进程存在的惟一标识,系统通过PCB的存在而感知进程的存在。

     一般情况下Linux下C程序的生成可分为4个阶段:预编译、编译、汇编、链接。编译器gcc经过预编译、编译、汇编3个步骤将源程序文件转换为目标文件。如果程序有多个目标文件或者程序使用了库函数,编译器还要将所有的目标文件或所需要的库链接起来最后形成可执行程序。当程序执行时,操作系统将可执行程序复制到内存中。一般程序转换为进程分一下几个步骤:

1、内核将程序读入内存,为程序分配内存空间

2、内核为该进程分配进程标识符(PID)和其他所需资源

3、内核为进程保存PID及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统的调度程序调度执行了。

1.2 进程的创建

   系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构。整个Linux系统的所有进程也是一个树形结构(如上图的pstree命令的结果)。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟贮存管理的内核线程。随后,1号进程调用execve()运行可执行程序init(),并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作。新的进程通过复制父进程而建立的。

a、在物理系统内存中为新进程创建一个Task_struct结构体(使用kmalloc函数,以便得到一片连续的区域),将父进Task_struct结构体中的内容copy到其中再修改部分数据。

b、为新进程分配新的核心堆栈页,分配新的进程表示符pid。

c、讲新的Task_struct结构体添到task[ ]数组中,并调整进程链关系,插入运行队列中。

   于是新进程便可以在下次调度时,被选择执行。此时,由于父进程的上下文TSS结构体复制到子进程的TSS结构中,通过改变其中的部分数据,便可以使子进程的执行效果于父进程一致,都是从系统调用中退出,而且子进程将得到与父进程不同的返回值(返回父进程的是子进程的pid,而返回子进程的是0)。

注意,上述过程描述中提到:1号内核进程调用执行init()并演变成1号用户态进程init进程这里前者是init()是函数,后者是进程。两者容易混淆,区别如下:

1.init()函数在内核态运行,是内核代码

2.init进程是内核启动并运行的第一个用户进程,运行在用户态下

3.init()函数调用execve()从文件/etc/inittab中加载可执行程序init()并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。

   以上所说的创建子进程的过程由fork()函数来完成,在Linux中通过clone()系统调用实现fork() ,这个调用通过一系列的参数标志来指明父、子进程组要的共享资源。fork(),vfork()、__clone()库函数根据各自需要的参数去标志调用clone(),然后由 clone()去调用do_fork()。然后内核有意选择子进程首先执行。因为一半子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了著名的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立

编译后的执行结果是:

2、可执行文件的加载 

   可执行文件是一个普通的文件,它描述了如何初始化一个新的进程上下文, linux标准的可执行文件格式是copy_process()ELF当我们在Linux系统的bash下输入一个命令执行某个ELF程序时,在用户层面上bash进程会调用fork()系统调用床照一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。

    execve()系统调用相应的入口是copy_process()sys_execve(),sys_execve()进行一些参数检查复制之后,调用do_execve()。do_execve()会首先查找被执行的文件,如果找到文件,则读取文件的钱128字节(读取时为了判断文件的格式),然后调用search_binary_handle()去搜索和匹配合适的可执行文件装载过程,Linux中所有的被支持的可执行文件格式都有相应的装载处理过程,search_binary_handle()会通过判断文件头部的魔数确定文件格式,并调用相应的装载处理过程。

                                                                                                  

                                   

       ELF可执行文件的装载处理过程叫做load_elf_binary();它的主要步骤如下

1、 检查ELF可执行文件的有效性,如魔数、程序头表中段(segment)的数量

2、  寻找动态链接的“.interp”段,是指动态链接路径

3、  寻找ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据

4、  初始化ELF进程环境

5、  将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的连接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entrys所指的地址,对于动态链接的ELF可执行文件,程序入口点就是动态连接器

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

编译后的执行结果是:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值