从整体上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

学号520

1.阅读理解task_struct数据结构

  • 进程是程序的一个执行的实例;
  • 进程是正在执行的程序;
  • 进程是能分配处理器并由处理器执行的实体;

进程是分配系统资源(CPU时间,内存)的实体。为了管理进程,操作系统必须对每个进程所做的事情进行清楚的描述,为此,操作系统使用数据结构来代表处理不同的实体,这个数据结构就是通常所说的进程描述符或进程控制块(PCB)。在linux操作系统下这就是task_struct结构 ,所属的头文件#include   <sched.h>每个进程都会被分配一个task_struct结构,它包含了这个进程的所有信息,在任何时候操作系统都能够跟踪这个结构的信息,宰割结构是linux内核汇总最重要的数据结构。

这个进程的主要信息:

1、与进程相关的唯一标识符,区别正在执行的进程和其他进程

2、状态:描述进程的状态,因为进程有阻塞、挂起、运行等好几个状态,所以都有个表示符来记录进程的执行状态。

3、优先级:如果有好几个进程正在执行,就涉及到进程的执行的先后顺序,这和进程的优先级这个标识符有关。

4、程序计数器:程序中即将被执行指令的下一条地址。

5、内存指针:程序代码和进程相关数据的指针。

6、上下文数据:进程执行时处理器的寄存器中的数据。

7、I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

8、记账信息:包括处理机的时间总和,记账号等等。

 

2.fork函数

在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程叫做根进程。根进程是Linux中所有进程的祖宗,其余进程都是根进程的子孙。具有同一个父进程的进程叫做兄弟进程。

linux中,父进程以分裂的方式来创建子进程,创建一个子进程的系统调用叫做fork(),fork函数创建进程的实质是子进程对父进程的复制:

int ForkProcess()
{
    int pid; /* fork another process */
    pid = fork();
    if (pid<0)
    {
        /* error occurred */
        printf("Fork Failed!");
        //exit(-1);
    }
    else if (pid==0)
    {
        /* child process */
        //execlp("/bin/ls","ls",NULL);
        printf("Child Process!,My PID is %d\n",pid);
    }
    else
    {
        /* parent process */
        /* parent will wait for the child to complete*/
        printf("Parent process!My PID is %d\n",pid);    
     //wait(NULL); printf("Child Complete!"); //exit(0); } return 0; }

 

3.编译链接的过程和ELF可执行文件格式

3.1 编译链接的过程

3.2 ELF可执行文件格式

在Linux下,可执行文件/动态库文件/目标文件(可重定向文件)都是同一种文件格式,我们把它称之为ELF文件格式。
虽然它们三个都是ELF文件格式但都各有不同:

  • 可执行文件没有section header table 。
  • 目标文件没有program header table。
  • 动态库文件俩个 header table 都有,因为链接器在链接的时候需要section header table 来查看目标文件各个 section 的信息然后对各个目标文件进行链接,而加载器在加载可执行程序的时候需要program header table ,它需要根据这个表把相应的段加载到相应的虚拟内存(虚拟地址空间)中。

 

4.使用exec*库函数加载一个可执行文件

  • 先编辑一个 .c
#include <stdio.h>
#include <stdlib.h> 
int main()
{
    printf("Hello World!\n");
    return 0;
}
  • 生成预处理文件hello.cpp(预处理负责把include的文件包含进来及宏替换等工作)
  • 编译成汇编代码hello.s
  • 编译成目标代码,得到二进制文件hello.o
  • 链接成可执行文件hello
  • 运行一下./hello

 

5.使用gdb跟踪分析schedule()函数 

打开终端中输入qemu-system-x86_64 –kernel linux-5.0.1/arch/x86/boot/bzImage –initrd rootfs.img –S –s 

 

  • 设置断点

 

  • 中断

 

6.分析switch_to中的汇编代码

switch_to实现了进程之间的真正切换:

  • 首先在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。
  • 然后将prev的内核堆栈指针ebp存入prev->thread.esp中。
  • 把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中
  • 将popl指令所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度
  • 通过jmp指令(而不是call指令)转入一个函数__switch_to()
  • 恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行
#define switch_to(prev, next, last)    
do {         
/*        
  * Context-switching clobbers(彻底击败) all registers, so we clobber
  * them explicitly, via unused output variables.  
  * (EAX and EBP is not listed because EBP is saved/restored 
  * explicitly for wchan access and EAX is the return value of 
  * __switch_to())     
  */        
unsigned long ebx, ecx, edx, esi, edi;    

asm volatile("pushfl\n\t"  /* save    flags */"pushl %%ebp\n\t"  /* save    EBP   */"movl %%esp,%[prev_sp]\n\t" /* save    ESP   */"movl %[next_sp],%%esp\n\t" /* restore ESP   */"movl $1f,%[prev_ip]\n\t" /* save    EIP   */"pushl %[next_ip]\n\t" /* restore EIP   */"jmp __switch_to\n" /* regparm call  */"1:\t""popl %%ebp\n\t"  /* restore EBP   */"popfl\n"   /* restore flags *//* output parameters */                       
       : [prev_sp] "=m" (prev->thread.sp),  
       /*m表示把变量放入内存,即把[prev_sp]存储的变量放入内存,最后再写入prev->thread.sp*/
         [prev_ip] "=m" (prev->thread.ip),  
         "=a" (last),                                           
         /*=表示输出,a表示把变量last放入ax,eax = last*//* clobbered output registers: */"=b" (ebx), "=c" (ecx), "=d" (edx),  
         /*b 变量放入ebx,c表示放入ecx,d放入edx,S放入si,D放入edi*/"=S" (esi), "=D" (edi)    
                
         /* input parameters: */    
       : [next_sp]  "m" (next->thread.sp),  
       /*next->thread.sp 放入内存中的[next_sp]*/
         [next_ip]  "m" (next->thread.ip),  
                
         /* regparm parameters for __switch_to(): */ 
         [prev]     "a" (prev),    
         /*eax = prev  edx = next*/
         [next]     "d" (next)    
         
       : /* reloaded segment registers */"memory");     
} while (0)

 

posted @ 2019-03-26 13:56  cyzhou96  阅读(286)  评论(0编辑  收藏  举报