面试-操作系统

进程和线程

进程是资源分配的基本单位,线程是调度运行的基本单位。(最开始的如DOS系统不支持多线程,其以进程为调度单位)
processes are abstractions for the processor, main memory, and I/O devices. The program appears to have exclusive use of both the processor, main memory, andI/O devices.

  • 为什么需要线程? 资源共享简单;轻量,不需要单独分配资源,创建销毁开销小。
  • 什么时候使用多进程? 可靠性高(崩溃不影响其他进程);适用于CPU密集型任务(资源隔离,无需加锁,真并行)
  • 进程间通信方式?管道,信号,共享内存,套接字,消息队列
    • 共享内存
      • This is the fastest form of IPC, because the data does not need to be copied between the client and the server。
      • The only trick in using shared memory is synchronizing access to a given region among multiple processes. If the server is placing data into a shared memory region, the client shouldn’t try to access the data until the server is done.Often, semaphores are used to synchronize shared memory access.
      • 通过让不同进程的虚拟地址映射到同一块物理内存页,从而实现数据共享。内存由内核维护,进程退出不会自动释放。
    • 消息队列
      • If we need a bidirectional flow of data between a client and a server, we can use either message queues or full-duplex pipes.
      • 内核维护一个消息链表,允许不同进程向队列写入或读取数据,需加锁保护。支持全双工,发送和接受时通过msgtype标识消息,接收时遍历链表,获取第一个消息。

可重入锁,可重入函数

同一个线程可嵌套调用调用可重入锁,不会死锁,其通过记录锁当前持有线程及持有数实现。
函数被中断,返回后再次执行,不影响执行结果,即为可重入函数。故可重入函数是指可能被中断的函数,其不能含有全局变量。

