当前位置: 首页 > article >正文

操作系统面试题

进程管理

  1. 说说动态库和静态库的区别?

    1. 编译和链接阶段
      • 静态库:在编译时被链接到程序中,库中的代码会被复制到生成的可执行文件中。编译器在编译程序时,将需要的静态库函数代码直接拷贝到可执行文件中,因此生成的程序文件较大。
      • 动态库:在编译时不会被直接包含到程序中,程序只会包含对动态库的引用。运行时,系统会加载动态库,并在运行时将程序对动态库的调用重定向到库的实现上。这使得编译生成的可执行文件较小。
    2. 文件大小
      • 静态库:由于将库的内容嵌入到可执行文件中,生成的可执行文件较大。
      • 动态库:生成的可执行文件较小,因为它仅包含对动态库的引用。真正的库代码存储在外部文件中。
    3. 内存占用
      • 静态库:每个进程会各自加载并包含一份静态库代码,多个进程共享静态库代码的可能性较低,因此内存占用相对较高。
      • 动态库:多个进程可以共享同一份动态库代码,只有在第一次加载时才会被加载到内存中,后续使用时可直接共享,节省了内存空间。
  2. 讲讲上下文(进程、线程)切换的过程?

    1. 进程切换
      进程切换需要在两个独立的进程之间切换执行,涉及到相对较重的上下文保存和恢复。进程切换时,操作系统会:
    2. 保存当前进程的上下文:
      o 将当前进程的寄存器(包括程序计数器PC、栈指针SP等)值保存在进程控制块(PCB)中。
      o 保存当前进程的内存映射信息,如页表、虚拟地址空间等。
    3. 更新进程调度信息:
      o 根据调度算法(如时间片轮转、优先级调度等),选择下一个要运行的进程。
      o 更新调度信息,如上一个进程的状态设置为“阻塞”或“就绪”,新的进程设置为“运行”。
    4. 恢复新进程的上下文:
      o 恢复新的进程的寄存器内容、程序计数器等,重新加载页表信息,使CPU和内存指向新的地址空间。
      o 在多核系统中,可能需要刷新TLB(Translation Lookaside Buffer)缓存,以确保新的虚拟地址空间正常访问。
    5. 重新开始执行:
      o CPU执行恢复的程序计数器所指向的指令,继续运行新进程。
      由于进程切换需要重新加载内存地址空间,涉及内存管理信息的切换,开销较大。
    6. 线程切换
      线程切换发生在同一进程内的不同线程之间,相比进程切换,线程切换不需要重新加载进程的地址空间,因此开销更小。线程切换过程为:
    7. 保存当前线程的上下文:
      o 将当前线程的寄存器(包括程序计数器PC、栈指针SP等)值保存在线程控制块(TCB)中。
      o 因为同一进程中的线程共享相同的地址空间,不涉及页表切换,因此不需保存内存映射信息。
    8. 更新线程调度信息:
      o 使用线程调度算法选择要运行的下一个线程,将当前线程的状态更新为“阻塞”或“就绪”,新线程状态更新为“运行”。
    9. 恢复新线程的上下文:
      o 恢复新的线程的寄存器内容和程序计数器,使其指向新线程的栈和代码位置。
    10. 重新开始执行:
      o CPU执行恢复的程序计数器所指向的指令,继续运行新线程。
      线程切换的开销更低,因为不涉及内存空间的切换和内存映射的重载,仅需要保存和恢复少量的寄存器。
  3. 进程和线程的区别?
    进程和线程在内核都用一个task_struct结构体表示,两者区别在于资源的共享程度,由task_struct中各字段是否共享决定,而字段是否共享则由clone()创建新task时的参数flag决定的。
    进程:拥有独立虚拟地址空间,包括代码段、数据段和堆栈段
    线程:同一线程组的线程共享父进程的虚拟地址空间(线程组id=父进程id)
    线程拥有独立的栈空间和寄存器,共享堆空间
    最大的区别在于上下文切换:
    进程
    虚拟内存与物理内存的映射需要查页表,TLB缓存记录映射关系,可以加快查询速度,而上下文切换时会切换页表(更新cr3寄存器指向新的页表),TLB失效,切换到新进程后查页表速度很慢,因为缓存命中率低。切换页表是进程切换的主要开销
    线程
    由于同一进程内的线程共享虚拟内存地址空间,没有切换页表(即更新cr3寄存器),也不会刷新TLB,内核只需保存当前线程的寄存器状态、程序计数器、堆栈指针等,开销相对较小
    (CR3寄存器:CR3寄存器保存的是当前进程的页目录的物理地址)

  4. 描述系统调用过程?
    用户态调用标准库函数发起系统调用,会触发0x80中断或syscall指令切换到内核态。进入内核态根据eax寄存器保存的系统调用号,查系统调用表找到对应处理函数并执行,执行结果保存在eax寄存器,再使用 iret 指令将 CPU 恢复到用户态

  5. 讲讲用户态到内核态的切换过程?
    用户态通过系统调用、异常、中断三种方式可以陷入内核态
    进入内核态前首先保存用户态上下文,包括程序计数器(PC)、栈指针(SP)、通用寄存器和状态寄存器,每个进程的上下文保存到独立的内核栈中
    接着修改CS寄存器(代码段寄存器)的特权级别位CS.RPL(00内核态,11用户态)来实现cpu权限的转换
    使用iret指令将CS.RPL = 11恢复到用户态

  6. 进程间通信方式了解哪些?
    共享内存mmap,socket通信、管道、消息队列
    其中共享内存需要考虑多进程同时修改的竞态问题,即进程间同步的问题,使用信号量解决:初始化一个信号量=1,使用PV操作:第一个进程执行P操作将信号量-1,第二个进程判断信号量=0会阻塞等待,第一个进程操作完成后执行V操作将信号量+1,这时信号量>0,其他进程可以访问共享资源

  7. 线程同步方式?
    原子操作、锁和条件变量:
    1.原子操作:c++11提供原子变量,对原子变量的修改保证原子性,底层用cpu硬件提供的CAS原子指令
    2.互斥锁、自旋锁。
    -互斥锁:底层依赖CAS,当一个线程持有锁,会临时屏蔽中断保证操作的连续,没有获取到锁的线程被记录到一个阻塞队列中阻塞等待,当锁再次可用时唤醒一个线程并恢复其上下文开始调度运行。
    -自旋锁:同样依赖CAS,当一个线程持有锁,会临时屏蔽中断保证操作的连续,没有获取到锁的线程会循环检查锁状态,避免了线程从阻塞到恢复的上下文切换的开销。为了避免自旋等待时间过长导致性能下降,通常会对自旋次数进行限制,达到自选次数上限会被阻塞。
    两者都会维护一个表示锁状态的标志位,0表示锁空闲,1表示锁被占用。当一个线程尝试获取锁时,它会尝试使用原子操作(CAS)将锁状态从0修改为1
    -条件变量:
    条件变量必须配合互斥锁实现,即调用pthread_cond_wait前后必须上锁、释放锁
    因为pthread_cond_wait内多个子行为并不是原子性的,所以需要使用锁。
    条件变量的原理:1.先释放锁,然后使线程阻塞,加入等待队列。2.被条件变量唤醒后,先获取锁,函数返回,返回后线程的状态与调用pthread_cond_wait前一致,都为获取锁的状态。

  8. 讲讲CAS与原子操作?
    CAS是一种用于实现原子性操作的机制Compare-And-Swap),一般由硬件提供支持,软件层面也可以实现。它的基本操作包括三个参数:

    1. 内存地址:需要进行比较和交换的内存位置。
    2. 预期值(expected):希望内存位置当前的值。
    3. 新值(new):如果内存位置的值等于预期值,则将其更新为这个新值。
      工作原理
      CAS的工作原理如下:
      • CAS操作会先读取内存地址的当前值。
      • 然后将这个值与预期值进行比较:
      o 如果它们相等,则将内存地址的值替换为新值,并返回成功的结果(通常为真)。
      o 如果它们不相等,则不进行任何操作,并返回失败的结果(通常为假)。
      原子操作:一个操作要么完全执行成功,要么完全不执行,无法被其他操作打断。称为原子操作。
  9. 讲讲fork()?
    1.调用fork() 首先创建一个task_struct结构体
    2.底层调用copy_process()复制父进程的虚拟地址空间、文件描述符等信息(除了pid其他全部复制)
    3.复制虚拟地址空间时,使用写时复制:
    只复制页表(记录虚拟地址-物理地址映射关系),此时父子进程的页表映射在同一块物理地址。并将父进程和子进程的相同页表标记为只读,当父进程或子进程试图修改标记为只读的共享内存区域,为该进程创建一个新的物理内存页并映射到虚拟地址,并将此页面(父和子)设置为可读写并进行修改。
    如果只读虚拟内存,父子进程会访问到同一块物理内存,因为页表是相同的
    4.内核会将新创建的子进程的状态设置为就绪态,等待被调度。Fork将子进程的 PID 返回给父进程,并将 0 返回给子进程

  10. 进程调度策略了解哪些?
    CFS公平调度策略(linux)
    用红黑树记录每一个task的运行时间,每次选择运行时间最短的task调度运行,而这个运行时间是虚拟运行时间,会根据其nice值*实际运行时间,也就是加权的,故nice值小的task实际会被分配到更多cpu时间。选择最小ask运行时移除红黑树,运行一段时间后重新计算运行时间,并重新插入到红黑树,然后选择下一个最小节点。

  11. 后台进程有什么特点?如何实现?
    后台运行:守护进程在后台运行,没有控制终端,不与用户直接交互。
    生命周期长:通常随系统启动运行,直到系统关闭或服务被手动停止。
    实现:
    Fork创建子进程、父进程退出,该子进程会被init进程接管,成为孤儿进程

  12. cpu是怎么执行指令的?
    取指令、译码、执行、写回
    取指令:CPU的程序计数器(PC)会存储下一条要执行的指令地址。在取指阶段,CPU从内存(RAM或高速缓存)中取出该地址处的指令,并加载到指令寄存器(IR)。同时,PC会递增,以指向下一条指令的地址。
    译码:将指令解析为操作码和操作数
    执行:cpu算术逻辑单元执行算术操作
    写回:将计算结果保存到寄存器或内存

  13. 什么是死锁?如何避免?
    死锁是指多个进程(或线程)相互等待对方持有的资源释放,也就是“占有并等待”的状态
    无法避免,可以进行死锁检测

  14. 互斥锁是怎么实现的?
    (同问题7.)

  15. 条件变量是怎么实现的?
    (同7.)

  16. 中断是怎么实现的?
    Cpu收到中断后先保存cpu上下文,再根据中断号查中断向量表,找到中断处理函数,处理中断后恢复上下文。

  17. Linux进程和线程是怎么实现的?
    (主要介绍一下task_struct中的字段,哪些实现了内存管理、进程调度、堆栈等)

  18. 一个进程最多可以创建多少个线程?
    首先,受系统资源的限制:每个线程需要内存维护栈空间和线程控制块(pcb),一个线程栈空间1-8MB,ulimit -s 命令调整每个线程的栈空间大小。减小栈空间可以允许更多线程。
    其次,系统会限制单进程的线程数,由内核参数决定,参数可以修改

  19. epoll最多能接受多少个连接?
    首先epoll不限制能管理的连接(fd)数量,所以这个问题是在问操作系统可以创建多少tcp连接
    1.系统参数限制了单进程能创建的最大文件描述符fd的数量,默认1024
    2. 每个连接都会占用一定的内存,包括套接字缓冲区、TCP 控制块
    3. tcp连接,它由一个四元组唯一确定(源端口,目的端口,源ip,目的ip)。对于服务端的每一个ip+端口,一个客户端最多能创建64511个连接,因为端口号是16位的,最多表示65535个端口,减去0-1023不可分配的端口只剩下64511个

