操作系统学习(1) 操作系统概述

1. 异常和中断的区别

  • 中断:由CPU以外的事件引起的中断,如I/O中断、时钟中断、控制台中断等。
  • 异常:来自CPU的内部事件或程序执行中的事件引起的过程。如,由于CPU本身故障、程序故障和请求系统服务的指令引起的中断等。

2. 中断和系统调用
(1)中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。
(2)中断一般分为三类
a. 由计算机硬件异常或故障引起的中断,称为内部异常中断
b. 由程序中执行了引起中断的指令而造成的中断,称为软中断(这也是和我们将要说明的系统调用相关的中断);
c. 由外部设备请求引起的中断,称为外部中断
(3)中断处理程序:当中断发生的时候,系统需要去对中断进行处理,对这些中断的处理是由操作系统内核中的特定函数进行的,这些处理中断的特定的函数就是我们所说的中断处理程序了。
(4)中断的优先级
中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断可以被处理器接受并且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
(5)系统调用相关概念:

  • 进程的执行在系统上的两个级别:用户级和核心级(也称为用户态和系统态);
    用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。

  • 用户态切换到内核态的方式如下:
    (1)系统调用:程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件(这些均属于系统调用)等,就需要向操作系统发出调用服务的请求,这就是系统调用。
    (2)异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
    (3)外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

  • 用户态和核心态(内核态)之间的区别是什么呢?
    权限不一样。
    (1)用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。
    (2)核心态下的进程能够存取内核和用户地址某些机器指令是特权指令,在用户态下执行特权指令会引起错误。

  • 程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件等,就需要向操作系统发出调用服务的请求,这就是系统调用。

  • Linux系统有专门的函数库来提供这些请求操作系统服务的入口,这个函数库中包含了操作系统所提供的对外服务的接口。当进程发出系统调用之后,它所处的运行状态就会由用户态变成核心态。但这个时候,进程本身其实并没有做什么事情,这个时候是由内核在做相应的操作,去完成进程所提出的这些请求。

  • 那么用户态和核心态之间的区别是什么呢?
    用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。然而,核心态下的进程能够存取内核和用户地址

  • 系统调用和中断的关系,当进程发出系统调用申请的时候,会产生一个软件中断。产生这个软件中断以后,系统会去对这个软中断进行处理,这个时候进程就处于核心态了。

3. 中断和轮询的特点

  • 对I/O设备的程序轮询的方式,是早期的计算机系统对I/O设备的一种管理方式。它定时对各种设备轮流询问一遍有无处理要求。轮流询问之后,有要求的,则加以处理。在处理I/O设备的要求之后,处理机返回继续工作。尽管轮询需要时间,但轮询要比I/O设备的速度要快得多,所以一般不会发生不能及时处理的问题。当然,再快的处理机,能处理的输入输出设备的数量也是有一定限度的。而且,程序轮询毕竟占据了CPU相当一部分处理时间,因此,程序轮询是一种效率较低的方式,在现代计算机系统中已很少应用。
  • 程序中断通常简称中断,是指CPU在正常运行程序的过程中,由于预先安排或发生了各种随机的内部或外部事件,使CPU中断正在运行的程序,而转到为响应的服务程序去处理。
  • 轮询——效率低,等待时间很长,CPU利用率不高。
    中断——容易遗漏一些问题,CPU利用率高。

4. 你知道操作系统的内容分为几块吗?
操作系统的主要组成部分:进程和线程的管理,存储管理,设备管理,文件管理。

5. 一个程序从开始运行到结束的完整过程

  • 预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。
  • 编译:将预处理后的文件转换成汇编语言,生成.s文件
  • 汇编:汇编变为目标代码(机器代码)生成.o的文件
  • 链接:连接目标代码,生成可执行程序

6. 动态链接库与静态链接库的区别

  • 静态库
    静态库是一个外部函数与变量的集合体。静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器与链接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态创建(static build)
    在这里插入图片描述
  • 动态库
    (1)动态链接可以在首次载入的时候执行(load-time linking),这是 Linux 的标准做法,会由动态链接器ld-linux.so 完成,比方标准 C 库(libc.so) 通常就是动态链接的,这样所有的程序可以共享同一个库,而不用分别进行封装。
    (2)动态链接也可以在程序开始执行的时候完成(run-time linking),在 Linux 中使用 dlopen()接口来完成(会使用函数指针),通常用于分布式软件,高性能服务器上。而且共享库也可以在多个进程间共享。
    (3)链接使得我们可以用多个对象文件构造我们的程序。可以在程序的不同阶段进行(编译、载入、运行期间均可),理解链接可以帮助我们避免遇到奇怪的错误。
    在这里插入图片描述
  • 区别:
    (1)使用静态库的时候,静态链接库要参与编译,在生成执行文件之前的链接过程中,要将静态链接库的全部指令直接链接入可执行文件中。而动态库提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个.dll文件中,该dll包含一个或多个已被编译,链接并与使用它们的进程分开储存的函数。
    (2)静态库中不能再包含其他动态库或静态库,而在动态库中还可以再包含其他动态或者静态库。
    (3)静态库在编译的时候,就将库函数装在到程序中去了,而动态库函数必须在运行的时候才被装载,所以使用静态库速度快一些。

