操作系统-iOS面经问题汇总

2,064 阅读11分钟

1.什么是操作系统?

  1. 操作系统(OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
  2. 操作系统本质上是运行在计算机的软件程序,其他应用程序都需要通过操作系统来调用系统的资源,如内存、CPU、GPU、磁盘;
  3. 操作系统的存在屏蔽了硬件和硬件层面的复杂性;
  4. 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存、硬件、文件系统、应用程序的管理。

2.讲一讲用户态和系统态

  • 根据进程访问资源的特点可以分为用户态的系统态
    1. 用户态:用户态运行的进程可以访问用户程序的数据;
    2. 系统态:几乎可以访问任意的计算机资源,不受限制
  • 我们运行的程序基本上都在用户态运行,如果需要调用系统级别的子功能(如文件管理,进程控制,内存管理等)则需要切换到系统态,进行系统调用。通过系统调用,向操作系统提出请求,由其代为完成。

3.讲一讲系统调用

  • 系统调用按照功能可以分为以下几类:
    1. 设备管理:设备的请求或释放,如视频电话启动摄像头;
    2. 文件管理:文件的修改、创建、删除等;
    3. 进程控制:进程的创建、撤销、阻塞、唤醒等;
    4. 进程通信:完成进程间的消息或信号传递;
    5. 内存管理:内存分配(malloc 函数)、回收(free)、地址转换(将逻辑地址转化为物理地址等)

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

  • 联系
    • 进程是资源分配的基本单位,线程是cpu调度的基本单位;一个进程中可以有多个线程,它们共享进程资源;
  • 区别
    • 拥有资源:

    进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。

    • 调度:

    线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换;从一个进程的线程切换到另一个进程的线程时,会引起进程的切换。

    • 系统开销

    进程的开销更大,线程的开销小。

    • 通信

    线程间可以通过直接读写同一进程中的数据进行通信,但进程间的通信需要借助IPC。

5.进程和线程的切换哪个带来的资源消耗大?为什么线程的消耗比较小?

由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前进程CPU的保存及新调度进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。

说法2:CPU上下文切换就是把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,来运行新任务。一次系统调用的过程,其实是发生了两次 CPU 上下文切换。(用户态代码-系统态代码-用户态代码)。系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源。进程上下文切换就比系统调用时多了一步:在保存内核态资源之前,需要把用户态资源(虚拟内存、栈等)保存下来;而由于同一进程的线程共享堆和方法区,因此同一进程的线程切换时,只需要切换线程的私有数据即可,而不同进程的线程切换过程就跟进程上下文切换是一样的。

6.线程共享哪些内存空间?

一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(私有的)栈(stack);同一进程内的线程共享本进程的资源如虚拟内存、描述符等,但是进程之间的资源是独立的。

7.进程和线程各自分配了哪些资源?

  • 进程 内存空间、I/O设备、cpu、信号处理函数、打开的文件描述符等;
  • 线程 有独立的调用栈/本地变量/寄存器上下文

8.讲一下各种锁

  • 互斥锁

    定义:互斥锁是一种用于多线程编程,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制,该目的是通过将代码切成一个个临界区而达成。
    • NSLock
    • pthread_mutex
    • @synchronized
  • 自旋锁

    定义:在自旋锁中,线程会反复检查变量是否可用。由于线程这个过程中一直保持执行,所以是一种忙等待。一旦获取了自旋锁,线程就会一直保持该锁,直到显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。对于iOS属性的修饰符atomic,自己带一把自旋锁。
    • OSSpinLock
    • atomic
  • 条件锁

    定义:条件锁就是条件变量,当进程的某些资源要求不满足时就进入休眠,即锁住了;当资源被分配到满足条件时条件锁打开,进程继续运行。
    • NSCondition
    • NSConditionLock
  • 递归锁

    定义:递归锁就是同一个线程可以加锁N次而不会引发死锁,递归锁是特殊的互斥锁,即是带有递归性质的互斥锁;
    • pthread_mutex(recursive)
    • NSRecursiveLock
    • @synchronized
  • 读写锁

    读写锁实际是一种特殊的自旋锁。将对共享资源的访问分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁能提高并发性。
  • 更高级的同步机制——信号量

    互斥锁可以说是semaphore取值仅在(0/1)之间的特例。信号量可以有更大的取值空间,通过限制同一资源的最多访问数来实现更加复杂的同步,而不单单是线程间的互斥。

9.死锁的各种问题

死锁的必要条件

  • 互斥

每个资源要么已经分配给了某个进程,要么就是可用的

  • 占有和等待

已经得到了某个资源的进程可以再请求新的资源

  • 不可抢占

已经分配给某个进程的资源不可被强制性抢占,它只能被占有它的进程显式的释放

  • 环路等待

有两个或两个以上的进程组成一条环路,该环路中每一个进程都在等待下一个进程占有的资源

处理方法

  • 鸵鸟策略(忽略死锁,不处理)
  • 死锁检测与死锁恢复(不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复)
    • 通过抢占恢复
    • 利用回滚恢复
    • 通过杀死进程恢复
  • 死锁预防(在程序运行之前预防发生死锁)
    • 破坏互斥条件
      • 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
    • 破坏占有和等待条件
      • 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
    • 破坏不可抢占条件
    • 破坏环路等待
      • 给资源统一编号,进程只能按编号顺序来请求资源。
  • 死锁避免(在程序运行时避免发生死锁)
    • 安全状态和银行家算法

10.谈一谈同步和异步

同步方式指的是添加任务后阻塞当前线程直到任务被加入队列并且执行结束; 异步方式指的是加入任务队列后,不必等待任务执行,函数立即返回

同步与异步只与线程有关,和队列无关,不要混淆;队列的串行和并行与同步、异步并无关联; 同步与异步是准备抛任务的线程 与 执行任务线程的关系;抛任务的线程是否等待这个任务执行完成,若等待就是同步,不等待就是发起了异步。

如何深入理解同步异步只与线程有关而和队列无关?

举个例子:主线程往串行队列异步抛100个任务,这100个任务是在串行队列里串行执行的,但主线程在抛完之后就不关心任务执行的怎么样了。串行是这100个任务的串行,和主线程没关系。

dispatch_queue_t queue = dispatch_queue_create("serial",null);
for(int i = 0; i < 100; i ++ ){
    dispatch_async(queue, ^{
        NSLog(@"current:%d", i);
    });
}

此处感谢@Fater7的精彩讲解。

11.两个线程同时访问i并使之++一万次,i的结果

答:不可预期,具体要看线程如何调度。

  • 多线程下执行的顺序不可预测

    • 编译器优化会重排代码
    • cpu会乱序执行指令
    • 不要对执行顺序妄下假设

12.手写一个线程安全的单例模式

  • 双重校验锁模式
@implementation Singleton

+ (instancetype)sharedInstance{
static Singleton * sharedInstance = nil;
      if(!sharedInstance){
      @synchronized(self){
        if(!sharedInstance){
          sharedInstance = [[Singleton alloc] init];
        }
      }
   }
    //NSLog(@"currentTime:%@", [NSDate date]);
    if(sharedInstance){
        NSLog(@"address:%@",[sharedInstance description]);
    }
  return sharedInstance;
}

@end
  • dispatch_once
@implementation Singleton
+(instancetype)sharedInstance{
    static Singleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[Singleton alloc] init];
    });
    if(sharedInstance){
        NSLog(@"address: %@", [sharedInstance description]);
    }
    return sharedInstance;
}
@end

