Linux实验总结分析报告


Linux实验总结分析报告

一、Linux系统概念模型

linux操作系统是一个基于POSIX的多用户、多任务、支持多线程的复杂系统。它的复杂程度难以想象,作为一个操作系统linux为用户提供进程管理、内存管理、设备控制以及网络管理等功能。要学习如此错综复杂的系统,最主要的是要抓住其脉络,构建一个易于理解的模型。linux操作系统最主要的功能是管理用户程序,为用户程序执行提供资源,从这个角度本文描述linux系统的运行模型可以概括为如下几个模块:

  1. 存储程序计算机。目前的大多数计算机都是冯诺依曼型计算机,计算机硬件应由运算器、存储器、控制器、输入设备、输出设备5大基本类型部件组成。计算机内部采用二进制来表示指令和数据。将编好的程序和原始数据先存入存储器中经过CPU取值分析后才能执行,这就是存储程序的基本含义。

    linux操作系统频闭了底层硬件的复杂性,为用户提供了简洁、易用的系统接口;并且负责管理所有的硬件资源,采用巧妙的抽象技术支持多道程序运行以提高系统的资源利用率。

  2. 函数调用堆栈

    无论是面向对象的程序设计方式还是面向过程的程序设计方式,函数都是程序的基本单位,函数之间的相互调用是构造复杂程序的基础。在linux操作系统通过堆栈这一简单的数据结构,使得函数调用的模型变得简单易于理解。

    每一个函数执行时都有自己的栈帧,栈帧内存储局部变量、上一个栈帧的栈基址,当要调用其他函数时还会存储调用返回的返回地址,以及被调用函数的参数列表。

  3. 中断处理机制

    正常情况下CPU执行的指令流是按照执行在内存的地址顺序执行的,但是由于内外部事件或由程序预先安排的事件会引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

    中断不光为系统处理异常事件提供了统一的实现模型,还是用户程序获取系统服务的入口,以及进程调度的时机。

  4. 系统调用服务

    由于计算机的硬件资源是有限的,操作系统的基本功能就是管理硬件建资源。为了减少多个进程对有限资源的访问冲突,CPU和操作系统将指令划分为普通指令和特权指令,和资源相关的指令均属于特权指令,CPU在执行特权指令时处于内核态。用户程序只能执行普通指令,要执行特权指令必须通过操作系统提供的系统调用接口。

    系统调用封装了访问系统资源的方式,位用户进程与硬件设备的交互提供了一组简单易用的接口。在日常编程中处处离不开系统调用的身影,最常见的就是通过库函数提供的printf语句访问系统输出设备,printf函数的API内部便封装了访问硬件的系统调用。

    当用户态进程通过库函数访问系统调用时,首先会通过系统调用堆栈在用户态堆栈保存调用函数的现场和程序断点;然后通过中断机制CPU进入内核态执行系统调用,系统调用执行完成后返回前操作系统会检查中断向量判断是否有可执行的中断;最后还会运行schedule()函数判断是否需要进行调度,如果不需要调度就恢复用户程序的函数堆栈,从断点继续执行。

  5. 进程调度

linux操作系统是多任务系统,通过进程管理实现多个进程的公平调度尽可能提高系统的吞吐量、资源利用率。具体的,linux内核通过schedule()函数实现进程的调度,schedule()通过调度算法在运行队列中找到下一个运行的进程,并把CPU分配给它。

 

模型验证

一个有文件读写操作的用户程序,用户程序的代码经过编译、链接、装载之后数据和代码都存储在内存中;执行时操作系统的shell调用fork创建一个子进程,子进程再调用execve构造用户程序的用户堆栈,加载函数参数与环境变量,子进程返回后从用户程序入口开始执行;

在执行过程中如果需要调用函数,首先需要将函数参数压栈,然后调用call指令将返回地址压栈,最后为被调用函数构造一个新栈帧;被调用函数执行完毕后,首先销毁栈帧,然后将返回地址弹出到RIP寄存器中使得CPU能够从调用函数的下一条语句继续执行;