获取Linux C/C++开发学习资料


http://www.kler.cn/a/384692.html

相关文章:

  • Android——多线程、线程通信、handler机制
  • ENSP作业——园区网
  • 为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
  • lsblk 命令学习
  • 什么是蜂窝移动网络
  • OpenResty 1.27.1.1 已经正式发布
  • ssm060基于SSM的高校共享单车管理系统的设计与实现+vue(论文+源码)_kaic
  • 前端md5加密
  • 高级Python自动化运维:容器安全与网络策略的深度解析
  • 深入学习指针(5)!!!!!!!!!!!!!!!
  • 5G的发展演进
  • Java智慧养老养老护理帮忙代办陪诊陪护平台系统小程序源码
  • cmake中execute_process详解
  • 全卷积和全连接
  • C++20 STL CookBook2 更强大的编译时 + 安全比较 + spaceship比较符
  • IP SSL证书
  • 22.04Ubuntu---ROS2使用rclcpp编写节点
  • Catsxp云游戏白屏
  • 飞牛fnOs内网穿透-使用Lucky实现ipv6动态解析+HTTPS访问NAS服务
  • Go-性能优化、自动内存管理
  • Spring Boot详解:从入门到精通
  • File.separator与File.separatorChar的区别
  • 166页PDF | 埃森哲-XX集团企业架构数字化整体规划设计方案(限免下载)
  • Javaweb梳理10——Maven
  • 2020年美国总统大选数据分析与模型预测
  • 【人工智能】利用大语言模型(LLM)实现机器学习模型选择与实验的自动化