程序虚拟内存

  • 为什么需要虚拟内存?
    With one clean mechanism, virtual memory provides three important capabilities: (1) It uses main memory efficiently by treating it as a cache for an address space stored on disk, keeping only the active areas in main memory and transferring data back and forth between disk and memory as needed. (2) It simplifies memory management by providing each process with a uniform address space. (3) It protects the address space of each process from corruption by other processes.
  • MMU是硬件单元吗?怎么起作用的?
    These capabilities are provided by a combination of operating system software, address translation hardware in the MMU (memory management unit), and a data structure stored in physical memory known as a page table that maps virtual pages to physical pages. The address translation hardware reads the page table each time it converts a virtual address to a physical address. The operating system
    is responsible for maintaining the contents of the page table and transferring pages back and forth between disk and DRAM。
  • 虚拟内存换入换出过程?
    Pages are swapped in (paged in) from disk to DRAM, and swapped out (paged out) from DRAM to disk. The strategy of waiting until the last moment to swap in a page, when a miss occurs, is known as demand paging. The CPU has referenced a word in VP 3, which is not cached in DRAM. The address translation hardware reads PTE 3 from memory, infers from the valid bit that VP 3 is not cached, and triggers a page fault exception. The page fault exception invokes a page fault exception handler in the kernel, which selects a victim page—in this case, VP 4 stored in PP 3. If VP 4 has been modified, then the kernel copies it back to disk. In either case, the kernel modifies the page table entry for VP 4 to reflect the fact that VP 4 is no longer cached in main memory. Next, the kernel copies VP 3 from disk to PP 3 in memory, updates PTE 3, and then returns. When the handler returns, it restarts the faulting instruction, which resends the faulting virtual address to the address translation hardware. But now, VP 3 is cached in main memory, and the page hit is handled normally by the address translation hardware.
  • malloc,free时内核的行为?
    Figure 9.8 shows the effect on our example page table when the operating system allocates a new page of virtual memory—for example, as a result of calling malloc. In the example, VP 5 is allocated by creating room on disk and updating PTE 5 to point to the newly created page on disk.
  • 程序虚拟内存的结构
    root@rdma-host1:~# cat /proc/1662/maps
    55adba5e0000-55adba5ec000 r--p 00000000 08:05 394079                     /usr/sbin/sshd
    0 .interp       0000001c  0000000000000318  0000000000000318  00000318  2**0
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    1 .note.gnu.property 00000020  0000000000000338  0000000000000338  00000338  2**3
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    2 .note.gnu.build-id 00000024  0000000000000358  0000000000000358  00000358  2**2
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    3 .note.ABI-tag 00000020  000000000000037c  000000000000037c  0000037c  2**2
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    4 .gnu.hash     000000dc  00000000000003a0  00000000000003a0  000003a0  2**3
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    5 .dynsym       00002f10  0000000000000480  0000000000000480  00000480  2**3
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    6 .dynstr       000018fc  0000000000003390  0000000000003390  00003390  2**0
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    7 .gnu.version  000003ec  0000000000004c8c  0000000000004c8c  00004c8c  2**1
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    8 .gnu.version_r 000001b0  0000000000005078  0000000000005078  00005078  2**3
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    9 .rela.dyn     000033f0  0000000000005228  0000000000005228  00005228  2**3
                    CONTENTS, ALLOC, LOAD, READONLY, DATA
    10 .rela.plt     00002a00  0000000000008618  0000000000008618  00008618  2**3
    
    
    55adba5ec000-55adba66c000 r-xp 0000c000 08:05 394079                     /usr/sbin/sshd
    11 .init         0000001b  000000000000c000  000000000000c000  0000c000  2**2
                    CONTENTS, ALLOC, LOAD, READONLY, CODE
    12 .plt          00001c10  000000000000c020  000000000000c020  0000c020  2**4
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
    13 .plt.got      00000040  000000000000dc30  000000000000dc30  0000dc30  2**4
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
    14 .plt.sec      00001c00  000000000000dc70  000000000000dc70  0000dc70  2**4
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
    15 .text         0007bb75  000000000000f870  000000000000f870  0000f870  2**4
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
    16 .fini         0000000d  000000000008b3e8  000000000008b3e8  0008b3e8  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
    
    55adba66c000-55adba6b4000 r--p 0008c000 08:05 394079                     /usr/sbin/sshd
    17 .rodata       00032f30  000000000008c000  000000000008c000  0008c000  2**5
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
    18 .eh_frame_hdr 00002c24  00000000000bef30  00000000000bef30  000bef30  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
    19 .eh_frame     00011fa8  00000000000c1b58  00000000000c1b58  000c1b58  2**3
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
    
    55adba6b4000-55adba6b8000 r--p 000d3000 08:05 394079                     /usr/sbin/sshd
    20 .init_array   00000008  00000000000d4f90  00000000000d4f90  000d3f90  2**3 (全局构造函数)
                      CONTENTS, ALLOC, LOAD, DATA
    21 .fini_array   00000008  00000000000d4f98  00000000000d4f98  000d3f98  2**3 (全局析构函数)
                      CONTENTS, ALLOC, LOAD, DATA
    22 .data.rel.ro  00001f40  00000000000d4fa0  00000000000d4fa0  000d3fa0  2**5
                      CONTENTS, ALLOC, LOAD, DATA
    23 .dynamic      000002b0  00000000000d6ee0  00000000000d6ee0  000d5ee0  2**3
                      CONTENTS, ALLOC, LOAD, DATA
    24 .got          00000e70  00000000000d7190  00000000000d7190  000d6190  2**3
                      CONTENTS, ALLOC, LOAD, DATA
    
    55adba6b8000-55adba6b9000 rw-p 000d7000 08:05 394079                     /usr/sbin/sshd
    25 .data         00000650  00000000000d8000  00000000000d8000  000d7000  2**5
                    CONTENTS, ALLOC, LOAD, DATA
    26 .bss          000049c0  00000000000d8660  00000000000d8660  000d7650  2**5
                    ALLOC
    27 .gnu_debuglink 00000034  0000000000000000  0000000000000000  000d7650  2**2
                    CONTENTS, READONLY
    
    55adba6b9000-55adba6be000 rw-p 00000000 00:00 0
    55add1d0c000-55add1db9000 rw-p 00000000 00:00 0                          [heap]
    
    7f2176f2b000-7f2176f31000 r--p 00000000 08:05 407150                     /usr/lib/x86_64-linux-gnu/libnss_systemd.so.2
    7f2176fb9000-7f2176fbb000 r--p 00000000 08:05 541658                     /usr/lib/x86_64-linux-gnu/security/pam_gnome_keyring
    
    7f2177b61000-7f2177b62000 rw-p 00000000 00:00 0
    7fff4d3a0000-7fff4d3e5000 rw-p 00000000 00:00 0                          [stack]
    
    7fff4d3f1000-7fff4d3f5000 r--p 00000000 00:00 0                          [vvar]
    7fff4d3f5000-7fff4d3f7000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    
  • vdso,vvar?
    vdso(Virtual Dynamic Shared Object)是内核提供的一种机制,用于无需系统调用即可访问某些内核数据和服务,解决传统系统调用(int 0x80 或 syscall 指令)的性能开销问题。提供常见系统调用的用户空间实现(如 gettimeofday, clock_gettime)。
    vvar(Virtual Variables) 是内核暴露给用户空间的只读数据区域,包含 vdso 所需的内核数据。如系统时间数据,时钟源信息,其他内核状态信息。
    内核通过以下方式保持 vvar 数据更新:
    • 内存映射更新:将内核时间数据映射到用户空间的 vvar 区域
    • 写时复制(COW):虽然显示为只读(r--p),但内核可修改底层物理页
    • 时钟中断触发更新:每次时钟中断会更新相关内存页
  • BSS段存在的意义?
    BSS段(Block Started by Symbol)用于存储未初始化的全局变量和静态变量(或者被初始化为0的变量)。bss不占据实际的磁盘空间,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化。
    优点:在可执行文件中不占用实际空间,只需记录大小信息;提高加载效率,操作系统可以快速清零大块内存。
    root@rdma-host1:~# size /usr/sbin/sshd
     text    data     bss     dec     hex filename
     859101   14016   18880  891997   d9c5d /usr/sbin/sshd
    