在执行过程中CPU可能会相应各种各样的中断事件,最常见的如时钟中断、read()系统调用引发的软中断等。当中断发生时CPU需要进入内核态执行中断处理程序,与函数调用处理过程类似,首先需要进行中断上下文的切换,不同的是需要将被中断进程的断点和寄存器上下文保存到进程的内核栈中;紧接着根据中断调用号查找中断向量表确定服务例程的地址,开始执行中断服务程序;具体地当用户进程执行read()函数时,操作系统要执行中断指令进入内核态,根据中断描述符找到服务例程sys_read(),将文件内容读入到内核态再复制到用户地址空间,完成文件读操作。

中断服务程序完成后,中断返回前是操作系统进行进程调度的时机,操作系统会执行schedule()判断是否需要执行进程调度。若需要则根据调度算法从进程的就绪队列中选取下一个可运行的进程,完成进程上下文的切换;若不需要,则正常的中断返回,从被中断函数的下一条语句继续执行。

在上述所述的概念模型中,操作系统通过堆栈管理着用户程序的调用关系,通过系统调用想用户程序提供资源服务,通过中断机制处理异常情况,通过进程调度使得整个系统能够生生不息,延绵有序地执行下去。

二、应用程序生命周期与影响程序性能的因素

在Linux下使用GCC将源码编译成可执行文件的过程可以分解为4个步骤,分别是预处理、编译、汇编和链接。一个简单的hello word程序编译过程如下:

  1. 预处理

    预编译过程主要处理那些源代码中以#开始的预编译指令,主要过程有:

    • 删除所有的#define,并且展开所有的宏定义;

    • 处理所有条件编译指令,如#if,#ifdef等;

    • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。

  2. 编译

    编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s)。

  3. 汇编

    汇编就是将汇编代码转变成机器可以执行的命令,生成目标文件(.o),在linux操作系统下的目标文件是ELF格式文件。ELF文件中将程序划分为不同的Section,主要有代码段(.text),未初始化的全局变量(.bss),已初始化的全局变量(.data)以及只读数据段(.rodata)等。

  4. 链接

    链接就是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,经过链接这个文件可以被加载到内存执行。链接是可执行文件生产过程中最为复杂的部分,可以分为符号解析和重定位两部分;根据链接时机的不同,又可分为静态链接和动态链接。

可执行文件只有被装载到内存以后才能被CPU执行。操作系统创建一个进程,然后装载相应的可执行文件并且执行,整个过程可以描述如下:

  1. 创建一个独立的虚拟地址空间。创建虚拟空间实际上只是分配一个页目录,虚拟空间到物理内存的映射关系等到后面程序发生页错误的时候再进行设置。

  2. 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。当操作系统捕获到缺页错误时,通过该映射关系就知道当前所需要的页在可执行文件中的位置。这种映射关系是按照段进行映射的,进程虚拟空间中的一个段叫做虚拟内存区域。

  3. 将CPU的执行寄存器设置成可执行文件的入口地址,启动运行。ELF文件头中保存了入口地址,操作系统通过设置CPU指令寄存器将控制权转交给进程,由此进程开始执行。

用户程序执行过程中CPU按照指令的地址顺序不断取址执行,如果存在函数调用关系会在用户态堆栈中创建新的栈帧,并跳转执行指令;当访问系统调用时首先在用户态栈帧保存断电,然后CPU通过中断指令进入内核态执行系统调用;当进程被阻塞或执行中断处理函数结束时,操作系统都会执行schedule()函数判断是否需要进行进程调度,若需要会切换进程上下文执行新的进程。事实上进程在执行结束前会不断地被调入,调出。

经过上述分析可知影响一个应用程序运行的因素大致可以分为三种:

  1. 应用程序频繁的访问系统调用,CPU需要不断地完成从用户态与内核态之间的切换,降低系统性能。

  2. 应用程序的运算量较大,CPU过于繁忙,此时CPU性能是瓶颈。

  3. 应用程序需要做大量的I/O,CPU频繁地进行进程调度,切换进程上下文。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM