操作系统

1. 进程和线程有什么区别

  1. 根本区别
    1. 进程是操作系统进行资源分配的基本单位,用于执行计算机程序
    2. 线程是系统进行调度的基本单位,是进程的执行单元
  2. 切换开销
    1. 进程有独立的数据空间,切换开销大
    2. 线程共享进程的数据空间,切换开销小

2. 多线程和多进程的区别:优缺点

  1. 多线程:同一进程的线程共享进程数据
    1. 优点:内存占用小,cpu利用率高,切换开销小,创建销毁开销小
    2. 缺点:一个线程挂掉会导致整个进程挂掉,因为线程没有独立的内存地址,同一进程的线程可以相互访问对方的栈空间造成数据读写错误,为了保护同一进程的其他线程,只能将整个进程关掉
  2. 多进程:进程间数据隔离
    1. 优点:进程间数据隔离,一个进程挂掉不会影响其他进程
    2. 缺点:内存占用大,cpu利用率低,切换开销大,创建销毁开销大

3. 进程间通信方式:管道IO、消息队列、共享内存、socket

  1. 管道IO:半双工通信,有固定的读写端,读写阻塞,不能一边读一边写。有匿名IO和有名IO,匿名IO只能用于有关进程,比如父子进程。有名IO可以用于无关进程
  2. 消息队列:面向记录,有独特的格式和优先级,进程从消息队列中读取消息后,消息被删除,通信效率比较高,只需要将消息存入队列后就可以返回,写入消息时,将用户态的数据拷贝到内核态,读取消息时,将内核态数据拷贝到用户态
  3. 共享内存:最快的一种通信方式,解决了消息队列中数据拷贝的开销问题,通过虚拟内存映射出的物理内存来进行读写,进程直接从内存中读取,不需要进行数据的拷贝,多个进程可以同时读取,需要信号量进行同步
  4. socket:用于不同服务器之间通信,比如跨网络的客户端和服务器

4. 进程调度算法:先来先去、短作业、高响应、时间片轮转、高优先级

  1. 先来先去服务:选择等待时间最久的进程
    1. 非抢占式
    2. 优点:公平
    3. 缺点:在长进程后的短进程等待时间长
  2. 短作业优先:选择服务时间最短的进程
    1. 非抢占式
    2. 优点:系统响应快
    3. 缺点:不公平,长作业长时间无法获得调度
  3. 高响应优先:选择响应速度最快的进程
    1. 非抢占
    2. 优点:解决了长作业长时间无法获得调度的问题
  4. 时间片轮转:轮流让队列的进程获得时间片,时间片用完或者被剥夺就重新回到队列等待下一次调度
    1. 如果时间片未使用完就发生切换,是抢占式
    2. 优点:公平,实现简单
    3. 缺点:频繁切换开销大
  5. 高优先级:选择优先级最高的进程
    1. 抢占
    2. 优点:系统运行速度快
    3. 缺点:低优先级的进程长时间无法获得调度

5. 进程的三种状态及其切换过程:就绪、运行、阻塞

  1. 就绪ready:获得资源,等待CPU调度
  2. 运行running:获得CPU调度,获得时间片
  3. 阻塞block:时间片被剥夺

6. 死锁产生的原因和四条件:互斥、不可剥夺、请求和保持、循环等待

  1. 系统资源不足,无法分配资源

  2. 请求和释放资源的顺序不当,导致无法获取资源

  3. 互斥条件:进程互相持有对方所需的资源

  4. 不可剥夺:资源未使用完不释放

  5. 请求和保持:请求资源时不会释放持有的资源

  6. 循环等待:无法获取资源时,进入循环等待

  7. 四条件是必要条件,也就是说发生死锁,必定满足这四个条件,满足这四个条件,不一定发生死锁

7. 死锁预防:破环不可剥夺、破环请求和保持、破环循环等待

  1. 破环不可剥夺:先释放资源才能请求
  2. 破环请求和保持:一次性分配所有资源或者有一个资源就不请求
  3. 破环循环等待:对资源进行编号,请求时必须按照编号升序请求

8. 虚拟内存换页算法:FIFO、LFU、LRU

  1. FIFO:最先被加载到内存的最先被置换出去

  2. LRU:最少被访问的被置换

  3. LFU:最近一段时间内最少被访问的被置换

  4. OPT:最佳置换算法,不会再被访问的页面被置换,但是这只是一个理论的算法,因为没办法预知这个页面是否真的不会再被访问,目前无法实现

  5. 手撕LRU

    class LRUCache {
    
     /*
     分析:访问时,先弹出key,再存入。添加时,如果有,先弹出,再存入,map更新。如果容量为0,先弹出队首,再存入。
     */
    
     Map<Integer,Integer> map;
     Queue<Integer> queue;
     int capacity;
    
     public LRUCache(int capacity) {
         map = new HashMap<>();
         queue = new LinkedList<>();
         this.capacity = capacity;
     }
    
     public int get(int key) {
         if(queue.contains(key)){
             queue.remove(key);
             queue.add(key);
             return map.get(key);
         }else{
             return -1;
         }
     }
    
     public void put(int key, int value) {
         if(queue.contains(key)){
             queue.remove(key);
             queue.add(key);
             map.put(key,value);
         }else if(capacity == 0){
             map.remove(queue.poll());
             queue.add(key);
             map.put(key,value);
         }else{
             queue.add(key);
             map.put(key,value);
             capacity--;
         }
     }
    }

