标签:读取 物理 alt src pair 语法 组成 分析报告 停止
linux操作系统是一个基于POSIX的多用户、多任务、支持多线程的复杂系统。它的复杂程度难以想象,作为一个操作系统linux为用户提供进程管理、内存管理、设备控制以及网络管理等功能。
要学习如此错综复杂的系统,最主要的是要抓住其脉络,构建一个易于理解的模型。linux操作系统最主要的功能是管理用户程序,为用户程序执行提供资源,从这个角度本文描述linux系统的运行模型可以概括为如下几个模块:
存储程序计算机。目前的大多数计算机都是冯诺依曼型计算机,计算机硬件应由运算器、存储器、控制器、输入设备、输出设备5大基本类型部件组成。计算机内部采用二进制来表示指令和数据。将编好的程序和原始数据先存入存储器中经过CPU取值分析后才能执行,这就是存储程序的基本含义。
linux操作系统频闭了底层硬件的复杂性,为用户提供了简洁、易用的系统接口;并且负责管理所有的硬件资源,采用巧妙的抽象技术支持多道程序运行以提高系统的资源利用率。
函数调用堆栈
无论是面向对象的程序设计方式还是面向过程的程序设计方式,函数都是程序的基本单位,函数之间的相互调用是构造复杂程序的基础。在linux操作系统通过堆栈这一简单的数据结构,使得函数调用的模型变得简单易于理解。
每一个函数执行时都有自己的栈帧,栈帧内存储局部变量、上一个栈帧的栈基址,当要调用其他函数时还会存储调用返回的返回地址,以及被调用函数的参数列表。
中断处理机制
正常情况下CPU执行的指令流是按照执行在内存的地址顺序执行的,但是由于内外部事件或由程序预先安排的事件会引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。
从广义上讲,中断又可分为四类:
IO中断,外部设备通知CPU设备已就绪等待下一步指令。
故障,由于程序内部异常导致的可恢复系统故障,常见的有:数组越界、除零错以及缺页中断的。
陷阱,用户程序通过调用访管指令调用操作系统提供的系统调用,获得操作系统的服务,获取系统资源。
终止,有程序或硬件引发的不可恢复的系统故障。
中断不光为系统处理异常事件提供了统一的实现模型,还是用户程序获取系统服务的入口,以及进程调度的时机。
系统调用服务
由于计算机的硬件资源是有限的,操作系统的基本功能就是管理硬件建资源。为了减少多个进程对有限资源的访问冲突,CPU和操作系统将指令划分为普通指令和特权指令,和资源相关的指令均属于特权指令,CPU在执行特权指令时处于内核态。用户程序只能执行普通指令,要执行特权指令必须通过操作系统提供的系统调用接口。
系统调用封装了访问系统资源的方式,位用户进程与硬件设备的交互提供了一组简单易用的接口。在日常编程中处处离不开系统调用的身影,最常见的就是通过库函数提供的printf
语句访问系统输出设备,printf
函数的API内部便封装了访问硬件的系统调用。
当用户态进程通过库函数访问系统调用时,首先会通过系统调用堆栈在用户态堆栈保存调用函数的现场和程序断点;然后通过中断机制CPU进入内核态执行系统调用,系统调用执行完成后返回前操作系统会检查中断向量判断是否有可执行的中断;最后还会运行schedule()
函数判断是否需要进行调度,如果不需要调度就恢复用户程序的函数堆栈,从断点继续执行。
进程调度
linux操作系统是多任务系统,通过进程管理实现多个进程的公平调度尽可能提高系统的吞吐量、资源利用率。具体的,linux内核通过schedule()
函数实现进程的调度,schedule()
通过调度算法在运行队列中找到下一个运行的进程,并把CPU分配给它。
进程调度时机可以总结如下:
用户进程通过特定的系统调用主动让出CPU
中断处理程序在内核返回用户态
内核线程主动调用schedule()
让出CPU
中断处理程序主动调用schedule()
让出CPU
举例一个有文件读写操作的用户程序,用户程序的代码经过编译、链接、装载之后数据和代码都存储在内存中,函数调用关系通过堆栈来实现;在执行过程中,操作系统会发生时钟中断CPU进入内核态执行中断处理程序;在中断处理程序执行完毕后,操作系统会调用schedule()
判断当前的进程时间片是否已用完来决定是否进行调度;当用户进程执行read()
函数时,操作系统要执行中断指令进入内核态,根据中断描述符找到服务例程sys_read()
,将文件内容读入到内核态再复制到用户地址空间,完成文件读操作。中断服务程序返回前,操作系统同样需要执行schedule()
判断是否需要执行进程调度。
在Linux下使用GCC将源码编译成可执行文件的过程可以分解为4个步骤,分别是预处理、编译、汇编和链接。一个简单的hello word程序编译过程如下:
预处理
预编译过程主要处理那些源代码中以#开始的预编译指令,主要过程有:
删除所有的#define
,并且展开所有的宏定义;
处理所有条件编译指令,如#if,#ifdef等;
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s)。
汇编
汇编就是将汇编代码转变成机器可以执行的命令,生成目标文件(.o),在linux操作系统下的目标文件是ELF格式文件。ELF文件中将程序划分为不同的Section,主要有代码段(.text),未初始化的全局变量(.bss),已初始化的全局变量(.data)以及只读数据段(.rodata)等。
链接
链接就是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,经过链接这个文件可以被加载到内存执行。链接是可执行文件生产过程中最为复杂的部分,可以分为符号解析和重定位两部分;根据链接时机的不同,又可分为静态链接和动态链接。
可执行文件只有被装载到内存以后才能被CPU执行。操作系统创建一个进程,然后装载相应的可执行文件并且执行,整个过程可以描述如下:
创建一个独立的虚拟地址空间。创建虚拟空间实际上只是分配一个页目录,虚拟空间到物理内存的映射关系等到后面程序发生页错误的时候再进行设置。
读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。当操作系统捕获到缺页错误时,通过该映射关系就知道当前所需要的页在可执行文件中的位置。这种映射关系是按照段进行映射的,进程虚拟空间中的一个段叫做虚拟内存区域。
将CPU的执行寄存器设置成可执行文件的入口地址,启动运行。ELF文件头中保存了入口地址,操作系统通过设置CPU指令寄存器将控制权转交给进程,由此进程开始执行。
用户程序执行过程中CPU按照指令的地址顺序不断取址执行,如果存在函数调用关系会在用户态堆栈中创建新的栈帧,并跳转执行指令;当访问系统调用时首先在用户态栈帧保存断电,然后CPU通过中断指令进入内核态执行系统调用;当进程被阻塞或执行中断处理函数结束时,操作系统都会执行schedule()
函数判断是否需要进行进程调度,若需要会切换进程上下文执行新的进程。事实上进程在执行结束前会不断地被调入,调出。
经过上述分析可知影响一个应用程序运行的因素大致可以分为三种:
应用程序频繁的访问系统调用,CPU需要不断地完成从用户态与内核态之间的切换,降低系统性能。
应用程序的运算量较大,CPU过于繁忙,此时CPU性能是瓶颈。
标签:读取 物理 alt src pair 语法 组成 分析报告 停止
原文地址:https://www.cnblogs.com/lzlaa/p/14754999.html