zoukankan      html  css  js  c++  java
  • 20169215《Linux内核原理与分析》 第九周作业

    网络云课堂学习

    之前我们学习过,Linux是通过fork()产生一个和父进程几乎一样的子进程,但这并不是我们需要的新进程,还需要用新的代码和数据替换掉子进程中对应的内容,才能达到创建一个我们想要的新进程的目的,也就是可执行程序的装载。
    首先了解下C代码转换成可执行程序的过程:

    • gcc -E -o hello.cpp hello.c -m32 这行命令用于生成.cpp文件,主要是将include的文件包含进来并且将宏替换
    • gcc -x cpp-output -S -o hello.s hello.cpp -m32 该命令通过预处理过的文件生成对应的汇编文件hello.s
    • gcc -x assembler -c hello.s -o hello.o -m32 命令通过汇编代码生成包含有机器指令目标代码,得到二进制文件hello.o
    • gcc -o hello hello.o -m32 链接形成可执行文件hello
      目标文件有三种:可重定位文件、可执行文件、共享object文件。

    ELF文件加载到内存默认是从0x804800开始加载,前面是ELF头部信息,头部大小不同会影响程序的实际入口地址。
    当fork一个子进程的时候,完全复制的是父进程,然后调用execv时候,用要加载的可执行程序把原来的进程环境覆盖了,覆盖了之后用户态堆栈被清空,开始新的程序的压栈和执行。

    从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式。
    一种是固定的、静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中;
    另一种是动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。

    这样,就有了两种不同的ELF格式映像。

    一种是静态链接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。

    另一种是动态连接,需要在装入/启动其运行时同时装入函数库映像并进行动态链接。

    下面是实验楼实验:

    通过gdb和设置断点调试跟踪可执行程序装载的过程:
    由于在load_elf_binary设置了断点,而在内核启动初始化时执行init的时候执行了load_elf_binary,所以会在这个地方停下来。在ELF文件格式中,处理函数是load_elf_binary函数:

    继续执行的话,并且在MenuOS中执行exec命令,会在我们之前设置的断点sys_execve处停下来,可以看到此时exec命令并没有执行完:

    该系统调用所需要的参数pt_regs:

    struct pt_regs {
        long ebx;
        long ecx;
        long edx;
        long esi;
        long edi;
        long ebp;
        long eax;
        int xds;
        int xes;
        long orig_eax;
        long eip;
        int xcs;
        long eflags;
        long esp;
        int xss;
    };
    

    该参数描述了在执行该系统调用时,用户态下的CPU寄存器在核心态的栈中的保存情况。通过这个参数,sys_execve可以获得保存在用户空间的以下信息:可执行文件路径的指针(regs.ebx中)、命令行参数的指针(regs.ecx中)和环境变量的指针(regs.edx中)。真正执行程序的功能则是do_execve函数中实现的。
    可以通过list命令查看接下来的几行代码:


    然后单步执行,进入函数内部,就会发现它真的是通过调用do_execve完成的。这个函数的第一个参数都是要被执行的程序的路径,第二个参数则向程序传递了命令行参数,第三个参数则向程序传递环境变量,其中三个参数的传递:可执行文件路径的指针(regs.ebx中)、命令行参数的指针(regs.ecx中)和环境变量的指针(regs.edx中)。:


    然后在load_elf_binary出停下来,可以通过list查看装载ELF文件的代码:


    其大致过程是:填充并且检查目标程序ELF头部,load_elf_phdrs加载目标程序的程序头表,如果需要动态链接, 则寻找和处理解释器段,检查并读取解释器的程序表头,装入目标程序的段segment,填写程序的入口地址,create_elf_tables填写目标文件的参数环境变量等必要信息,start_thread准备进入新的程序入口。
    接下来的断点是start_thread,可以看到新程序的堆栈内容,通过po new_ip我们可以知道新进程入口在0x8048d0a的位置,new_ip地址是返回用户态执行的第一条指令:

    新开一个终端,可以通过命令readelf -h hello查看要加载的ELF头,容易发现入口地址和上面是一样的:

    进入set_user_gs可以看到它在修改内核堆栈:

    课本知识学习

    虚拟文件系统(VFS)定义了所有文件系统都支持的、基本的、概念上的接口和数据结构,它使Linux能够支持各种文件系统。内核通过抽象层能够方便、简单地支持各种类型的文件系统。
    系统调用是通过VFS接口提供给用户空间的前端;系统调用是具体文件系统的后端,处理实现细节。
    Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点、安装结点。其通用操作包括创建、删除和安装等。
    文件通过目录组织起来,路径中每一部分都被称作目录条目。VFS把目录当做文件对待,所以可以对目录执行和文件相同的操作。Unix将文件的相关信息和文件本身两个概念加以区分。
    VFS采用的是面向对象的设计思路。BFS中有四个主要的对象类型,分别是:

    • 超级对象块,它代表一个具体的已安装的文件系统。
    • 索引节点对象,它代表一个具体文件。
    • 目录项对象,它代表一个目录项,是路径的一个组成部分。
    • 文件对象,它代表由进程打开的文件。

    超级对象块由super_block结构体表示,最重要的一个域是s_op,它指向超级块的操作函数表。如果一个文件系统要写自己的超级块,需要调用:

    sb->s_op->write_super(sb);
    

    索引节点对象包含了内核在操作文件或目录时需要的全部信息,由inode结构体表示。索引节点对象中inode_operations项描述了VFS用以操作索引节点对象的所有方法,调用方式是:

    i-i_op->truncate(i);
    

    目录项对象由dentry结构体表示,没有对应的磁盘数据结构。目录项对象有三种有效状态:被使用、未被使用、负状态。dentry_operation结构体指明了VFS操作目录项的所有方法。
    目录项缓存包括三个主要部分:“被使用的”目录项链表、“最近被使用”的双向链表、散列表和响应的散列函数。
    文件对象由file结构体表示,它是已打开文件在内存中的表示。由open()系统调用创建,由close()系统调用撤销。
    file_struct、fs_struct和namespace三个结构把VFS层和系统的进程紧密联系在一起。
    设备类型分为块设备和字符设备,区分在于是否可以随机访问数据。
    内核中块I/O操作的基本容器由bio结构体表示。

    总结

    本次在实验楼实验中由于不能git新的代码,只能通过修改原代码中内容和手动添加hello.c以及在makefile中照猫画虎的添加关于hello.c的编译链接的相关内容。以后可以学习下MakeFile,对在Linux下编程又不晓得用处。

  • 相关阅读:
    Symmetrical Network Acceleration with EBS 12
    Using Oracle Database In-Memory with Oracle E-Business Suite
    OI回忆录
    一只退役狗最后的想法
    codeforces-102501J Counting Trees题解
    CCPC2020 秦皇岛 H Holy Sequence
    CodeForces
    2020 ccpc 网络赛 1004 Chess Class
    2020 ccpc 网络赛 1012 Xor
    2020 ccpc 网络赛 1013 Residual Polynomial
  • 原文地址:https://www.cnblogs.com/308cww/p/6083600.html
Copyright © 2011-2022 走看看