【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇)

【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇)

 

  大家好,我是Lampard~~

  经过一番艰苦奋战之后,我终于是进入了心仪的公司。

  今天给大家分享一下我在之前精心准备的一套面试知识。

 

   今天和大家分享的是操作系统相关的面试题

    额其实这篇博客在项目开发的过程中,用到的情况并不多(也可能因为我还是新人,接触不多)。然鹅各种游戏大厂都会很注重面试者的基础,在博主所见识过的大厂面试中,它们主要是着重面试者以下几种技能:1.语言:c++,c#,lua,js,python等,2.引擎:内存管理,渲染机制,事件分发机制,计时器调度机制,设计模式等,3.基础:虽然客户端和服务端会有不同的侧重,但是只要你是科班出身,它们就要考察你的基础,比如说计算机网络,操作系统,数据库,计算机组成原理等,4.算法:前三者如果说大家可以靠过目不忘的本领把知识点都背下来,那么算法的测试就是见真章的时候了,做出来不是终点,它们会一直深挖一直深挖,让你一直优化(头皮发麻)。5.项目经验:若各位是校招的话,前面4点会占了80,90%的面试比重,但是若是社招的同学,项目经验就是最重要的,起码占50%的比重,面试官会考察你做过什么系统,看你的能力到哪里。对于客户端来说如果你有计算机图形学,接过战斗系统,接过sdk,负责过打包patch业务,有为项目写过脚本完善工具链,那么就会有加分点。所以多准备准备是没有错滴。最后祝大家疯狂收割offer,通过自己的努力摆脱生活的苟且,走向诗和远方~

    

 

一. 线程和进程

基本概念:

进程:进程是对运行时程序的一个封装,是系统进行资源调度和分配的基本单位(在操作系统层面的并发)。

线程:线程是进程的子任务,是cpu调度和分派的基本单位(在进程层面的并发)。

为什么要设计进程和线程呢?有一位解释的很好

一开始大家想要同一时间执行那么三五个程序,大家能一块跑一跑。特别是UI什么的,别一上计算量比较大的玩意就跟死机一样。于是就有了并发,从程序员的角度可以看成是多个独立的逻辑流。内部是单cpu时间分片,能快速的切换逻辑流,看起来像是大家一块跑的就行。

但是一块跑就有问题了。我计算到一半,刚把多次方程解到最后一步,你突然插进来,我的中间状态咋办,我用来储存的内存被你覆盖了咋办?所以跑在一个cpu里面的并发都需要处理上下文切换的问题进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。

后来一电脑上有了好几个cpu,好咧,大家都别闲着,一人跑一进程。就是所谓的并行

因为程序的使用涉及大量的计算机资源配置,把这活随意的交给用户程序,非常容易让整个系统分分钟被搞跪。所以核心的操作需要陷入内核(kernel),切换到操作系统,让老大帮你来做。

有的时候碰着I/O访问,阻塞了后面所有的计算。空着也是空着,老大就直接把CPU切换到其他进程,让人家先用着。当然除了I\O阻塞,还有时钟阻塞等等。一开始大家都这样弄,后来发现不成,太慢了。为啥呀,一切换进程得反复进入内核,置换掉一大堆状态。进程数一高,大部分系统资源就被进程切换给吃掉了。后来搞出线程的概念,大致意思就是,这个地方阻塞了,但我还有其他地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不用特别麻烦的切换页表、刷新TLB,只要把寄存器刷新一遍就行,能比切换进程开销少点。

简单来说就是,进程存在的意义是为了在cpu进行并发操作时,保护上下文,保护进程数据间不相互影响而设计出来的概念。有了进程我们就可以在单个cpu进行多进程分片,也可以多个cpu并发处理进程。而线程,就是在一个进程内其实也有多个逻辑,当一个逻辑需要io的时候,会白占cpu,占着茅坑不拉屎。而进程间切换的代价也比较大,为了提高效率,在就设计出可线程的概念,当进程中其中一个线程需要IO的时候,另一个线程来占用cpu以此提高效率。

进程和线程的区别,我们可以用以下的五点进行概括

1.一个线程只能属于一个进程,而一个进程可以有多个线程,线程依赖于进程而存在。

2.一个进程在执行的过程中有独立的内存空间,而线程之间共享进程的内存(线程有自己的寄存器组)。

3.进程是资源分配的最小单位,而线程是cpu调度的最小单位

