一、上课学习笔记

1、shell作用:①运行程序 ②重定向(输入/输出重定向) ③可编程(写脚本)

执行一个c程序时,如果切进另一个进程,会进入该进程而切不回原进程,所以需要为调用的进程创一个子进程。

2、fork()

getpid()自己进程的id, getppid()父亲的进程, fork()的返回值是自己的子进程。

二、课本与视频学习内容

1、ELF格式 头部+代码+数据

ELF文件加载到内存时:把代码段和数据加载到内存中,默认从ELF文件头部信息中的Entry point address开始加载(程序的实际入口,可执行文件加载到内存中开始执行的第一行代码)。一般静态链接会将所有代码放在一个代码段,而动态链接的进程会有多个代码段。

目标文件的格式ELF:

最开始用的目标文件格式是A.out,后来发展成COFF文件格式,现在常用PE(在windows中常用)和ELF(在linux中常用)

ABI:应用程序二进制文件,是二进制兼容文件(目标文件已经适应某一种CPU体系结构的二进制指令)

ABI和目标文件格式的关系:目标文件也叫ABI

2、ELF三种目标文件类型

(1)可重定位文件:保存代码和适当数据,用来和其他object文件一起创建可执行文件或者共享文件。(.o文件)

(2)可执行文件:保存用来执行的程序,该文件指出了exec(BA_OS)如何来创建程序进程映像。

(3)共享object文件:保存代码和合适的数据,用来被下面两个链接器链接,第一个是链接编辑器(静态链接时,可以和其他的重定位和共享object文件创建其他的object),第二个是动态链接器(联合一个可执行文件和其他的共享object文件创建一个进程映像)

object文件:程序的链接(用来创建一个程序):ELF头(保存了路线图,描述该文件的组织情况),程序头表(告诉系统如何来创建一个进程的内存映像),多个节,section头表:包含了描述文件section的信息。每个section在这个头表中有一个入口,每个入口给出了该section的名字和大小等信息。

程序的执行(用来执行一个程序):ELF头,程序头表,多个段,段头表。

当创建或者增加一个进程映像的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。(代码段和数据段)

3、可执行程序是怎么得来的

以C语言代码为例,经过编译器的预处理(下图省略这一步)后编译(gcc)成汇编代码(.asm),再经过汇编器将之编译(gas)为目标代码(.o),之后再链接(ld)成一个可执行文件(.out),之后操作系统将它加载到内存中执行。



以C语言实例进行说明:



hello.static静态编译出的文件,将所有执行需要的依赖放入文件中,所以文件会比hello文件大很多。

4、装载可执行程序之前的工作

一般我们通过shell启动一个可执行程序,那么shell程序帮我们做了什么?

可执行程序的执行环境

(1)命令行参数和shell环境,一般我们执行一个程序的shell环境,我们的实验直接使用exevue系统调用。shell本身不限制命令行参数的个数,命令行参数个数受限于命令本身

例如

int main(int argc,char *argv[])用户自己设置的参数

int main(int argc,char *argv[],char * envp[])//envp[]代表shell环境变量

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

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

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

命令行参数和环境变量是如何保存和传递的?

命令行参数和环境串都放在用户态堆栈中,当创建一个子进程的时候,完全复制父进程的信息,在调用execve系统调用时,当前要加载的可执行程序的进城环境把原来的进程环境覆盖掉,覆盖掉之后,用户态堆栈也被清空,那么
命令行参数和环境变量时如何进入到新程序的堆栈的?**

shell程序->execve->sys->execve,通过指针的方式,在初始化新程序堆栈时拷贝进去。

先函数调用参数传递,再系统调用参数传递。

5、 sys_execve的内部处理过程

sys_execve->do_execve->do_execve_common->exec_binprm

SYSCALL_DEFINE(系统调用的入口)调用do_execve(getname(filename),argv,envp)

在do_execve(struct filename *filename,const char __user *const __user *__argv,struct char __user *const __user *__envp)用户态指针,在它内部调用do_execve_common(filename,argv,envp)

在do_execve_common中,有

file = do_open_exec(filename);//打开可执行文件
//之后加载可执行文件的头部
retval = exec_binprm(bprm)//对可执行文件的处理过程

在exec_binprm(struct linux_binprm *bprm)中,可执行文件的处理过程,其中,关键代码有

ret = search_binary_handler(bprm);//寻找可执行文件的处理函数,在这个函数值中,寻找可以解析这个可执行文件的代码模块。

if(elf_interpreter),如果需要动态链接的话(即需要依赖动态库的话),需要加载load_elf_interp动态装载器的起点(即ld的链接器)。

6、重定位

重定位是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程,也就是说在装入时对目标程序中指令和数据的修改过程。是实现多道程序在内存中同时运行的基础

重定位的步骤

重定位和符号定义:链接器将所有的相同类型的节合并为同一类型的新的聚合节,将运行时存储器地址赋给新的聚合节,、输出模块定义的每个节,以及输入模块定义的每个符号。

重定位节中的符号的引用:链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。

符号表记录了目标文件中所有全局函数以及其地址。

重定位表中记录了所有调用这些函数的代码位置。

7、静态链接与动态链接

(1)静态链接

在编译链接时直接将需要的执行执行代码复制到最终可执行文件中,编译时会把需要所有代码链接进去,应用程序相对比较大

优点:代码装载速度快,,执行速度快,对外部环境依赖度低。

缺点:如果多个应用程序使用同一库函数,会被装载多次,浪费内存。

(2)动态链接

编译时不直接复制可执行代码,而是通过记录一系列符号和参数,在程序中运行或者加载时将这些信息传递给操作系统。操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定代码时,去共享执行内存中已经加载的动态库去执行代码,最终达到运行时链接的目的。