7. 系统调用与函数调用的区别?
(1)一个在用户地址空间执行;一个在内核空间执行
(2)一个是过程调用,开销小;一个需要切换用户空间和内核上下文,开销大
(3)一般相同;不同系统不同

  • 系统调用(System call)是程序向系统内核请求服务的方式。可以包括硬件相关的服务(例如,访问硬盘等),或者创建新进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。
  • 库函数:把一些常用的函数编写完放到一个文件里,编写应用程序时调用,这是由第三方提供的,发生在用户地址空间。
  • 在移植性方面,不同操作系统的系统调用一般是不同的,移植性差;
  • 在调用开销方面,系统调用需要在用户空间和内核环境间切换,开销较大;而库函数调用属于“过程调用”,开销较小。

8. 操作系统拿来干什么的?

  • 操作系统的功能:
    (1).进程和线程的管理 ——进程线程的状态、控制、同步互斥、通信调度等
    (2).存储管理——分配/回收、地址转换、存储保护等
    (3).文件管理——文件目录、文件操作、磁盘空间、文件存取控制
    (4).设备管理——设备驱动、分配回收、缓冲技术等
    (5).用户接口——系统命令、编程接口
  • 操作系统的三个作用:
    (1).资源的管理者
    (2).向用户提供各种服务
    (3).对硬件机器的扩展

9. 操作系统的用户态与内核态?

  • 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
  • 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

10. 为什么要有用户态和内核态
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 – 用户态和内核态。

11. 什么是RAII资源管理?
它是“Resource Acquisition Is Initialization”的首字母缩写,即资源获取就是初始化。利用对象生命周期来控制程序资源,简单来说就是通过局部对象来处理一些资源问题

12. 为什么要字节对齐?
(1)有些特殊的CPU只能处理4倍开始的内存地址
(2)如果不是整倍数读取会导致读取多次
(3)数据总线为读取数据提供了基础

13. 什么是内存对齐以及为什么要内存对齐么?
(一) 内存对齐
内存对齐:编译器将程序中的每个“数据单元”安排在适当的位置上。
简单理解:按照某种规则将我们定义的结构体成员放在合适的地址偏移位置上存储。
举一个例子:

//32位系统
#include<iostream>
using namespace std;

struct{
    int x;
    char y;
}s;

int main()
{
    cout << sizeof(s) << endl;  
    return 0;
}

问题:上述代码的输出是多少?
答案:8

为什么要额外的3字节去填充这个结构体?一个原本5字节的结构现在变成8 字节,几乎扩大了 2 倍的存储空间,这样的空间开销是否值得?又是什么样的原因导致这样的设计?

(二) 内存对齐的原因

  1. 内存以字节为单位:
    内存是以字节为单位存储,但是处理器并不会按照一个字节为单位去存取内存。处理器存取内存是块为单位,块的大小可以是2,4,8,16字节大小,这样的存取单位称为内存存取粒度。如果在64位的机器上,不论CPU是要读取第0个字节还是要读取第1个字节,在硬件上传输的信号都是一样的。因为它都会把地址0到地址7,这8个字节全部读到CPU,只是当我们是需要读取第0个字节时,丢掉后面7个字节,当我们是需要读取第1个字节,丢掉第1个和后面6个字节。所以对于计算机硬件来说,内存只能通过特定的对齐地址进行访问。
  2. 内存存取效率:
    从内存存取效率方面考虑,内存对齐的情况下可以提升CPU存取内存的效率。比如有一个整型变量(4 字节),现在有一块内存单元: 地址从 0~7。这个整型变量从 地址为 1 的位置开始占据了 1,2,3,4 这 4 个字节。 现在处理器需要读取这个整型变量。假设处理器是 4 字节 4 字节的读取,所以从 0 开始读读取 0,1,2,3发现并没有读完整这个变量,那么需要再读一次,读取 4,5,6,7。然后对两次读取的结果进行处理,提取出 1,2,3,4 地址的内容。需要两次访问内存,同时通过一些逻辑计算才能得到最终的结果。如果进行内存对齐,将这个整型变量放在从0开始的地址存放,那么CPU只需要一次内存读取,并且没有额外的逻辑计算。可见内存对齐之后存取的效率提升了1倍。
    (三)总结
    通过填充字段padding使得结构体大小与机器字倍数对齐是一种常见的做法。显然内存对齐是会浪费一些空间的。但是这种空间上得浪费却可以减少存取的时间。这是典型的一种以空间换时间的做法。在内存越来越便宜的今天,这一点点的空间上的浪费就不算什么了。因为访问内存的速度对于处理来说是非常非常的慢, 内存访问速率对于现在 CPU 来说越来越跟不上, 额外的内存访问无疑是浪费 CPU的。