9. 高CPU占用排查

  1. top查看进程
  2. top -H -P [PID]查看进程中线程的cpu使用情况
  3. printf "%x\n" [线程ID]转换成十六进制
  4. jstack [PID] | grep [线程ID]查看线程

10. 孤儿进程和僵尸进程

  1. 孤儿进程:父进程退出后,子进程还在运行,子进程变成孤儿进程,被init进程(pid=1)收养,完成后续的资源释放
  2. 僵尸进程:子进程退出后,父进程没有调用wait或者waitpid释放子进程的资源,子进程pid依旧被占用,因为系统的pid是有限的,一个32767个,会限制进程数量
  3. 解决僵尸进程:将父进程kill掉,僵尸进程变成孤儿进程,被init进程收养完成资源释放

11. Linux内核IO模型:阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO

  1. 阻塞io:对应Java中的BIO。用户线程向内核发起IO请求,内核查看数据是否就绪,如果数据未就绪,阻塞线程,用户线程交出CPU时间片。当数据就绪后,内核将数据拷贝到用户线程,用户线程解除阻塞状态并处理数据

  2. 非阻塞io:用户线程向内核发起IO请求,内核查看数据是否就绪,如果数据未就绪,用户线程可以先去处理其他操作,同时用户线程不断询问内核,当数据就绪后,内核将数据拷贝到用户线程,用户线程处理数据

  3. 多路复用io:对应Java中的NIO。可以调用select、poll、epoll函数进行处理,用户线程调用函数向内核发起IO请求,用户线程可以先去执行其他操作,当数据就绪后,内核将数据拷贝到用户线程,函数返回大于0,通知用户线程可以处理数据

  4. 信号驱动io:用户线程注册一个信号函数,函数向内核发起IO请求,用户线程可以去执行其他操作,内核查看数据是否就绪,当数据就绪后,内核将数据拷贝到用户线程,信号函数返回一个信号,通知用户线程可以处理数据

  5. 异步io:对应Java中的AIO,用户线程启动一个read函数,然后就可以去处理其他操作,内核查看数据是否就绪,当数据就绪后,内核将数据拷贝到用户线程,用户线程只需要注册函数,整个操作由read函数完成

  6. select

    1. 操作方式:将用户态的fd_set拷贝到内核态,内核态遍历fd_set,时间复杂度On
    2. 数据结构:数组,1024和2048
    3. 成功返回大于0,失败返回小于0,超时返回0
    4. 优:兼容性好,支持多种系统
    5. 缺:效率低,需要遍历fd_set,有最大连接上限
  7. poll

    1. 操作方式:将用户态的fd_set拷贝到内核态,内核态遍历fd_set,时间复杂度On
    2. 数据结构:链表
    3. 成功返回大于0,失败返回小于0,超时返回0
    4. 优:没有最大连接上限
    5. 缺:效率低
  8. epoll

    1. 操作方式:事件触发,当fd就绪后,通知进程进行处理,只关心活跃的连接,时间复杂度O1
    2. 数据结构:红黑树
    3. 两种模式
      1. LT水平:当epoll_wait检测到fd触发后,可以等待下一次再次触发再处理
      2. ET边缘:当epoll_wait检测到fd触发后,必须马上处理
    4. 优:没有最大连接上限,事件触发,效率高,只关心活跃的连接
    5. 缺:兼容性差,只能在Linux上使用

12. Java IO模型:同步和异步、BIO、NIO、AIO

  1. 同步异步、阻塞非阻塞
    1. 同步:必须等待结果
    2. 异步:通过回调函数返回结果
    3. 阻塞:线程在进行io时不能执行其他操作
    4. 非阻塞:线程在进行io时可以执行其他操作
  2. BIO:同步阻塞,基于stream流,一个客户端请求对应一个服务器线程
    1. 传统单线程模式,一个Acceptor线程while true循环调用accept方法监听连接请求,监听和处理都由单个线程完成,线程必须处理完一个请求才能处理下一个请求
    2. 伪异步多线程模式:使用线程池处理请求
  3. NIO:同步非阻塞,基于buffer,将监听和处理分开进行,由selector同时监听多个客户端,然后再将事件交给线程进行处理,可以让单个线程高效处理请求
    1. selector:选择器,同时监听多个channel的状态,判断channel中是否有事件发生
    2. channel:双向通道,可读可写,不能直接读写客户端事件,必须通过buffer进行读写
    3. buffer:数据缓冲区,是一块可读可写的内存,用于简化数据的读写
  4. AIO:异步非阻塞,基于回调函数实现
全部评论

相关推荐

犹豫的小狐狸刷了100道题:你是我在牛课上见到的最漂亮的女孩了
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客企业服务