优点:多个程序可以共享同一段代码,而不需要在磁盘上存储多个复制。

缺点:影响程序前期运行性能,对使用的库依赖性较高。

动态链接分为运行时的动态链接和装载时的动态链接

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

先切换到 LinuxKernel目录下

rm menu -rf //删除menu

git clone https://github.com/mengning/menu.git //下载克隆新的menu

cd menu //切换到menu目录下

mv test_exce.c test.c //把test_exce.c 改成 test.c ,这里是应为Makefile里面用的是test.c

vi test.c //查看一下test.c的内容

vi makefile //查看一下Makefile的内容

make rootfs

再打开一个新的终端

gdb

file ../linux-3.18.6/vmlinux

target remote:1234

设置断点

2 b sys_execve (可以先停在sys_exceve然后再设置其他断点)

2 b search_bianry_handler

2 b load_elf_binary

2 b start_thread

如图所示:







Linux系统加载可执行程序所需处理过程的理解

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

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

如果这个新程序是静态链接的,那么这个程序就可以独立运行,elf头中的这个入口地址就是本程序的入口地址。

如果这个新程序是动态链接的,那么此时还需要装载共享库,elf头中的这个入口地址是动态链接器ld的入口地址。

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

新的可执行程序执行,需要以下:

(1)它所需要的库函数。

(2)属于它的进程空间:代码段,数据段,内核栈,用户栈等。

(3)它所需要的运行参数。

(4)它所需要的系统资源。

如果满足以上4个条件,那么新的可执行程序就会处于可运行态,只要被调度到,就可以正常执行。我们一个一个看这几个条件能不能满足。

条件1:如果新进程是静态链接的,那么库函数已经在可执行程序文件中,条件满足。如果是动态链接的,新进程的入口地址是动态链接器ld的起始地址,可以完成对所需库函数的加载,也能满足条件。

条件2:execve系统调用通过大幅度修改执行上下文,将用户态堆栈清空,将老进程的进程空间替换为新进程的进程空间,新进程从老进程那里继承了所需要的进程空间,条件满足。

条件3:我们一般在shell中,输入可执行程序所需要的参数,shell程序把这些参数用函数参数传递的方式传给给execve系统调用,然后execve系统调用以系统调用参数传递的方式传给sys_execve,最后sys_execve在初始化新程序的用户态堆栈时,将这些参数放在main函数取参数的位置上。条件满足。

条件4:如果当前系统中没有所需要的资源,那么新进程会被挂起,直到资源有了,唤醒新进程,变为可运行态,条件可以满足。

综上所述,新的可执行程序可以顺利执行。

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

execve系统调用会调用sys_execve,然后sys_execve调用do_execve,然后do_execve调用do_execve_common,然后do_execve_common调用exec_binprm,在exec_binprm中:

对于ELF文件格式,fmt函数指针实际会执行load_elf_binary,load_elf_binary会调用start_thread,在start_thread中通过修改内核堆栈中EIP的值,使其指向elf_entry,跳转到elf_entry执行。

对于静态链接的可执行程序,elf_entry是新程序的执行起点。对于动态链接的可执行程序,需要先加载链接器ld,

elf_entry = load_elf_interp(…)

将CPU控制权交给ld来加载依赖库,再由ld在完成加载工作后将CPU控制权还给新进程。

2019-2020-1 20199302《Linux内核原理与分析》第八周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  5. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. shared_ptr 用法

    引入 shared_ptr 是c++为了提高安全性而添加的智能指针,方便了内存管理. 特点 shared_ptr 是通过指针保持对象共享所有权的智能指针.多个 shared_ptr 对象可占有同一对象 ...

  2. 通过fsockopen()方法从中国福彩网获取双色球历史中奖数据

    # 以下代码基于 CI 框架 # public function history_draw($page = 0) { set_time_limit(0); $page++; $up2now = dat ...

  3. java知识精要(一)

    一.java数组 (疯狂java讲义 第4.5 ~ 4.6章节) 1) 声明形式: type[] arrayName; 推荐方式 type arrayName[]; 2) 初始化: 方式一: type ...

  4. scala学习遇到的坑

    1:软件是idea,在同一个包中,类名一样,会导致在调用类的时候,程序无法找到正确的类,最后出错. 2:return坑,在方法中,返回值的类型已经指定了,所以可以放心使用return,但是在函数中,没 ...

  5. JZOJ5833 永恒

    题目大意 给你一个树,每个节点上有有一个部落,以及部落的人数,要你求出每个节点的子树里面人数最多的部落是哪一个(人数相同部落编号最小的). 思路 全网第一篇分治题解 考虑树的dfs序,然后分治处理,每 ...

  6. IDEA 2019 快捷键终极大全

    常用的有fori/sout/psvm+Tab即可生成循环.System.out.main方法等boilerplate样板代码 . 例如要输入for(User user : users) 只需输入use ...

  7. AspNetCore MVC页面数据提交验证

    2019/05/14,AspNetCore 2.2.0 摘要:AspNetCore MVC使用数据注释配合jquery.validate提交页面进行前端加后端的数据验证 主要用到了jquery.val ...

  8. 用C#实现Rabbitmq应用的小实例

    RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件).RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的.所有主要 ...

  9. Tomcat组件梳理—Digester的使用

    Tomcat组件梳理-Digester的使用 再吐槽一下,本来以为可以不用再开一个篇章来梳理Digester了,但是发现在研究Service的创建时,还是对Digester的很多接口或者机制不熟悉,简 ...

  10. Flask整合WebLoader 用于大附件拆分上传再合并

    博客:https://blog.csdn.net/jinixin/article/details/77545140 github:https://github.com/jinixin/upload-d ...