4.进程之间切换开销大,而线程切换只需要保存一些寄存器数据

5.进程之间相互独立不会造成影响,而线程之间会造成死锁

 

二. 线程和进程之间的通讯方式

实际上只有进程间需要通信,同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。

而进程间通信无论是信号,管道pipe还是共享内存都是由操作系统保证的,是系统调用.

一、进程间的通信方式

  1. 管道( pipe ):
    管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (namedpipe) :
    有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 信号量(semophore ) :
    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列( messagequeue ) :
    消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号 (sinal ) :
    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  6. 共享内存(shared memory ) :
    共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
  7. 套接字(socket ) :
    套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。

二、线程间的通信方式

  1. 锁机制:包括互斥锁、条件变量、读写锁
    互斥锁提供了以排他方式防止数据结构被并发修改的方法。
    读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
    条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  3. 信号机制(Signal):类似进程间的信号处理
    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

 

三. 死锁

死锁是不同的进程在争夺资源而造成相互等待的现象。

要造成死锁,必须满足以下的四种条件。

1.互斥条件

    进程对所分配的资源不允许其他进程访问

2.请求和保持条件

    在等待资源的过程中不会释放资源

3.不可剥夺

    其他进程不可以强制剥夺该资源

4.环路等待

    必然存在一条进程资源的环形链

图为环形链:

解决死锁

上文提及,造成死锁必须满足四个条件。那么我们就可以破坏其中任意一个条件来解决死锁。

1.互斥条件

    当超时未释放时允许访问

2.请求和保持条件

    一次性分配资源不需要等待

3.不可剥夺

    超时判定死锁时可以强制剥夺

4.环路等待

    终止某一个进程,破坏该链

 

四. 并发和并行

并发:是指同一个cpu执行多个进程,进程之间交叉执行着指令

并行:指的是多cpu机器,分别单独的运行着不同的程序

 

五. 说一说进程状态的转换图

进程的状态分别有:创建,就绪,执行,终止,等待阻塞

正常的流程是:创建 -> 就绪(准备好)-> 执行 -> 终止

若在执行的过程中,执行量过大,不能够在操作系统分配的时间片内完成,那么就回到就绪态等待下一次执行

若在执行的过程中,需要等待资源,那么就会进入等待阻塞状态,等到资源齐备之后,就会被唤醒回到就绪态再次等待执行。

 

六. 虚拟内存和物理内存

为什么需要虚拟内存

操作系统有虚拟内存与物理内存的概念。在很久以前,还没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于CPU的地址线条数。

比如在32位平台下,寻址的范围是2^32也就是4G。并且这是固定的,如果没有虚拟内存,且每次开启一个进程都给4G的物理内存,就可能会出现很多问题:因为我的物理内存时有限的,当有多个进程要执行的时候,都要给4G内存,很显然你内存小一点,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完了以后,再将等待的进程装入内存。这种频繁的装入内存的操作是很没效率的

于是针对上面会出现的各种问题,虚拟内存就出来了。

这个虚拟内存你可以认为,每个进程都认为自己拥有4G的空间,这只是每个进程认为的,但是实际上,在虚拟内存对应的物理内存上,可能只对应的一点点的物理内存,实际用了多少内存,就会对应多少物理内存。进程得到的这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。

进程开始要访问一个地址,它可能会经历下面的过程

  1. 次我要访问地址空间上的某一个地址,都需要把地址翻译为实际物理内存地址
  2. 所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上
  3. 进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录
  4. 页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
  5. 当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常
  6. 缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就找一个页覆盖,至于具体覆盖的哪个页,就需要看操作系统的页面置换算法是怎么设计的了。

上文提及,若能够在页表中找到虚拟内存对应的物理内存,那么就会访问到该物理内存的内容。但是当在页表中查询发现对应的数据不在物理内存上(在磁盘中),就会发生缺页异常。

缺页中断的处理步骤:

1.保护cpu现场

2.分析原因

3.执行缺页中断恢复

4.恢复现场

执行缺页中断恢复同样分两种情况

首先是当前的物理内存还有空余,那么就会将硬盘里对应的页换入内存。若当前内存已经满了,那么此时可以有两种执行方案:1.替换最先进入的页,2.替换最少使用的页(常用)

 

好,今天的分享就到这里,祝各位功力渐长平步青云,谢谢大家~~

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lampard杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值