0%

Operating System Interview I

横眉冷对千夫指,俯首甘为孺子牛。——鲁迅

操作系统面试相关知识汇总:

3JaemQ.png

操作系统知识对于服务问题的排查和定位非常重要。

进程和线程

进程

进程的结构如下图所示:

3t5L5R.png

  • 每个进程都包含线程
  • 进程里面也包含内存。
  • “句柄“在英文中叫做“handle”,文件和网络资源与内存不同,它是所有进程所共有的,也就是说我这个进程和那个进程都是可以打开同一个文件的,或者都是可以同时去抢同一个网络的端口。

是不是把电脑的内存分配一部分给它呢?并不是。
这里的内存是逻辑内存。电脑的32位和64位到底是什么意思呢?就是在内存上面,是指它们内存的寻址空间。32位代表2的32次方,也就是4G大小,也就是每个进程都有一个4G内存的一个空间。这只是说它有4G内存的空间可以用,不是说我就把4G的内存分配给你了。

进程之间的内存的独立是合理且必须的,举个例子,如果我的进程的指针可以改一改,指向你正在输入银行卡密码的进程,那么我就可以得到你微信或者网上银行的内存了,那就很不安全,甚至恐怖了。

线程

线程的结构如下图所示:

3tIprD.png

我制作的这张图可以说比较形象地说明了线程的内部结构。

首先,线程里面包含栈。我们习惯说“调用堆栈”,实际上这里“堆“没有意义,这里的调用“堆栈”其实就是调用栈。

  • 栈里面有什么?——主线程中的main函数会进行各种调用,每次调用,会把所有的参数和返回的地址压到栈里面,一层一层地放入栈。包括每个函数地局部变量也都会放在栈里面。

  • 线程还有很重要的一个东西“PC”——program counter的意思,里面存放的是下一个要执行的指令的地址。所以总的来说,我们的操作系统运行的是一个一个的线程,进程只是一个容器,线程才是真正运行的东西

  • TLS——thread local storage——每个进程都有自己的一块内存,那么线程有没有呢?也是有的,就是这个TLS。我们可以在TLS中分配内存,存放变量,这些数据就是我们的线程所独有的数据

总得来说,线程才是我们操作的系统真正运行的,进程只是一个容器,他把一部分东西放在一起,旁边放了一个很强的隔离,把不同的程序隔离开来。

进程和线程的区别和联系?

从宏观上来回答:

a. 进程是系统资源分配的最小单位,线程是程序执行的最小单位

b.进程使用独立的数据空间,而线程共享进程的数据空间

但是这只是很宏观的角度,我们需要从更细的角度去钻研一下,进程(process)和线程(thread)的区别。而且面试官可能会从你的回答来继续后面的问题。

比如面试者回答说”它们的内存结构不同,进程之间不能共享内存,线程之间可以共享内存”,那面试官可以问与内存有关的内容,比如我们如何寻址

再比如面试者回答“线程之间通信很方便,但是进程之间通信比较复杂”,那么面试官会问进程间通信有哪些方法,比较一下优劣点,等等。

  • 共享内存,因为进程间不能共享内存,所以我们会用一些进程间相互交互的方案,比较常见的就是通过TCP/IP的端口来实现。也有其他方案,但是TCP/IP是最通用的,其他方案可能和某个特定操作系统的相关性要更大一些。

  • 线程间通信就很简单了,只要两个线程的指针指向同一块内存,它们之间就可以通信。

  • In terms of 开销,进程的开销当然比较大,因为我们要给它分配很多内存,而线程我们只是给它分配一个栈,分配一个PC指针(program counter)就可以了。此外,进程之间切换的开销会大于线程之间切换的开销。

进程间通信(IPC)

之前已经介绍了进程和线程之间的区别,进程就像一个container,里面有很多的线程。而除了有很多线程,更重要的一点,它有一块自己的内存。这块内存是这个进程里面所有线程之间共享的,所以线程之间共享数据很方便——我开一块内存,告诉你地址,你也能用,当然如果同时读写的话会有线程安全性问题,需要加一些同步的机制来防止线程安全性的问题。

但是进程和进程之间都有一块自己独立的内存,这块内存的大小和物理内存大小无关,和系统是32位还是64位有关。进程之间是不能互相看到信息的。

这里列举进程之间通信的7种方法:

  1. 文件
  2. 管道/命名管道
  3. Signal
  4. 共享内存
  5. 消息队列
  6. 同步机制,如信号量(semaphore)
  7. Socket

1.写一个文件:最简单的方法,一个进程写一个文件,另一个进程去访问这个文件,由此两个进程之间可以交换信息,得以通信。

2.管道:两个进程之间建立消息通信的通道。不命名的管道一般是单向的,一方可以向另一方从管道里发送数据;命名管道可以单向也可以双向。

比如:
-打印一个日志文件:cat XXX

-查找带有某个字符串内容:grep -e “ERROR” –color