编译链接

  • 预处理(Preprocessing):
    • cpp example.c -o example.i 或 gcc -E source.c -o source.i
    • 处理所有以#开头的预处理指令;展开宏定义;处理条件编译指令(如#ifdef);包含头文件(#include);删除注释;添加行号和文件名标识(用于调试)
  • 编译(Compilation)
    • /usr/lib/gcc/x86_64-linux-gnu/9/cc1 example.i -o example.s 或 gcc -S source.i -o source.s
    • 前端语法分析、语义分析;编译优化;生成特定平台的汇编代码,包含汇编指令和标签
  • 汇编(Assembly)
    • as example.s -o example.o 或 gcc -c source.s -o source.o
    • 生成二进制目标文件,包含机器指令和重定位信息
  • 链接(Linking)
    • ld -o demo /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o demo.o -lc /usr/lib/x86_64-linux-gnu/crtn.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 或 gcc source.o -o executable
    • 合并所有目标文件;解析外部符号引用;链接库文件(静态库/动态库);分配内存地址;生成可执行文件
  1. 二进制文件常用命令及其作用?

    uname -m  # 输出如 x86_64, arm64, i386 等
    lscpu  # Linux
    readelf -h <ELF文件>      # 查看ELF文件头信息
    objdump -f <二进制文件>    # 显示文件头信息
    file <二进制文件>          # 识别文件类型和架构
    ldd <可执行文件>          # 查看动态库依赖
    addr2line -e <二进制文件> <地址> # 将地址转换为源码位置
    strip --strip-debug <文件>  # 移除调试符号
    size <二进制文件>          # 查看各段大小
    strings <二进制文件>       # 提取字符串
    xxd <二进制文件>           # 十六进制转储
    
  2. 编译时如何区分不同平台?一般有哪些平台?
    通过编译宏获取。x86,x86-64; arm, arm64; mips; riscv.

  3. 汇编时需要根据考虑平台差异吗?
    编译全流程都需要考虑。通过-march=x86-64指定。不同的CPU架构,指令集不同(寄存器命名,参数传递、栈帧处理方式)。

  4. 链接时ld和collect2区别?
    collect2是GCC的链接过程包装器,不可直接调用,处理GCC特有的链接需求(如全局构造函数/析构函数),最终会调用ld完成实际链接工作。
    collect2会处理全局构造/析构函数:attribute((constructor))函数/attribute((destructor))函数,确保它们在main()之前/后被调用。
    ld链接也会调用全局构造/析构函数,CRT(C Running time)?>>?

  5. 除了GCC还有哪些其他编译器?LLVM?CLANG?

    对比维度 GCC (GNU Compiler Collection) LLVM/Clang
    架构设计 整体式设计,前后端耦合紧密 模块化设计,前后端完全分离
    许可证 GPL (传染性强) Apache 2.0 (商业友好)
    中间表示(IR) GENERIC → GIMPLE → RTL (多级转换) 单一 LLVM IR (全流程通用)
    编译速度 较慢(尤其增量编译) 通常快 15-30%
    内存占用 较高 更低(约减少 20-30%)
    错误诊断 基础提示 更清晰,带代码上下文和修复建议
    语言支持 C/C++/Fortran/Ada/Go 等 C/C++/Obj-C/Rust/Swift 等(无Fortran)
    平台支持 嵌入式平台支持更成熟 对 Apple/GPU 支持更好
    优化能力 传统数值计算优化更强 现代代码(如C++虚函数)优化更优
    工具链集成 GNU Binutils (as/ld/gdb) LLVM工具链 (lld/llvm-lto/llvm-ar等)
    调试信息 DWARF 格式 也支持 DWARF,但调试体验更佳
    C++标准支持 新标准支持稍慢 通常更快支持新特性
    跨平台编译 需配置多工具链 原生支持交叉编译(如 -target 参数)
    代码分析 基础静态分析 强大静态分析(clang-tidy/scan-build)
    Sanitizers 支持有限 全面支持(ASan/UBSan/TSan/MSan)
    JIT编译 不支持 原生支持(LLVM JIT引擎)
    典型应用场景 Linux内核/GNU软件/嵌入式开发 Apple开发/前沿C++项目/源码分析工具
  6. 动态库和静态库链接区别?
    静态库编译时直接链接到可执行程序内部,无外部依赖,调用无额外开销,启动速度更快。
    动态库:运行时解析符号表并加载到内存,可单独更新库文件。启动速度略慢,只需保存一份代码,磁盘占用小,多个程序共享动态库时,可共享代码页,内存占用小。
    动态库如何实现减少内存占用的?

    • 代码页(物理内存)共享:动态库的代码段(.text段)在内存中以只读方式加载,所有使用该库的进程映射到同一物理内存页。
    • 内存映射技术(mmap):通过mmap系统调用将库文件映射到进程地址空间,采用写时复制(Copy-On-Write)技术。未修改的代码段始终共享,数据段私有化(每个进程有独立副本)。
    • 延迟加载(Lazy Loading):PLT/GOT机制:过程链接表(PLT)和全局偏移表(GOT)实现,函数第一次被调用时才加载实际代码。
    • 按需分页(Demand Paging):只加载实际被执行的代码页,未使用的函数不会占用物理内存。

内存泄漏

如何观察是否泄漏?
cat /proc//status | grep -E 'VmSize|VmRSS' # Linux进程内存信息
watch -n 1 'ps -p -o %mem,rss,vsz' # 定期采样检测
watch -n 0.1 "cat /proc/$(pidof vmRSS)/status | grep -E 'VmRSS|VmSize|VmSwap'"

posted @ 2025-03-24 18:56  3yearleft  阅读(21)  评论(0)    收藏  举报