Linux 进程执行过程分析

本文详细分析了Linux进程执行的基本要素,包括可执行代码、系统堆栈、进程控制块和独立存储空间,并探讨了进程的生命周期。文章还介绍了fork()和execve()系统调用的工作原理,以及在内核中装载和执行可执行文件的过程。通过代码实测和gdb调试,深入解析了_fork()和copy_process()函数的调用步骤。
摘要由CSDN通过智能技术生成

Linux 进程执行过程分析

编号411,本文参考孟宁老师 Github 项目 https://github.com/mengning/linuxkernel

进程的基本要素

由于系统进程有一定的特殊性,这里主要分析普通用户进程。一般来讲,Linux 系统下的进程有几个基础要素:

  • 可执行代码

    可执行代码是进程的基本要素,这部分包含表示程序功能的进程私有代码和共享的链接库代码。

  • 系统专用系统堆栈空间

    进程专用的系统堆栈空间

  • 进程控制块

    即task_stuct数据结构,一方面,进程控制块包含的内容为内核调度提供了数据,另一方面,这个结构体记录了该进程的私有资源。

  • 独立存储空间

    独立的存储空间,即表示该进程拥有专有的用户空间(用户空间堆栈)。

除了以上的基本要素外,为了理解进程的执行过程,我们还需要理解以下基本内容:

  • 进程的生命周期

在这里插入图片描述
一个进程被fork出来后,进入就绪态;当被调度到获得CPU执行时,进入执行态;如果时间片用完或被强占时,进入就绪态;资源得不到满足时,进入睡眠态(深度睡眠或浅度睡眠),比如一个网络程序,在等对方发包,此时不能占着CPU,进入睡眠态,当包发过来时,进程被唤醒,进入就绪态;如果被暂停,进入停止态;执行完成后,资源释放,此时父进程wait4还未收到它的信号,进入僵死态。即整个周期可能会涉及的状态有:就绪态,执行态,僵死态,停止态,睡眠态。

  • 进程执行相关的系统调用

    • fork() 调用

    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值;

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

    • execve() 调用

      • 预处理
        首先在内核空间分配一个物理页面,然后调用do_getname()从用户空间拷贝文件名字符串。
    • 调用主体函数do_execve()

      • 我们既然要执行参数中给的二进制文件,首先需要打开文件,获取文件句柄file

      • 然后我们需要一个linux_binprm结构体去保存函数具体的参数信息,包括文件名,argv,envp,还会将文件前128字节读到linux_binprm.buf中。

      • 因为可执行文件的种类很多,比如elf,a.out等格式。我们需要从内核全局linux_binfmt队列中找到一个能够处理参数中所给的可执行文件的linux_binfmt结构,具体就是依次试用linux_binfmt结构中各自的load_binary()函数。

    • 可执行文件的装载和投运(a.out为例)

      • 与过去决裂,释放用户空间。
        既然是要执行参数中给定的二进制文件,就需要放弃可能从父进程继承下来的用户空间,而使用本进程自己的用户空间。因此,需要检查是否与父进程通过指针共享用户空间,还是之前复制父进程用户空间。如果通过指针共享,说明本进程本身没有自己的用户空间,之前称为“进程”不合适,应该称作线程,就直接申请进程用户空间。如果复制父进程的用户空间,这是就需要全部释放。

      • 装载可执行文件数据段代码段
        这时可以将可执行文件装入进程的用户空间了,这时分两种情况:

        • 可执行文件不是"纯代码",需要通过do_brk()扩展数据段+代码段大小的空间,然后通过read()读取文件内容到用户空间

        • 否则,如果文件系统提供mmap(),并且数据段和代码段长度与页面大小对齐,直接通过文件映射读取到用户空间,否则,通过1方法读取。

      • 装载可执行文件堆栈段和bss段

      用户空间堆栈区顶部当然是用户虚存空间顶部,即TASK_SIZE,为3GB,虚存地址为0xC000 0000的位置。

      这里主要是设置用户堆栈区,包括envp[],argv[]以及argc

      • start_thread()

代码实测

  • 编写 fork() 和 execve() 的测试代码并进行测试

    编写以下两个文件

    • helloworld 文件
    // file helloword.c
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    int main()
    {
         
        printf("Hello world!\n");
        pid_t pid = getpid();
        printf("pid of helloworld from helloword is %d\n", pid);
    
        return 0;
    }
    // end helloword.c
    
    • start_process 文件
    // file start_process.c
    #include<stdio.h>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值