Linux内核如何装载和启动一个可执行程序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核如何装载和启动一个可执行程序相关的知识,希望对你有一定的参考价值。

王晨光 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

姓名:王晨光

学号:20133232

 

一、网络课程笔记:

1)预处理、编译和链接

      ELF头部在文件的开始,描述文件的总体格式,保存了路线图,描述该文件的组织情况,即生成该文件系统的字的大小和字节顺序 段头部表用来描述ELF可执行文件与连续的存储段之间的映射关系。节头表包含了描述文件节区的信息,每个节区在表中都有一个项,给出节区的名称、节区大小这类心里。用于链接的目标文件(可重定向文件)必须包含节区头部表,而可执行文件可以没有。

(2)可执行程序的执行环境

     命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

     $ ls -l /usr/bin列出/usr/bin下的目录信息

     Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身

     例如,int main(int argc, char*argv[])

     又如, int main(int argc, char*argv[], char *envp[])

     Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

     int execve(const char *filename,char * const argv[ ],char * const envp[ ]);

     库函数exec*都是execve的封装例程

     可执行程序的装载

(3)

程序编译链接过程

预处理:(.c -> .cpp)

gcc -E -o hello.cpp hello.c -m32

编译:(.cpp -> .s 汇编)

gcc -x cpp-output -S -o hello.s hello.cpp -m32

编译:(.s -> .o 二进制目标代码)

gcc -x assembler -c hello.s -o hello.o -m32

链接:(.o -> a.out)共享库

gcc -o hello hello.o -m32

静态编译:

gcc -o hello.static hello.o -m32 -static

(4)浅析动态链接的可执行程序的装载

1、可以关注ELF格式中的interp和dynamic。

2、动态链接库的装载过程是一个图的遍历。

3、装载和连接之后ld将CPU的控制权交给可执行程序。

 

二、实验内容

打开实验楼中的虚拟机,在shell中依次运行以下命令,获取本次实验的代码,并编译运行

cd LinuxKernel

rm menu -rf

git clone https://github.com/mengning/menu.git

cd menu

mv test_exec.c test.c

make rootfs 

技术分享

技术分享

关闭QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目录,使用下面的命令启动内核并在CPU运行代码前停下以便调试:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S 

接下来,我们就可以水平分割一个新的shell窗口出来,依次使用下面的命令启动gdb调试

gdb

(gdb) file linux-3.18.6/vmlinux

(gdb) target remote:1234

并在系统调用sys_execve的入口处设置断点

(gdb) b sys_execve

继续运行程序,在QEMU窗口中输入exec,系统就会停在上面设置的断点处。

相关截图如下:

技术分享

技术分享

技术分享

技术分享

接下来我们可以单步跟踪sys_execve的内核代码,也可以通过设置以下断点

b load_elf_binary

b start_thread

来完整地跟踪进程的创建和启动代码!

三、实验问题与理解

1、新的可执行程序是从哪里开始执行的?

当execve()系统调用终止且进程重新恢复它在用户态执行时,执行上下文被大幅度改变,要执行的新程序已被映射到进程空间,从elf头中的程序入口点开始执行新程序。

2、为什么execve系统调用返回后新的可执行程序能顺利执行?

新的可执行程序执行需要库函数,属于它的进程空间:代码段,数据段,内核栈,用户栈等,需要运行参数和系统资源。 如果满足条件,那么新的可执行程序就会处于可运行态,只要被调度到,就可以正常执行。

3、对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

对于静态链接的可执行程序,elf_entry是新程序的执行起点。对于动态链接的可执行程序,需要先加载链接器ld,
elf_entry = load_elf_interp(…)
将CPU控制权交给ld来加载依赖库,再由ld在完成加载工作后将CPU控制权还给新进程。

四、总结

       实验的操作还是很简单,但是理解具体的实质内容却很困难,参考了云课堂的课件和以前的别人写过的相关博客,才有了一定的了解。

       在Linux中,fork是进程创建另一个进程的唯一方法。只有第一个进程也就是被称作 init 的进程需要 手工创建 。所有其他进程都是用fork这个系统调用创建的。fork系统调用只是复制了父进程的数据和堆栈,并在这两个进程之间共享文本区。fork系统调用采用比较聪明的方式— 写时拷贝(copy-on-write) 技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复制,这样使效率大大提高。fork函数创建了一个子进程后,子进程会调用exec族函数执行另外一个程序。
      多进程、多用户、虚拟存储的操作系统出现以后,可执行文件的装载过程变得非常复杂。引入了进程的虚拟地址空间;然后根据操作系统如何为程序的代码、数据、堆、栈在进程地址空间中分配,它们是如何分布的;最后以页映射的方式将程序映射进程虚拟地址空间。 
      动态链接是一种与静态链接程序不同的概念,即一个单一的可执行文件模块被拆分成若干个模块,在程序运行时进行链接的一种方式。然后根据实际例子do_exece()分析了ELF装载的大致过程,中间实现了动态链接。

 

 

以上是关于Linux内核如何装载和启动一个可执行程序的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核如何装载和启动一个可执行程序(转)

Linux内核如何装载和启动一个可执行程序

《Linux内核分析》课程第七周学习总结

Linux内核分析之可执行程序的装载和启动

第七周 linux如何装载和启动一个可执行文件

Linux内核分析作业 NO.7