此外,如果还用统计,可以用wc(word count)命令

3.Signal:linux系统中最常用。一个进程给另一个进程发送信号,一般是一串数字,这个数字有自己特定的含义。比如”kill”可以 send a signal to a process,强行”kill”掉。

4.共享内存:虽然进程之间是彼此独立的,但是操作系统可以提供一个机制,两个进程可以约定好,打开一个文件,这个文件映射到内存中,也就是多个进程使用同一块内存。

共享内存方式适用于进程间数据共享的场景

5.消息队列:一个进程可以向另一个进程发消息。

进程间数据交换的场景可以使用Unix Socket或者消息队列

6.同步机制,信号量。

很经典的知识点。如果进程或者线程进入之后,可能不是在进行写操作,而是进行读之类的操作,此时没必要只限制一个进程或者线程去执行。

信号量用一个整型(sem)表示,它是一个有符号数。针对信号量有两个原子操作:

  • P()(Perlaag(荷兰语,减少))
    • sem减1
    • 若sem < 0,进入等待,否则继续
  • V()(Verhoog(荷兰语,增加))
    • sem加1
    • 若sem<=0,唤醒一个等待进程

可以用火车站台举例子,如果只有两个车站和站台,只有2个资源的信号量,那么一开始sem是2。来了一辆车,sem会减1,若sem减小到了0,那么再来的资源就只能等待了。

信号量有两种:二进制信号量(0或1)、一般/计数信号量(可以取任何非负值)

P()操作可能会让进程阻塞挂起、V()操作可能可以唤醒进程。

在临界区之前用P()操作,在临界区之后用V()操作加回去。

使用二值信号量的一个例子:如果要设置两个Thread的同步关系,那么可以把condition初始化值设置为0:condition = new Semaphore(0)
让Thread在执行条件(比如condition())之前执行减法P()操作,这样condition就不得不暂停,直到condition里面的信号量回到0才能继续执行。此时可以在ThreadB中执行V()操作,此时才可以把ThreadA唤醒,使得Thread继续执行。

也可以用信号量解决有缓冲区的生产者-消费者问题。此时需要用到计数信号量。

3U1SYV.png

图中的情况和Lock完全不同。生产者可能生产很多内容都往buffer里面”挤”,但是作为消费者的线程,可能不能一口气消耗掉所有buffer里面的数据。

有关生产者消费者模型的问题,有以下要求(约束):

  • 在任何一个时间只能有一个线程操作缓冲区(互斥)
  • 当缓冲区为空,消费者必须等待生产者(调度/同步约束)
  • 当缓存区满,生产者必须等待消费者(调度/同步约束)

解决这三个约束的办法,就是定义三个信号量,每个约束单独用一个信号量控制:

  • 二进制信号量(处理互斥)
  • 一般信号量fullBuffers(一开始为0,取不到数据,标记消费者是否可以去取数据了)
  • 一般信号量emptyBuffers(一开始为n,可以用P()函数去取)

具体定义操作:

3U6nhT.png

上面这两个函数中,即用到了互斥机制,也用到了同步机制,来解决生产者和消费者的问题。

fullBuffers一开始是0。emptyBuffers里面一开始设置可以放入n。

生产者函数:Deposit、消费者函数:Remove()

需要注意初始值的赋值,需要知道一开始Buffer是不是空的。

一个问题:上述生产者消费者模型中,P、V操作的顺序有影响吗?——答:因为V操作不会阻塞,所以交换V操作没有问题。

但是生产者的P操作不能交换,因为P操作是阻塞的,可能产生死锁。比如生产者很快,Buffer满了,生产者先执行mutex的P操作,可以执行。但是之后执行emptyBuffer()的时候,因为此时Buffer满了,emptyBuffers已经是0了,所以再执行P操作之后,Deposit会进入到睡眠状态。之后消费者如果P顺序不变,第一个fullBuufers操作没问题,但是第二个mutex会卡住,导致最后不能执行到emptyBuffers操作的V操作,无法唤醒生产者的操作,产生死锁。

所以,对信号量的操作的顺序需要注意。

7.Socket:最后一种,而且没回答出来这个其实整个答案都不算好。因为前面6种只能作为同一个机器的进程之间的通信,而Socket可以是不同机器的进程之间的通信

可以在机器上开一个端口,作为一个服务器,让用户连接。这种通信包含了网络上的服务端和服务器的这种结构。

用浏览器去访问一个网站,这时浏览器的进程远端服务器的进程要进行通信

Socket可以作为不同机器之间进程的通信——通过客户端,服务器的方法。这里面走的一般是TCP的协议或者UDP的协议。

线程调度

介绍两个调度算法:时间片轮转调度算法动态优先级调度算法

时间片轮转调度是一种最古老,最简单而且最公平的算法,每个进程被分配一段时间,称作是时间片,轮流分享CPU的计算资源。这个时间片的设置比较讲究。比如linux早期是百分之一秒,现在设置为千分之一秒。

