20169219《linux内核原理与分析》第九周作业
网易云课堂学习
可执行程序的装载
可执行程序的产生过程:预处理-----> 编译 ----> 汇编 ----> 链接
以hello.c文件为例进行分析,编译步骤如下
vi hello.c
gcc -E -o hello.cpp hello.c -m32
vi hello.cpp
gcc -x cpp-outout -S -o hello.s hello.cpp -m32
vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32
vi hello.o
gcc -o hello hello.o -m32
vi hello
gcc -o hello.static hello.o -m32 -static
结果如下图
编程使用exec*库函数加载一个可执行文件.
静态链接和动态链接的区别
动态库链接时,会在运行时选择需要的部分进行编译,生成的可执行文件会比较小,而且容易后续的更新。静态编译会把所有的函数等都嵌入到可执行文件中,可以直接在任何电脑上直接运行.在静态连接程序中,所有的程序代码包好在一个可执行模块中。因为库程序静态连接到应用程序中,库的引用效率更高。静态连接增加应用程序文件的大小,如果系统同时还运行其他的应用程序时,静态连接也会增加内存中代码的大小。
加载方式上:
1.静态库只能使用静态加载方式,静态加载就是编译、链接的时候把静态库代码拷贝到程序中。
2.动态库可以使用“静态加载”和“动态加载”,静态加载就是在程序启动的时候把库所有的内容加载到内存,动态加载就是用到哪个函数所在的模块就加载对应的库就行。(这是因为,动态库可能也很大,一口气全部加载也是非常耗时,所以运行时用到谁就加载谁,就是动态加载)。
静态库、共享库以及动态加载库
(1) 静态库
静态库是一些目标文件的集合,库文件以”.a”结尾,连接器会将应用程序所需要用到的代码拷贝到应用程序中.
静态库的优缺点:
静态库增加了应用程序的大小,另外在处理静态库更新问题上需要花费更多的重编译代价(recompile),但是理论上,静态库应该比共享库或者动态加载库运行更快(1-5%),因为它减少了在程序运行才去加载库的开销。
(2) 共享库:
与静态库不同的是,共享库在链接阶段并不需要拷贝所需使用的代码,而只是做些参考标记,然后在程序启动时加载所需要的库文件,因此,对比静态库,链接共享库的应用程序小得多。
共享库的优缺点:
对比静态库,共享库在链接阶段只是对所需代码做些标识,在程序启动时才会加载,这样减少了目标应用程序的大小,通过soname,可以做到多版本的兼容,这样每次升级也不需要将原代码全部重新编译,免除了在升级过程中重编译带来的开销;但是,因为需要在程序运行阶段加载共享库,这样势必需要付出运行期间的库加载代价。
(3)动态加载库 (Dynamically loaded libraries)
是指在程序运行过程中可以加载的函数库,而不像共享库是在程序启动的时候加载。DLL对实现插件和模块非常实用,因为它们运行程序在允许时等待插件的加载,动态加载库有自己的一套API接口去完成打开、查找符号,处理出错、关闭加载库等功能。
进程的切换和系统的一般执行过程
不同类型的进程有不同的调度需求
第一种分类:
(1)I/O-bound
- 频繁的进行I/O
- 通常会话费很多时间等待I/O操作完成
(2)CPU-bound- 计算密集型
- 需要大量的CPU时间进行运算
第二种分类:
(1)批处理进程
- 不必与用户交互,通常在后台运行
- 不必很快响应
- 典型的批处理程序:编译程序、科学计算
(2)实时进程- 有实时需求,不必被低优先级的进程阻塞
- 响应时间要短、要稳定
- 典型实时进程:视频/音频、机械控制等
(3)交互式进程- 需要经常与用户交互,因此要花很多时间等待用户输入操作
- 相应时间要快,平均延迟要低于50-150ms
- 典型的交互式进程:shell、文本编辑程序、图形应用程序等
进程调度算法
(1)先进先出算法:
算法总是把处理机分配给最先进入就绪队列的进程,一个进程一旦分得处理机,便一直执行下去,直到该进程完成或阻塞时,才释放处理机。
(2)短进程优先
最短CPU运行期优先调度算法(SCBF--Shortest CPU Burst First)
该算法虽可获得较好的调度性能,但难以准确地知道下一个CPU执行期,而只能根据每一个进程的执行历史来预测。
(3)轮转法:
前几种算法主要用于批处理系统中,不能作为分时系统中的主调度算法,在分时系统中,都采用时间片轮转法。
简单轮转法:系统将所有就绪进程按FIFO规则排队,按一定的时间间隔把处理机分配给队列中的进程。这样,就绪队列中所有进程均可获得一个时间片的处理机而运行。
多级队列方法:将系统中所有进程分成若干类,每类为一级。
(4)多级反馈队列
多级反馈队列方式是在系统中设置多个就绪队列,并赋予各队列以不同的优先权。
Linux中进程的优先级是动态的,较长时间未分配到CPU的进程,优先级会升高;已经在CPU上运行了较长时间的进程,优先级会下降。
进程的调度时机与进程的切换
操作系统原理中介绍了大量进程调度算法,这些算法从实现的角度看仅仅是从运行队列中选择一个新进程,选择的过程中运用了不同的策略而已。
对于理解操作系统的工作机制,反而是进程的调度时机与进程的切换机制更为关键。
进程调度的时机
- 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
- 内核线程(特殊的进程,只有内核态没有用户态)可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
- 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。
进程的切换
- 为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
- 挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行;
- 进程上下文包含了进程执行需要的所有信息
- 用户地址空间:包括程序代码,数据,用户堆栈等
- 控制信息:进程描述符,内核堆栈等
- 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
- schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
- next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
- context_switch(rq, prev, next);//进程上下文切换
- switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
使用gdb跟踪调用schedule函数
在time系统调用返回前,调用schedule(),在schedule设置断点:
Linux系统的一般执行过程
最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
- 正在运行的用户态进程X
- 发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
- SAVE_ALL //保存现场
- 中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
- 标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
- restore_all //恢复现场
- iret - pop cs:eip/ss:esp/eflags from kernel stack
- 继续运行用户态进程Y
几种特殊情况
- 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
- 内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
- 创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
- 加载一个新的可执行程序后返回到用户态的情况,如execve;
虚拟文件系统
虚拟文件系统中(VFS)有四个主要的对象类型
超级块对象:代表一个具体的已安装文件系统;
索引节点对象:代表一个具体文件;
目录项对象:代表一个目录项,是路径的一个组成部分;
文件对象:代表由进程打开的文件
块I/O层
块设备中最小的可寻址单元是扇区。扇区是设备的最小寻址单元;块是文件系统的最小寻址单元。块包含一个或多个扇区,但大小不能超过一个页面,所以一个页面可以容纳一个或多个内存的块。
目前内核中块I/O操作的基本容器由bio结构体表示,它代表了正在现场的(活动的)以片段链表形式组织的块I/O操作。
磁盘寻址是整个计算机系统中最慢的操作之一,所以尽量缩短寻址时间是提高系统性能的关键。
I/O调度程序通过两种方法减少磁盘寻址时间:合并和排序。
出现的问题
在使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve的过程中,需要在执行exec命令的时候同时输出“hello world!”,需要修改Makefile文件里面的代码,使系统在执行exec命令的时候同时执行hello.c文件。
只需要把Makefile文件里的“cp hello ../rootfs/”这行代码去掉就能解决问题,但是我还是不理解具体原因。!