14. C++当中一个空的结构体或者类的对象的大小是多少?
空的类或者结构体的大小是1个字节,因为C++当中每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址。

15. 结构体成员的声明顺序会影响结构体的大小么?比如下面两个结构体A,B他们大小是多少?

struct A  // sizeof (A) == 12
{
 char b;
 int a;
 char c;
};
struct B  // sizeof (B) == 8
{
 char b;
 char c;
 int a;
};

答案:成员声明顺序会影响结构体大小。

对齐规则是按照成员的声明顺序,依次安排内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍,最后结构体的大小为最大成员的整数倍

16. 内存对齐的作用是什么?
提升性能:减少CPU读取内存的次数,提升程序执行的效率

17 .内存对齐的原则是什么?
答:三原则:

  • 结构体变量的起始地址能够被其最宽的成员大小整除;
  • 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充空白字节;
  • 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充空白字节;

分析:编译器在编译的时候是可以指定对齐大小的,实际使用的有效对齐其实是取指定大小和自身大小的最小值,一般默认的对齐大小是4。可以通过预编译命令#pragma pack(n)。除了上述3原则之外还有其他的对齐规则:计算机体系结构当中缓存是很重要的一环,CPU不是直接读取内存而是读取缓存:高速缓冲存储器。其作用是为了更好的利用局部性原理,减少CPU访问主存的次数。因为存取内存相对存取缓存是慢很多的,cache也可以看做是一种空间换时间的做法。实际读取内存的是缓存。所以内存对齐有的时候还需要考虑缓存更新的读取策略,一些规则如下:
1.对较大结构体进行cache line对齐:Cache与内存交换的最小单位为cache line。一个cache line大小以64字节为例。当我们的结构体大小没有与64字节对齐时,一个结构体可能就要占用比原本需要更多的cache line,同时还会带来错误共享问题,大家可以自行google。

2.对只读字段和读写字段分离对齐: 只读字段和读写字段分离对齐的目的是为了让只读字段和读写字段分别存储在缓存的不同cache line中,使得读写字段的淘汰尽量少的影响只读字段,因为只读字段不会被改变所以应该尽量少的被缓存换出。

18.什么是指令乱序?
从编译器的角度其实是对我们写的代码的一种优化,按照机器的角度讲一些指令代码执行顺序进行改变,优化程序实际执行的效率。

分析:之所以出现编译器乱序优化是因为编译器能在很大范围内进行代码分析,从而做出更优的执行策略,可以充分利用处理器的乱序执行功能。
指令乱序的问题:编译器优化产生的指令乱序可能会导致多线程程序产生意外的结果。

19. 如何解决指令乱序问题?
答:内存屏障。

分析:内存屏障,是一类同步指令,是对内存随机访问的操作中的一个同步点。此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。因为指令乱序执行的存在,就需要内存屏障保证程序执行的可靠。

20. 为什么要内存对齐?

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

21. Linux开机流程

  • 加载BIOS的硬件信息与进行自我测试,并依据设置取得第一个可启动设备;
  • 读取并执行第一个启动设备内MBR(主引导分区)的Boot Loader(即是gurb等程序);
  • 依据Boot Loader的设置加载Kernel,Kernel会开始检测硬件与加载驱动程序;
  • 在硬件驱动成功后,Kernel会主动调用init进程(/sbin/init),而init会取得runlevel信息;
  • init执行/etc/rc.d/rc.sysinit文件来准备软件的操作环境(如网络、时区等);
  • init执行runlevel的各个服务的启动(script方式);
  • init执行/etc/rc.d/rc.local文件;
  • init执行终端机模拟程序mingetty来启动login程序,最后等待用户登录。

22. linux程序启动过程
当你在 shell 中敲入一个命令要执行时,内核会帮我们创建一个新的进程,它在往这个新进程的进程空间里面加载进可执行程序的代码段和数据段后,也会加载进动态连接器(在Linux里面通常就是 /lib/ld-linux.so 符号链接所指向的那个程序,它本身就是一个动态库)的代码段和数据。在这之后,内核将控制传递给动态链接库里面的代码。动态连接器接下来负责加载该命令应用程序所需要使用的各种动态库。加载完毕,动态连接器才将控制传递给应用程序的main函数。如此,你的应用程序才得以运行。(过程链接表(PLT), Global Offset Table(GOT))

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值