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下编程又不晓得用处。

原文地址:https://www.cnblogs.com/308cww/p/6083600.html