一、计算机系统概述
1. 操作系统
(1)操作系统是什么?四大基本特征?操作系统功能?
问:是什么? 操作系统(Operating System,OS)是指控制和管理整个计算机系统的硬件与软件资源,合理地组织、调度计算机的工作与资源的分配,进而为用户和其他软件提供方便接口与环境的程序集合。 操作系统是计算机软件。
问:基本特征? 1. 并发(Concurrence) 并发是指两个或多个事件在同一时间间隔内发生。 宏观上多道程序同时执行,微观上交替执行。 2. 共享(Sharing) 资源共享即共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。 (1)互斥共享方式:打印机、磁带机;一段时间内只允许一个进程访问该资源。 (2)同时访问方式:允许一段时间内多个进程“同时”访问,“同时”是宏观上的,微观上可能交替。如磁盘设备 并发和共享是操作系统的两个最基本的特征,两者之间互为存在条件。 3. 虚拟(Virtual) 虚拟是指把一个物理上的实体变为若干逻辑上的对应物。 4. 异步(Asynchronism) 多道程序环境允许多个程序并发执行,但由于资源有限,进程的执行并不是一贯到底的,而是走走停停的,它以不可预知的速度向前推进,也就是进程的异步性。
问:操作系统功能? CPU管理、内存管理、文件管理、IO管理,向用户提供接口,作为扩充机器
(2)并行、并发区别?
1. 并行(parallelism)是指在同一时刻,有多条指令在多个处理器上同时执行; 无论从微观还是宏观上来看,都是多个独立任务同时执行, 适用于资源充足的情况,如多核。 2. 并发(concurrency)是指同一时刻只能有一条指令执行,但多个指令被快速地轮换执行; 宏观上看起来两个程序同时运行,微观上是两个程序在单核CPU上交替运行。 适用于资源有限的情况,如单核。
(3)操作系统的发展与分类
1. 手工操作阶段 2. 批处理阶段:单道批处理系统、多道批处理系统 3. 分时操作系统 4. 实时操作系统 5. 网格操作系统和分布式计算机系统 6. 个人计算机操作系统
2. 内核
(1)什么是内核?内核的功能?
问:什么是内核? 内核是计算机上配置的底层软件,是计算机功能的延伸。 //计算机由各种外部硬件设备组成的,比如CPU、内存、硬盘等,如果每个应用都要和这些硬件设备对接通过协议,那这样太累了,所以这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。 问:内核的功能? 1. 进程调度:管理进程、线程,决定哪个进程、线程使用CPU。 2. 内存管理:决定内存的分配和回收 3. 硬件通信:为进程与硬件设备之间提供通信能力 4. 提供系统调用:如果应用程序要运行更高权限的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。
(2)内核是怎么工作的?
内存分成两个区域: * 内核空间,这个内存空间只有内核程序可以访问 * 用户空间,这个内存空间专门给应用程序使用 用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。 当程序使用用户空间时,该程序在用户态执行;当程序使用内核空间时,程序在内核态执行。
(3)微内核与宏内核
宏内核:大内核系统将操作系统的主要功能模块都作为一个紧密联系的整体运行在核心态,从而为应用提供高性能的系统服务。 //除了最基本的进程、线程管理、内存管理外,将文件系统,驱动,网络协议等等都集成在内核里面,例如linux内核。 优点:效率高。 缺点:难以维护,稳定性差,开发过程中的bug经常会导致整个系统挂掉。 微内核:微内核将内核中最基本的功能(如进程管理等)保留在内核,而将那些不需要在核心态执行的功能移到用户态执行,从而降低了内核的设计复杂性。 //内核中只有最基本的调度、内存管理;驱动、文件系统等都是用户态(的守护进程)去实现的。 优点:稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃 缺点:效率低。典型代表QNX,QNX的文件系统是跑在用户态的进程,称为resmgr的东西,是订阅发布机制,文件系统的错误只会导致这个守护进程挂掉。不过数据吞吐量就比较不乐观了。
3. 中断
(1)中断?异常?中断和异常的区别?
问:为什么要有中断? 操作系统引入内核态和用户态,就要考虑这两种状态怎么切换,就是考中断实现状态转换
问:中断是什么? 中断(Interruption),也称为外中断,指来自CPU执行指令以外的事件的发生,//如设备发出I/O结束中断,表示设备输入/输出处理已经完成,希望处理机能够向设备发下一个输入/输出请求,同时让完成输入/输出后的程序继续执行。 中断是一种异步的事件处理机制,可以提高系统的并发处理能力。操作系统收到中断请求,会打断其他进程的运行,所以中断处理程序(中断请求的响应程序)要尽可能快的执行完,这样可以减少对正常进行运行调度地影响。
问:异常是什么? 异常(Exception)也称为内中断、或陷入(trap)。指源自CPU执行指令内部的事件,如程序的非法操作码、地址越界、算术溢出、虚存系统的缺页以及专门的陷入指令等引起的时间。 对异常的处理一般要依赖于当前程序的运行现场,而且异常不能屏蔽。一旦出现应立即处理。
问:中断和异常的区别? 外中断是由CPU执行指令以外的事件引起,如I/O完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。 异常是由CPU执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。
(2)什么是软中断?
中断请求的处理程序应该要短且快,这样才能减少对正常进程运行调度地影响,而且中断处理程序可能会暂时关闭中断,这时如果中断处理程序执行时间过长,可能在还未执行完中断处理程序前,会丢失当前其他设备的中断请求。 Linux系统将中断分成两个阶段:上半部和下半部分 * 上半部(硬中断)由硬件触发中断,快速处理中断,一般会暂时关闭中断请求, * 下半部(软中断)由内核触发中断,以内核线程的方式运行,用来异步处理上部分未完成的工作,通常耗时长,特点是延迟执行。 网卡接收网络包的例子: 网卡接收网络包后,会通过硬件中断通知内核有新的数据到了,于是内核就会调用对应的中断处理程序来响应该事件。 上部分要做到快速处理,所以只要把网卡的数据读到内存中,然后更新以下硬件寄存器的状态 接着,内核会触发一个软中断,把一些处理比较耗时且复杂的事情,交给软中断处理程序去做,其主要是需要从内存找到网络数据,再按照网络协议栈,对网络数据进行逐层解析和处理,最后把数据送给应用程序。
3. 系统调用
(1)为什么要有用户态和内核态?内核态、用户态区别?
为什么要有用户态和内核态? 答:为了安全性。在CPU的一些指令中,有的指令如果用错,将会导致整个系统崩溃。(比如设置时钟、内存清理) 分了内核态和用户态后,当用户需要操作这些指令时候,内核为其提供API,可以通过系统调用陷入内核(陷阱指令,trap instruction),让内核去执行这些操作。
内核态:处于内核态的CPU可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的CPU可以从一个程序切换到另一个程序,并且占用CPU不会发生抢占情况,一般处于特权级0的状态称为内核态。 用户态:处于用户态的CPU只能受限的访问内存,并且不允许访问外围设备,用户态下的CPU不允许独占,也就是说CPU能够被其他程序获取。 区别:特权级别不同。用户态拥有最低的特权级,内核态拥有较高的特权级。 //运行在用户态的程序不能直接访问操作系统内核数据结构和程序。
(2)用户态到内核态的切换方式
用户态切换到内核态的3种方式:系统调用、异常、中断 1、系统调用 这是用户进程主动要求切换到内核态的一种方式,用户进程通过系统调用申请操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的ine 80h中断。 2、异常 当CPU在执行运行在用户态的程序时,发现了某些事件不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就到了内核态,比如缺页异常。 3、外围设备的中断 当外围设备完成用户请求的操作之后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条将要执行的指令,转而去执行中断信号的处理程序,如果先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
(3)用户态到内核态的转化原理
(2)切换操作 从出发方式看,可以在认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一样的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断处理机制基本上是一样的。 用户态切换到内核态的步骤主要包括: 1、从当前进程的描述符中提取其内核栈的ss0及esp0信息。 2、使用ss0和esp0指向的内核栈将当前进程的cs、eip、eflags、ss、esp信息保存起来,这个过程也完成了由用户栈找到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。 3、将先前由中断向量检索得到的中断处理程序的cs、eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
(4)系统调用是什么?为什么要系统调用?系统调用的功能?
问:系统调用是什么? 系统调用是指用户在程序中调用操作系统所提供的一些子功能,系统调用可视为特殊的公共子程序。系统中的各种共享资源都由操作系统统一管理,在用户程序中凡是与资源有关的操作(存储分配、IO传输、文件管理)都必须通过系统调用方式向操作系统提供服务请求,并由操作系统代为完成。 系统调用(system call)指运行在使用者空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供了用户程序与操作系统之间的接口(即系统调用是用户程序和内核交互的接口)。
问:为什么要系统调用? 应用程序有时会需要一些危险的、权限很高的指令,如果把这些权限放心地交给用户程序是很危险的(比如一个进程可能修改另一个进程的内存区,导致其不能运行),但是又不能完全不给这些权限。 于是有了系统调用,危险的指令被包装成系统调用,用户程序只能调用而无权自己运行那些危险的指令。另外,计算机硬件的资源是有限的,为了更好的管理这些资源,所有的资源都由操作系统控制,进程只能向操作系统请求这些资源。操作系统是这些资源的唯一入口,这个入口就是系统调用。
问:系统调用的功能? 1. 设备管理:完成设备的请求或释放,以及设备启动等功能。 2. 文件管理:完成文件的读、写、创建以及删除等功能。 3. 进程管理:完成进程的创建、撤销、阻塞以及唤醒等功能。 4. 进程通信:完成进程之间的消息传递或信号传递等功能。 5. 内存管理:完成内存的分配、回收以及获取作业占用内存区大小及始址等功能。
(5)系统调用过程
当应用程序使用系统调用时,会产生一个中断。发生中断后,CPU会中断当前在执行的用户程序,转而跳转到中断处理程序,开始执行内核程序。内核处理完后,主动触发中断,把CPU执行权限交回给用户程序,回到用户态继续工作。
(6)库函数、系统调用区别?
1. 库函数属于语言或应用程序的一部分; 系统调用是内核提供给应用程序的接口,属于系统的一部分。 2. 库函数在用户地址空间执行,运行时间属于用户时间,开销较小; 系统调用是在内核地址空间执行,运行时间属于系统时间,开销较大。 3. 库函数采用缓冲区技术,可以在读写文件时减少系统调用次数,从而提高效率。
4. 其他
(1)什么是大端和小端?如何判断一个系统是大端还是小端?
* 大端就是高位字节存放在内存的低地址段,低位字节存放在内存的高地址段; * 小端就是低位字节存放在内存的低地址段,高位字节存放在内存的高地址段;
判断方法:
union { short val; char c[sizeof(short)]; } test; test.val = 0x0102; if (test.c[0] == 0x01 && test.c[1] == 0x02) cout << "big endian" << endl; else cout << "little endian" << endl;
(3)程序编译的顺序是什么?
- 预处理:源代码经过预处理器,生成一个
.i
中间文件,这个阶段会把#include
的头文件内容进行替换,并处理宏定义;
- 编译:
.i
中间文件生成.s
汇编文件;
- 汇编:
.s
汇编文件经过汇编器生成.obj
目标文件;
- 链接:
.obj
目标文件经过链接器,与lib
静态链接库和dll
动态链接库生成可执行文件。
1. 预编译:主要处理源码文件中的以“#”开头的预编译指令。 处理规则: (1)删除所有的#define,展开所有的宏定义 (2)处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。 (3)处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。 (4)删除所有的注释,“//”和“/**/”。 (5)保留所有的#pragma 编译器指令,编译器需要用到它们,如#pragma once是为了防止有文件被重复引用。 (6)添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。 2. 编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。 (1)词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。 (2)语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。 (3)语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。 (4)优化:源代码级别的一个优化过程。 (5)目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。 (6)目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。 3. 汇编:将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows 下)、xxx.obj(Linux下)。 4. 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。 链接分为静态链接和动态链接:
(4)动态链接、静态链接?
1. 静态链接: 静态链接就是在编译链接时直接将需要执行的代码拷贝到调用处。 //函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。 优点:发布方便、更新速度快 * 在程序发布的时候就不需要依赖库,也就是不再需要带着库一起发布,程序可以独立执行 * 在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。 缺点:空间浪费、更新困难 * 空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本; * 更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。 2. 动态链接:把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。 共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本; 优点:更新方便。 更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。 缺点:性能损耗。 因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。 //静态库.a文件,动态库.so文件