13.并发和并行

4月2日更新,清华大学操作系统课中给出的更清晰的概念:并发是指一段时间内多个程序(进程)可以运行,并行是指一个时刻上多个程序(进程)可以运行。单CPU可以实现并发(依靠进程切换),但不可以实现并行。

并发和并行并不是互斥的概念。 与并发执行对应的是顺序执行; 与并行相对应的是串行; 并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机; 并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行;如果可以就说明是并行;而并发是多个线程被(一个)cpu轮流执行;

14.进程之间如何通信?

常见的通信方法有管道、信号、消息队列、信号量、共享内存、套接字;

  • 管道:本质上是一块缓冲区,父进程通过fork子进程来共享管道,进行通信。管道是半双工单向通信,因此在通信前就要确定数据流向:即关闭父子进程各自一端不用的读写。如果一方是读数据就关闭写的描述符。
  • 信号量:信号量是一个计数器,用于多进程共享数据的访问,通常用来实现进程间的同步,避免资源竞争
  • 共享内存:多个进程访问同一块内存空间,这样进程之间可以及时看到对方对内存空间中的数据更新,通常需要使用互斥锁、信号量等同步操作来完成
  • 套接字(socket):主要用于客户端和服务器进程之间的网络通信;套接字是支持tcp/ip网络通信的基本操作单元,可以看作是不同主机进程之间双向通信的端点,是通信双方的一种约定;

