面试-操作系统
进程和线程
进程是资源分配的基本单位,线程是调度运行的基本单位。(最开始的如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
- 合并所有目标文件;解析外部符号引用;链接库文件(静态库/动态库);分配内存地址;生成可执行文件
-
二进制文件常用命令及其作用?
uname -m # 输出如 x86_64, arm64, i386 等 lscpu # Linux readelf -h <ELF文件> # 查看ELF文件头信息 objdump -f <二进制文件> # 显示文件头信息 file <二进制文件> # 识别文件类型和架构 ldd <可执行文件> # 查看动态库依赖 addr2line -e <二进制文件> <地址> # 将地址转换为源码位置 strip --strip-debug <文件> # 移除调试符号 size <二进制文件> # 查看各段大小 strings <二进制文件> # 提取字符串 xxd <二进制文件> # 十六进制转储 -
编译时如何区分不同平台?一般有哪些平台?
通过编译宏获取。x86,x86-64; arm, arm64; mips; riscv. -
汇编时需要根据考虑平台差异吗?
编译全流程都需要考虑。通过-march=x86-64指定。不同的CPU架构,指令集不同(寄存器命名,参数传递、栈帧处理方式)。 -
链接时ld和collect2区别?
collect2是GCC的链接过程包装器,不可直接调用,处理GCC特有的链接需求(如全局构造函数/析构函数),最终会调用ld完成实际链接工作。
collect2会处理全局构造/析构函数:attribute((constructor))函数/attribute((destructor))函数,确保它们在main()之前/后被调用。
ld链接也会调用全局构造/析构函数,CRT(C Running time)?>>? -
除了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++项目/源码分析工具 -
动态库和静态库链接区别?
静态库编译时直接链接到可执行程序内部,无外部依赖,调用无额外开销,启动速度更快。
动态库:运行时解析符号表并加载到内存,可单独更新库文件。启动速度略慢,只需保存一份代码,磁盘占用小,多个程序共享动态库时,可共享代码页,内存占用小。
动态库如何实现减少内存占用的?- 代码页(物理内存)共享:动态库的代码段(.text段)在内存中以只读方式加载,所有使用该库的进程映射到同一物理内存页。
- 内存映射技术(mmap):通过mmap系统调用将库文件映射到进程地址空间,采用写时复制(Copy-On-Write)技术。未修改的代码段始终共享,数据段私有化(每个进程有独立副本)。
- 延迟加载(Lazy Loading):PLT/GOT机制:过程链接表(PLT)和全局偏移表(GOT)实现,函数第一次被调用时才加载实际代码。
- 按需分页(Demand Paging):只加载实际被执行的代码页,未使用的函数不会占用物理内存。
内存泄漏
如何观察是否泄漏?
cat /proc/
watch -n 1 'ps -p
watch -n 0.1 "cat /proc/$(pidof vmRSS)/status | grep -E 'VmRSS|VmSize|VmSwap'"

浙公网安备 33010602011771号