和之前做过的堆机场吞吐量优化的项目类似,动态优先级调度算法就是给每个进程都设置一个权值,而且这个权值会随自变量而改变,而系统会根据每个进程的优先级来判断之后优先调度哪一个进程。

存储和寻址

存储整体结构如下图:

3UostH.png

最底层是硬盘,可以长久存放程序,而且一般都比较大。

然后是内存,它里面存放的数据断电就会没有,但是它可以更加快速地随机地进行访问。

再往上就进入了CPU。CPU除了运算模块,还有缓存。缓存也有各种级别,有些缓存是共有的,有些是自己独有的。

再往上就是离CPU运算单元更近的,寄存器。

这些存储单元的存储速度有什么特点?从上到下由快到慢。寄存器是最快的因为他就在运算模块的旁边。硬盘是最慢的。价格也是由贵到便宜。

谷歌的搜索会把整个互联网放在它们的内存中,尤其是对访问量高的网页。访问量低的网页才会被放在硬盘里。这样基于内存的搜索引擎肯定是又快又好的。

寻址过程

那么我们操作系统是如何寻址的?——也就是说给定一个地址之后,操作系统是怎么在这个层次性的结构中找到需要的数据呢?

需要结合寻址空间:

  • 寻址空间:每一个进程里面的指针可以取到的地址的范围。这个寻址空间和我们机器上装了多少存储空间,某时刻有多少个进程,都没有关系。每个进程都有自己独立的寻址空间。

那这个寻址空间有多大呢?一般会考虑到两种大小的寻址空间:32位和64位。

32位:2的32次方位字节(Byte,注意不是Bit,也就说不是位或者比特)。1G=2^10 M =2^10 * 2^10 K = 2^10 * 2^10 * 2^10 B,所以32位为 2^32/2^10/2^10/2^10 = 2^2 = 4G

64位:可以有10^19次方Byte的寻址空间,这个空间非常大,一时半会不需要考虑不够用……

举一个寻址的例子:int n = *p;,这段代码翻译成汇编,其实就是:MOV EAX, [EBX]

具体流程如下图所示:

3ay5IP.png

这段代码,int n = p,p代表一个地址,p表示把指向int类型的数据的指针p指向的内容读取出来,赋值给n

图中虚线表示,逻辑内存对应到物理内存是不一定对应得出来的,有可能它在物理内存里面(直接取出来就可以),也可能不在物理内存里面,在虚拟内存里面。操作系统会在硬盘上开辟一个空间作为虚拟内存,虚拟内存不会把具体多少字节放过去,而是把p的分页放进去(通常几k或者几兆这样,根据操作系统配置而定)。

通常硬盘是比较大的,只要放得下,可以不断把内存中的东西放到硬盘上去。

如果想要放入物理内存但是发现物理内存不够,那么就只能用算法在物理内存里面找到一块很久没有用过的地方,用新的分页部分把它替换出来,这个过程称作是“分页”或“交换”,这个时候p就进入了物理内存里面。

如果在win的C盘发现有一块空间(几百兆或者几G)是不能访问的,那么这是win分配的虚拟内存的块空间。
在Linux中用户也是没有权限看到这块空间的,然后物理内存里面有了p的数据,我们可以把它取出来,放到寄存器里面。

一个运行良好的系统,绝大部分数据都是在物理内存里面的,这样能够很快。但是物理内存使用过多会造成过度分页,由于硬盘很慢,这样的话系统就会变得很慢。

中断

  • 中断的概念:中断指处理机处理程序运行中出现的紧急事件的整个过程.程序运行过程中,系统外部、系统内部或者现行程序本身若出现紧急事件,处理机立即中止现行程序的运行,自动转入相应的处理程序(中断服务程序),待处理完后,再返回原来的程序运行,这整个过程称为程序中断。

  • 中断的流程:改变工作模式至中断模式 -> 保存现场 -> 分析中断原因,跳到中断起始地址处理中断 -> 返回到原来模式 -> 恢复现场继续执行原来的程序。

操作系统问题集合

进程和线程的区别和联系?

(1)粒度性分析:线程的粒度小于进程。

(2)调度性分析:进程是资源拥有的基本单位,线程是独立调度与独立运行的基本单位,出了寄存器,程序计数器等必要的资源外基本不拥有其他资源。

(3)系统开销分析:由于线程基本不拥有系统资源,所以在进行切换时,线程切换的开销远远小于进程。

两者联系:进程和线程都是操作系统所运行的程序运行的基本单元。

简单说说寻址的过程?

-可以结合上述内容。

32位操作系统和64位操作系统区别是什么?

-32位和64位是它们的寻址空间大小的区别,这和系统物理内存大小无关。

说出你知道的进程间通信机制?

-参考上述七种方法,最重要的是socket,最容易忽略的是“读写文件”(读文件和写文件)。

说一下中断的概念和流程?

参考上面内容。