15.线程之间如何同步?

  • 互斥资源(互斥量):拥有互斥对象的线程才能访问公共资源,比如synchronized关键词和各种锁都是使用这个机制。
  • 信号量:它允许多个线程访问同一资源,控制资源的最大线程访问数;
    • dispatch_semaphore
  • 事件:Wait/Notify,通过通知操作来保持线程同步。
    • dispatch_group_notify
    • dispatch_group_wait

16.讲一讲进程切换调度的算法

  1. 先到先服务(FCFS):从就绪队列中选择最先进入的进程,为其分配资源;
  2. 短作业优先:选择估计运行时间最短的进程;
  3. 时间片轮转:为每个进程分配固定的运行时间段;
  4. 优先级调度:根据进程优先级进行分配,如果优先级相同使用FCFS进行调度;
  5. 多级反馈队列:既能使高优先级任务得到响应,又能让短作业迅速完成,Unix采用这种调度算法。

17. 讲一讲内存管理机制和内存管理方法

内存管理主要分为连续分配管理和离散分配管理。离散分配管理又分为页式管理、段式管理和段页式管理。

  1. 页式管理:将主存分为大小固定的相等的页,用页表来管理;
  2. 段式管理:根据用户需求,为每段定义逻辑信息,根据段来分配内存,用段表管理;
  3. 段页式管理:结合段式和页式的优点,先分为段,每段再分为页。 段式和页式的共同点:都是离散存储,为了提高内存利用率,减少碎片; 区别:分页式由系统完成,分段式由用户根据需求划分。一个指令可能会跨页但不会跨段,页式有内碎片而段式没有。

18.讲一讲快表和多级页表

  1. 快表提高了内存的时间性能,提高了虚拟地址到物理地址的转换速度,是一种高速的缓存,正常情况分页下需要访问两次主存,而使用快表命中的情况下只需要访问一次高速内存,一次主存即可。日常系统开发redis缓存就是借鉴了这一思想;
  2. 多级页表提高了内存的空间性能,通过使用多级页表映射的方式来替代把所有页表都放在内存中。

19.什么是虚拟内存?

比如某些软件运行时占用的内存要远远超出电脑本身的内存,这是通过虚拟内存来实现的。虚拟内存定义了一个连续的虚拟内存地址空间,把内存扩展到硬盘空间上(内存到外存)

20.说一说虚拟内存的局部性原理

局部性原理是虚拟内存技术的基础,它允许程序部分装入内存即可运行。

  1. 时间局部性:一条指令被执行或者一个数据被访问,那么不久之后这个指令或数据会再次被执行或访问;
  2. 空间局部性:一个存储单元被访问,则不久之后其附近的存储单元也将被访问,通过预存机制来实现高速缓存;

21.虚拟内存的实现技术有哪些?讲一讲常见的页面置换算法。

  • 虚拟内存的实现建立在内存离散分配管理方式的基础上,在其基础上增加了请求调页(段)和页面(分段)置换的功能。请求分页、请求分段、请求段页式存储管理,其原理是在开始前仅装入部分程序,当运行时发现访问的页/段不在时,再启动缺页中断,如果内存足够,则直接加入内存,不够则进行页面置换。
  • 页面置换实际上就是一种淘汰页面的机制
    1. 先进先出(FIFO):淘汰最先进入的页面
    2. 最近最久未使用(LRU):记录每个页面上次被访问到现在的时间,淘汰最长的。LRU算法可以通过哈希链表(LinkedHashMap)数据结构来实现。
    3. 最少使用(LFU):淘汰最少使用;
    4. 最佳(OPT):根据未来的使用情况,以最低缺页率进行淘汰,不可实现的算法。