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

SG*****003  董*

一、实验过程

实验1 :fork()的调用

源代码 test.c

#include <errno.h>

 #include <unistd.h>

 #include <stdio.h>

 #include <stdlib.h>

 

 int  main()

 {

   pid_t pid;

   int count=0;

   pid=fork();

   count++;

   printf("count=%d\n",count);

   return 0;

}

 

编译后运行结果是

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

 

实验2:fork()和exec()的组合使用

源代码 string3.c

#include<stdlib.h> 

#include<stdio.h> 

#include<sys/types.h> 

#include<unistd.h> 

 

int main() 

    pid_t pid; 

    pid = fork(); 

    if(pid == 0) 

    { 

        execl("./test", "test", NULL); 

        printf(" this is Child process!\n"); 

    } 

    else if(pid > 0) 

    {   

        printf("this is Parent process!\n"); 

    } 

    else printf("failure!\n");  

    exit(0); 

编译后运行结果是:

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

 

二、Fork调用的执行过程

Fork()(API)—》

fork()(在c库中封装的系统调用)-à

system_call()(通过128号中断从用户态切换到内核态,进入系统调用处理程序)---》

sys_fork()àdo_fork()--àcopy_process()-à

一个新进程诞生

 

copy_process()函数完成的工作:

(1)调用dup_task_struct()为心进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符完全相同。

(2)检查新创建的这个子进程后,当前用户所用有的进程数目没有超过给他分配的资源的限制

(3)现在,子进程着手使自己与父进程区别开来,进程描述符内的许多成员都要被清0或者设置初始值。进程描述符的成员值并不是继承而来的,而主要是统计信息,进程描述符中的大多数数据都是共享的。

(4)接下来,子进程的状态被设置为不可终端等待状态以保证他不会投入运行

(5)copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是拥有超级用户权限的PF_SUPER[RIV标志被清0。表明进程还没有调用exec()函数的PE_FORKNOEXEC标志被设置。

(6)调用get_pid()为新进程获取一个有效的PID

(7)根据传给cloen()的参数标志copy_process()拷贝或者共享打开的文件、文件系统信、信号处理函数、进程地址空间和命名空间等。

(8)让父进程和子进程平分剩余的时间片。

(9)最后,copy_proccess()做扫尾工作并返回一个指向子进程的指针

 

三、ELF的格式:

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

ELF指定了进程中text段、bss段、data段等应该放置到进程虚拟内存空间的什么位置,以及记录了进程需要用到的各种动态链接库的位置。

1、ELF header在文件开始处描述了整个文件的组织,

2、段头部表的作用:将连续的文件节映射到运行时存储器段,指出怎样创建进程映像,含有每个program header的入口,

3、Section(节)提供了目标文件的各项信息(如指令、数据、符号表、重定位信息等)

4、节头表:描述目标文件节,包含每一个section的入口,给出名字、大小等信息


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

每一个linux程序都有一个运行时存储器映像,在可执行文件中段头部表的指导下,加载器将可执行文件的相关内容拷贝到代码和数据段,接下来,加载器跳转到程序的入口点,

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

 

Linux运行时存储器映像

栈是向下增长的,而堆是向上增长的,读/写段和只读段从可执行文件中加载

 

四、Execve执行过程

Execve—》系统调用----》sys_execve()--àdo-execve()-à

(1)    删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构

(2)    映射私有区域。为新程序的文本、数据、bss和栈区域创建新的区域结构。

(3)    映射共享区域。如标准c库的libc.so,都是动态链接到程序的,然后再映射到用户虚拟地址空间中的共享区域内。

(4)    放置程序计数器PC,execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。

 

五、总结

(一)fork 和exec函数簇的组合使用

fork创建一个子进程,父进程在运行中间过程中fork,产生一个子进程,子进程不会从头开始运行代码,而会从fork的开始的后面的代码开始运行

fork的奇妙之处在于:它被调用一次,却返回两次,它可能有三种不同的返回值。

1、    在父进程中,fork返回新创建的子进程的PID

2、    在子进程中,fork返回0

3、    如果出新错误,fork返回一个负值

程序中fork之后,有两个进程分别去运行后面的代码

子进程的数据空间,堆栈空间都会从父进程得到一个拷贝,而不是共享,但是代码段,父子进程是共享的。体现在实验一中,正因为父进程和子进程都有自己的count=0,而代码段共享,都执行count++,这样互不干扰,无论是子进程还是父进程先输出,两者的结果都是count=1

Exec的调用即是替换掉子进程,进而替换上有用的想要执行的进程,避免父子进程完全一样的浪费,若是替换成功,则不会返回。这也是linux中进程产生的手段,从内核启动的init进程逐渐衍生出应用程序实际的进程

(二)用共享库来动态链接的过程

由于几乎每个c程序都大量使用标准I/O函数,如printf(),如果使用静态链接,这些函数的代码会被复制到每个运行进程的文本段中,这将是对存储器资源的极大浪费,

共享库就是为了解决这一问题,共享库是一个目标模块,在运行时可被加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程叫做动态链接,是由动态链接器的程序来完成的

大致步骤:

1、源程序文件和头文件等被翻译器生成可重定位的目标文件

2、链接器把可重定位目标文件和共享库的重定位和符号表的信息经过链接生成部分链接的可执行目标文件

4、加载时,由动态链接器把部分链接的可执行文件和共享库的代码、数据完全链接成完全可执行文件

转载于:https://www.cnblogs.com/springerwit/archive/2013/05/27/3102468.html