C++ Linux系统编程基础面试题

1. Linux进程和线程的区别?如何创建?

答案:

本质区别:

  • 进程是资源分配的基本单位,线程是CPU调度的基本单位
  • 进程拥有独立的地址空间,而线程共享所属进程的地址空间
  • 进程间切换开销大(需要切换页表、刷新TLB等),线程切换开销小
  • 进程间通信需要特殊的IPC机制,线程间可以直接读写进程数据段(如全局变量)

资源占用:

  • 进程有独立的代码段、数据段、堆、栈
  • 同一进程的线程共享代码段、数据段、堆,但每个线程有独立的栈和寄存器
  • 创建进程需要分配独立的内存空间,开销大
  • 创建线程只需要分配栈空间,开销小得多

通信方式:

  • 进程间通信:管道、消息队列、共享内存、信号量、Socket等
  • 线程间通信:直接访问共享变量,配合互斥锁、条件变量等同步机制

创建方式:

  • 进程:使用fork()系统调用,子进程是父进程的副本
  • 线程:使用pthread_create(),新线程与创建者共享地址空间

fork的写时复制机制:

  • fork后父子进程共享物理内存页
  • 只有在某个进程写入时才真正复制该页
  • 大大提高了fork的效率

实际应用场景:

  • 多进程:需要隔离性、稳定性高的场景(如Nginx的多进程模型)
  • 多线程:需要频繁通信、共享数据的场景(如Web服务器处理请求)

2. Linux进程间通信(IPC)有哪些方式?各有什么特点?

答案:

Linux提供了多种进程间通信方式,各有特点和适用场景:

1. 管道(Pipe)

  • 特点:半双工通信,数据只能单向流动;只能用于有亲缘关系的进程(父子进程)
  • 原理:内核维护一个缓冲区,一个进程写入,另一个进程读取
  • 优点:简单易用,由内核管理
  • 缺点:只能单向通信,需要双向通信要创建两个管道;只能用于父子进程
  • 应用场景:Shell命令管道(如 ls | grep)

2. 命名管道(FIFO)

  • 特点:有文件名的管道,存在于文件系统中
  • 优点:可以用于任意两个进程间通信,不要求有亲缘关系
  • 缺点:仍然是半双工;读写需要同步
  • 应用场景:不相关进程间的简单通信

3. 消息队列(Message Queue)

  • 特点:消息的链表,存放在内核中
  • 优点:可以按消息类型接收;不需要同步;消息有边界
  • 缺点:有大小限制;不适合大数据量传输
  • 应用场景:需要按类型处理消息的场景

4. 共享内存(Shared Memory)

  • 特点:多个进程映射到同一块物理内存
  • 优点:最快的IPC方式,不需要数据拷贝
  • 缺点:需要额外的同步机制(如信号量);容易出现竞态条件
  • 应用场景:大量数据交换、高性能要求的场景
  • 注意:必须配合信号量或互斥锁使用,否则会有数据竞争

5. 信号量(Semaphore)

  • 特点:计数器,用于进程同步
  • 作用:主要用于保护共享资源,控制访问顺序
  • 操作:P操作(等待/减1)、V操作(释放/加1)
  • 应用场景:配合共享内存使用,实现进程同步

6. 信号(Signal)

  • 特点:异步通知机制,软件层面的中断
  • 优点:简单,可以通知进程发生了某个事件
  • 缺点:不能传递复杂数据,只能传递信号类型
  • 应用场景:进程控制(如Ctrl+C发送SIGINT)、异常处理

7. 套接字(Socket)

  • 特点:可用于网络通信,也可用于本地通信(Unix Domain Socket)
  • 优点:功能强大,支持网络通信;全双工
  • 缺点:相对复杂,开销较大
  • 应用场景:网络通信、客户端-服务器模型

性能对比:

  • 共享内存 > 管道 > 消息队列 > Socket
  • 共享内存最快是因为不需要数据拷贝,直接访问内存
  • 其他方式都需要在用户空间和内核空间之间拷贝数据

选择建议:

  • 简单的父子进程通信:管道
  • 大量数据、高性能:共享内存+信号量
  • 需要消息类型:消息队列
  • 网络通信或复杂通信:Socket
  • 进程控制和通知:信号

3. 什么是僵尸进程和孤儿进程?如何避免?

答案:

僵尸进程(Zombie Process):

定义和产生原因:

  • 子进程已经终止,但父进程还没有调用wait()或waitpid()来回收它
  • 进程虽然已死,但进程表项(PCB)还在,占用系统资源
  • 在ps命令中状态显示为Z(Zombie)
  • 无法通过kill命令杀死,因为它已经死了

为什么会产生:

  • 父进程太忙,没来得及调用wait()
  • 父进程编程错误,忘记调用wait()
  • 父进程故意不调用wait()(设计问题)

危害:

  • 占用进程表项,系统进程数有限
  • 大量僵尸进程会导致无法创建新进程
  • 浪费系统资源(虽然不占用内存,但占用PID)

如何避免:

  1. 父进程调用wait()或waitpid():及时回收子进程资源
  2. 信号处理:捕获SIGCHLD信号,在信号处理函数中调用wait()
  3. 两次fork:让子进程立即退出,孙进程由init收养
  4. 忽略SIGCHLD信号:某些系统会自动回收(不推荐)

孤儿进程(Orphan Process):

定义:

  • 父进程先于子进程终止,子进程成为孤儿进程
  • 孤儿进程会被init进程(PID=1)或systemd收养
  • init进程会自动调用wait()回收孤儿进程

特点:

  • 不会造成资源浪费,因为init会负责回收
  • 是正常现象,不是问题
  • 守护进程就是特殊的孤儿进程

区别总结:

  • 僵尸进程:子进程死了,父进程还活着但不回收 → 有害
  • 孤儿进程:父进程死了,子进程还活着 → 无害,由init收养

实际案例:

  • 如果一个长期运行的服务器程序不处理SIGCHLD信号,每次fork出的子进程结束后都会变成僵尸进程
  • 时间长了会积累大量僵尸进程,最终导致系统无法创建新进程
  • 这是很多服务器程序的常见bug

4. 如何创建守护进程?守护进程有什么特点?

答案:

守护进程(Daemon)的定义和特点:

什么是守护进程:

  • 在后台运行的特殊进程,不与任何终端关联
  • 生命周期很长,通常从系统启动一直运行到系统关闭
  • 不受用户登录注销的影响
  • 通常以超级用户权限运行
  • 命名通常以d结尾(如httpd、sshd、mysqld)

为什么需要守护进程:

  • 提供系统服务(如Web服务器、数据库服务器)
  • 执行定期任务(如日志清理、备份)
  • 监控系统状态
  • 不需要用户交互

创建守护进程的步骤:

  1. fork并退出父进程目的:让子进程在后台运行如果是从Shell启动,父进程退出后Shell认为命令已完成
  2. 调用setsid()创建新会话成为新会话的首进程脱离原来的控制终端成为新进程组的组长此时进程没有控制终端
  3. 再次fork并退出父进程(可选但推荐)目的:确保进程不是会话首进程防止进程重新打开控制终端提高安全性
  4. 改变工作目录到根目录使用chdir("/")防止占用可卸载的文件系统避免当前目录被删除导致问题
  5. 重设文件权限掩码使用umask(0)清除继承的文件权限掩码让守护进程创建的文件有明确的权限
  6. 关闭不需要的文件描述符关闭从父进程继承的打开文件特别是标准输入、输出、错误(0、1、2)释放资源,避免占用
  7. 重定向标准输入输出到/dev/null防止守护进程意外读写终端避免输出信息丢失或造成错误

守护进程的管理:

  • 通常将PID写入文件(如/var/run/xxx.pid)
  • 通过信号控制(SIGTERM终止、SIGHUP重新加载配置)
  • 使用systemd或init脚本管理
  • 日志输出到syslog或专门的日志文件

常见的守护进程:

  • sshd:SSH服务
  • httpd/nginx:Web服务器
  • mysqld:MySQL数据库
  • cron:定时任务
  • syslogd:系统日志

5. Linux文件I/O:系统调用和标准I/O的区别?

答案:

系统调用(System Call)- 无缓冲I/O:

特点:

  • 直接调用内核提供的系统调用(open、read、write、close等)
  • 每次调用都会陷入内核态,有上下文切换开销
  • 没有用户空间缓冲,数据直接在内核缓冲区和用户空间之间传输
  • 返回文件描述符(int类型)
  • 更底层,更灵活,但使用相对复杂

优点:

  • 可以精确控制I/O操作
  • 适合对性能要求高的场景
  • 可以使用特殊标志(如O_DIRECT、O_SYNC)
  • 适合二进制文件操作

缺点:

  • 频繁的系统调用开销大
  • 需要手动管理缓冲
  • 代码相对复杂

标准I/O(Standard I/O)- 缓冲I/O:

特点:

  • C标准库提供的I/O函数(fopen、fread、fwrite、fclose等)
  • 在用户空间维护缓冲区,减少系统调用次数
  • 返回文件指针(FILE*类型)
  • 更高层,使用简单,但灵活性较低

缓冲类型:

  1. 全缓冲:缓冲区满了才进行实际I/O操作(普通文件)
  2. 行缓冲:遇到换行符才刷新缓冲区(终端设备)
  3. 无缓冲:立即进行I/O操作(标准错误stderr)

优点:

  • 减少系统调用次数,提高效率
  • 使用简单,API丰富(格式化输入输出)
  • 自动管理缓冲区
  • 可移植性好

缺点:

  • 缓冲可能导致数据延迟写入
  • 不适合需要精确控制的场景
  • 额外的内存开销

性能对比:

  • 小数据量频繁读写:标准I/O更快(减少系统调用)
  • 大块数据读写:系统调用可能更快(减少拷贝)
  • 需要立即写入:系统调用更合适(如日志)

文件描述符的特殊值:

  • 0:标准输入(STDIN_FILENO)
  • 1:标准输出(STDOUT_FILENO)
  • 2:标准错误(STDERR_FILENO)
  • 这三个在进程启动时自动打开

何时使用哪种:

  • 文本文件、格式化I/O:标准I/O
  • 二进制文件、网络编程:系统调用
  • 需要精确控制、高性能:系统调用
  • 简单应用、可移植性:标准I/O

混合使用注意:

  • 不要对同一个文件同时使用系统调用和标准I/O
  • 如果必须混用,需要使用fflush()刷新缓冲区
  • 可以使用fileno()将FILE*转换为文件描述符

6. select、poll、epoll的区别?为什么epoll性能最好?

答案:

这三个都是Linux下的I/O多路复用机制,用于同时监控多个文件描述符的状态。

select:

原理:

  • 将要监控的文件描述符集合从用户空间拷贝到内核空间
  • 内核遍历整个集合,检查是否有就绪的文件描述符
  • 将结果拷贝回用户空间
  • 用户程序再次遍历找出就绪的文件描述符

限制:

  • 最大监控1024个文件描述符(FD_SETSIZE限制)
  • 每次调用都需要拷贝整个fd_set
  • 需要遍历所有文件描述符,时间复杂度O(n)
  • 不能跨平台修改限制

优点:

  • 跨平台,几乎所有Unix系统都支持
  • 超时精度高(微秒级)

缺点:

  • 性能差,不适合大量连接
  • 有最大文件描述符限制

poll:

改进:

  • 使用pollfd结构数组,没有最大文件描述符数量限制
  • 通过events和revents分离输入输出,不会修改原数组
  • 仍然需要遍历所有文件描述符,O(n)复杂度

优点:

  • 没有最大连接数限制
  • 不会修改原数组

缺点:

  • 仍需要拷贝整个pollfd数组
  • 仍需要遍历所有文件描述符
  • 性能仍然不够好

epoll(Linux特有):

革命性改进:

  • 使用事件驱动机制,不需要遍历
  • 内核维护就绪列表,只返回就绪的文件描述符
  • 使用红黑树管理文件描述符,增删改查O(log n)
  • 使用mmap减少内存拷贝

工作模式:

  1. 水平触发(LT,Level Triggered):默认模式,类似select/poll只要文件描述符就绪,就会一直通知不会丢失事件,编程简单如果不处理,下次还会通知
  2. 边缘触发(ET,Edge Triggered):高性能模式只在状态变化时通知一次必须一次性读完所有数据编程复杂,但性能更好

优点:

  • 没有最大连接数限制(受系统资源限制)
  • 不需要遍历,O(1)时间复杂度
  • 使用mmap,减少内存拷贝
  • 支持边缘触发,性能极高

缺点:

  • 只支持Linux
  • API相对复杂
  • 边缘触发模式编程难度大

性能对比:

  • 少量连接(<100):三者差别不大
  • 中等连接(100-1000):epoll明显优于select/poll
  • 大量连接(>1000):epoll性能远超select/poll
  • 高并发场景:epoll是唯一选择

为什么epoll性能最好:

  1. 不需要遍历:只返回就绪的文件描述符
  2. 减少拷贝:使用mmap共享内存
  3. 事件驱动:内核主动通知,而不是轮询
  4. 高效的数据结构:红黑树管理文件描述符

实际应用:

  • Nginx:使用epoll实现高并发
  • Redis:使用epoll处理网络事件
  • Node.js:底层使用epoll(Linux平台)

选择建议:

  • 跨平台、连接数少:select
  • Linux、中等连接:poll
  • Linux、高并发:epoll(首选)

7. 什么是信号?信号的处理方式有哪些?

答案:

信号的定义和作用:

什么是信号:

  • 软件层面的中断机制,用于进程间异步通信
  • 由内核发送给进程,通知进程发生了某个事件
  • 进程可以捕获、忽略或使用默认方式处理信号
  • 是一种异步通信机制,不需要进程主动查询

信号的来源:

  1. 硬件异常:除零、非法内存访问等
  2. 用户输入:Ctrl+C(SIGINT)、Ctrl+\(SIGQUIT)
  3. 软件条件:定时器到期、管道破裂等
  4. 其他进程:通过kill()系统调用发送

常见信号及其含义:

终止信号:

  • SIGINT(2):中断信号,Ctrl+C,可捕获
  • SIGQUIT(3):退出信号,Ctrl+\,产生core dump
  • SIGKILL(9):强制终止,不能捕获、忽略或阻塞
  • SIGTERM(15):终止信号,优雅退出,可捕获
  • SIGSTOP(19):停止进程,不能捕获

异常信号:

  • SIGSEGV(11):段错误,非法内存访问
  • SIGFPE(8):浮点异常,如除零
  • SIGILL(4):非法指令
  • SIGBUS(7):总线错误

其他信号:

  • SIGCHLD(17):子进程状态改变(终止或停止)
  • SIGALRM(14):定时器到期
  • SIGPIPE(13):管道破裂,写入无读者的管道
  • SIGHUP(1):终端挂断,常用于重新加载配置
  • SIGUSR1/SIGUSR2:用户自定义信号

信号的处理方式:

  1. 默认处理(Default):大多数信号的默认动作是终止进程某些信号会产生core dump文件SIGCHLD的默认动作是忽略
  2. 忽略信号(Ignore):进程可以选择忽略某些信号SIGKILL和SIGSTOP不能被忽略忽略SIGCHLD可能导致僵尸进程
  3. 捕获信号(Catch):注册信号处理函数信号发生时执行自定义处理SIGKILL和SIGSTOP不能被捕获

信号处理的注意事项:

可重入性问题:

  • 信号处理函数可能在任何时候被调用
  • 可能打断正在执行的代码
  • 信号处理函数必须是可重入的(reentrant)
  • 不能调用不可重入的函数(如malloc、printf)

安全的做法:

  • 只在信号处理函数中设置标志变量
  • 使用sig_atomic_t类型的全局变量
  • 在主程序中检查标志并处理
  • 使用异步信号安全的函数

信号的阻塞:

  • 可以临时阻塞某些信号
  • 阻塞期间信号会被挂起
  • 解除阻塞后信号会被传递
  • 用于保护临界区

实际应用场景:

  1. 优雅退出:捕获SIGTERM,清理资源后退出
  2. 重新加载配置:捕获SIGHUP,重新读取配置文件
  3. 定时任务:使用SIGALRM实现定时器
  4. 子进程管理:捕获SIGCHLD,回收僵尸进程
  5. 调试:捕获SIGSEGV,打印调用栈

信号与多线程:

  • 信号是发送给进程的,不是线程
  • 可以指定哪个线程处理信号
  • 建议使用专门的线程处理信号
  • 避免在信号处理函数中使用线程相关函数

8. 什么是内存映射(mmap)?有什么优势?

答案:

内存映射的定义:

什么是mmap:

  • 将文件或设备的内容映射到进程的虚拟地址空间
  • 通过内存地址直接访问文件内容,而不需要read/write系统调用
  • 建立文件磁盘地址和进程虚拟地址之间的映射关系
  • 是一种高效的文件I/O方式

工作原理:

  1. 进程调用mmap,内核在进程地址空间中分配一块虚拟内存区域
  2. 建立虚拟内存到文件的映射关系(此时并不真正读取文件)
  3. 进程访问这块内存时,触发缺页中断
  4. 内核将文件对应部分读入物理内存
  5. 建立虚拟内存到物理内存的映射
  6. 进程可以直接通过指针访问文件内容

mmap的优势:

1. 减少数据拷贝:

  • 传统I/O:文件 → 内核缓冲区 → 用户缓冲区(两次拷贝)
  • mmap:文件 → 内核缓冲区(一次拷贝,用户直接访问内核缓冲区)
  • 避免了内核空间到用户空间的数据拷贝

2. 提高性能:

  • 大文件操作时性能提升明显
  • 利用操作系统的页面置换机制
  • 不需要频繁的系统调用
  • 适合随机访问

3. 多进程共享:

  • 多个进程可以映射同一个文件
  • 实现进程间共享内存
  • 比其他IPC方式更高效

4. 简化编程:

  • 像访问数组一样访问文件
  • 不需要管理缓冲区
  • 代码更简洁

mmap的类型:

1. 文件映射(File-backed Mapping):

  • 映射实际的文件
  • 修改会同步到文件(MAP_SHARED)
  • 用于文件I/O

2. 匿名映射(Anonymous Mapping):

  • 不映射文件,映射的是内存
  • 用于进程间共享内存
  • 父子进程可以共享

映射方式:

1. 共享映射(MAP_SHARED):

  • 多个进程共享同一块物理内存
  • 一个进程的修改对其他进程可见
  • 修改会写回文件(文件映射时)
  • 用于进程间通信

2. 私有映射(MAP_PRIVATE):

  • 写时复制(Copy-On-Write)
  • 修改不影响其他进程
  • 修改不会写回文件
  • 用于加载共享库

使用场景:

1. 大文件处理:

  • 数据库文件访问
  • 日志文件分析
  • 大文件搜索

2. 进程间通信:

  • 共享内存实现
  • 比消息队列、管道更快

3. 共享库加载:

  • 动态链接库的加载
  • 多个进程共享库代码

4. 内存分配:

  • malloc底层可能使用mmap
  • 大块内存分配

注意事项:

1. 文件大小:

  • 映射的文件大小必须是页大小的整数倍
  • 文件大小改变需要重新映射

2. 同步问题:

  • 需要使用msync()显式同步到磁盘
  • 否则修改可能延迟写入

3. 信号处理:

  • 访问已解除映射的区域会产生SIGSEGV
  • 访问超出文件大小的区域会产生SIGBUS

4. 性能考虑:

  • 小文件使用mmap可能不如read/write
  • 顺序访问可能不如read/write
  • 随机访问时mmap优势明显

与传统I/O对比:

  • 小文件、顺序读写:read/write更好
  • 大文件、随机访问:mmap更好
  • 需要共享:mmap更好
  • 简单应用:read/write更简单

9. Linux线程同步机制有哪些?各有什么特点?

答案:

Linux提供了多种线程同步机制,用于解决多线程并发访问共享资源的问题。

1. 互斥锁(Mutex):

特点:

  • 最基本的同步机制,用于保护临界区
  • 同一时刻只有一个线程可以持有锁
  • 其他线程必须等待锁被释放
  • 支持递归锁(同一线程可以多次加锁)

适用场景:

  • 保护共享数据
  • 临界区互斥访问
  • 最常用的同步机制

注意事项:

  • 必须成对使用lock和unlock
  • 避免死锁(多个锁时注意加锁顺序)
  • 持有锁的时间要尽量短
  • 不要在持有锁时调用可能阻塞的函数

2. 读写锁(RWLock):

特点:

  • 允许多个读者同时访问
  • 写者独占访问
  • 读写互斥,写写互斥,读读不互斥
  • 适合读多写少的场景

优势:

  • 提高并发度,多个读者可以同时读取
  • 比互斥锁更高效(读多写少时)

适用场景:

  • 配置信息读取
  • 缓存数据访问
  • 数据库查询

注意事项:

  • 写者可能饥饿(大量读者时)
  • 某些实现优先写者,某些优先读者
  • 开销比互斥锁大

3. 条件变量(Condition Variable):

特点:

  • 用于线程间的等待和通知
  • 必须配合互斥锁使用
  • 可以让线程等待某个条件成立
  • 支持单个唤醒和全部唤醒

工作原理:

  • 线程在条件不满足时调用wait,释放锁并进入等待
  • 其他线程改变条件后调用signal/broadcast唤醒等待线程
  • 被唤醒的线程重新获取锁并检查条件

适用场景:

  • 生产者-消费者模型
  • 线程池任务分发
  • 事件通知

注意事项:

  • 必须在持有锁的情况下调用wait
  • wait返回后需要重新检查条件(防止虚假唤醒)
  • 使用while循环而不是if检查条件

4. 信号量(Semaphore):

特点:

  • 计数器,表示可用资源的数量
  • P操作(wait):计数器减1,为0时阻塞
  • V操作(post):计数器加1,唤醒等待线程
  • 可以用于进程间同步

类型:

  • 二值信号量:类似互斥锁,只有0和1
  • 计数信号量:可以有多个资源

适用场景:

  • 资源池管理(如连接池)
  • 限制并发数量
  • 生产者-消费者(有界缓冲区)

与互斥锁的区别:

  • 信号量可以由不同线程释放
  • 互斥锁必须由加锁线程解锁
  • 信号量可以用于进程间同步

5. 自旋锁(Spinlock):

特点:

  • 忙等待,不会让出CPU
  • 适合锁持有时间非常短的场景
  • 避免了线程切换的开销
  • 在单核CPU上效率低

优势:

  • 没有线程切换开销
  • 适合多核CPU
  • 锁持有时间短时效率高

缺点:

  • 浪费CPU时间
  • 不适合长时间持有
  • 单核CPU上性能差

适用场景:

  • 内核代码
  • 临界区非常短(几条指令)
  • 多核系统

6. 屏障(Barrier):

特点:

  • 让多个线程在某个点同步
  • 所有线程都到达屏障点后才继续执行
  • 用于分阶段并行计算

适用场景:

  • 并行计算的阶段同步
  • 多线程初始化完成后再开始工作

选择建议:

性能考虑:

  • 临界区很短:自旋锁
  • 读多写少:读写锁
  • 一般情况:互斥锁
  • 等待条件:条件变量
  • 资源计数:信号量

使用原则:

  1. 优先使用高层抽象(如C++的std::mutex)
  2. 锁的粒度要合适,不要太大也不要太小
  3. 避免嵌套锁,防止死锁
  4. 使用RAII管理锁(自动释放)
  5. 尽量减少锁的持有时间

常见问题:

  • 死锁:多个锁的加锁顺序不一致
  • 活锁:线程不断重试但无法继续
  • 饥饿:某些线程长期得不到资源
  • 优先级反转:低优先级线程持有锁,高优先级线程等待

10. 如何查看和调试Linux进程?常用工具有哪些?

答案:

进程查看工具:

1. ps命令:

  • 查看进程快照
  • ps aux:显示所有进程详细信息
  • ps -ef:显示所有进程,包括父进程ID
  • ps -T -p <pid>:查看进程的所有线程
  • ps -o pid,ppid,cmd,%cpu,%mem:自定义输出格式

2. top/htop:

  • 实时监控进程
  • 显示CPU、内存使用率
  • 可以交互式操作(杀死进程、改变优先级)
  • htop更友好,支持鼠标操作

3. pstree:

  • 以树形结构显示进程
  • 清晰展示进程间的父子关系
  • pstree -p:显示PID

进程信息查看:

/proc文件系统:

  • /proc/<pid>/status:进程状态信息(PID、内存、线程数等)
  • /proc/<pid>/cmdline:启动命令行
  • /proc/<pid>/environ:环境变量
  • /proc/<pid>/maps:内存映射信息
  • /proc/<pid>/fd/:打开的文件描述符
  • /proc/<pid>/task/:线程信息
  • /proc/<pid>/stat:进程统计信息

调试工具:

1. gdb(GNU Debugger):

  • 功能最强大的调试器
  • 可以设置断点、单步执行、查看变量
  • gdb ./program:调试程序
  • gdb -p <pid>:附加到运行中的进程
  • 常用命令:break、run、continue、step、next、print、backtrace

2. strace:

  • 跟踪系统调用和信号
  • 查看程序与内核的交互
  • strace ./program:跟踪程序
  • strace -p <pid>:跟踪运行中的进程
  • strace -c:统计系统调用
  • strace -e open:只跟踪open调用
  • 用于诊断程序卡住、性能问题

3. ltrace:

  • 跟踪库函数调用
  • 类似strace,但跟踪的是库函数
  • ltrace ./program

4. lsof:

  • 列出打开的文件
  • lsof -p <pid>:查看进程打开的文件
  • lsof -i :8080:查看端口占用
  • lsof -u username:查看用户打开的文件
  • 用于诊断文件描述符泄漏、端口占用

性能分析工具:

1. perf:

  • Linux性能分析工具
  • CPU性能计数器、采样分析
  • perf record ./program:记录性能数据
  • perf report:查看分析报告
  • perf top:实时性能监控
  • 用于找出性能瓶颈

2. valgrind:

  • 内存调试和性能分析
  • valgrind --leak-check=full ./program:检测内存泄漏
  • valgrind --tool=callgrind ./program:性能分析
  • valgrind --tool=helgrind ./program:检测线程错误
  • 会显著降低程序运行速度

3. pidstat:

  • 监控进程资源使用
  • pidstat -p <pid> 1:每秒显示进程CPU使用
  • pidstat -r -p <pid>:显示内存使用
  • pidstat -d -p <pid>:显示I/O统计

4. pmap:

  • 查看进程内存映射
  • pmap <pid>:显示内存映射
  • pmap -x <pid>:显示详细信息
  • 用于分析内存使用

网络调试工具:

1. netstat/ss:

  • 查看网络连接
  • netstat -antp:查看TCP连接
  • ss -antp:更快的netstat替代品
  • ss -s:统计信息

2. tcpdump:

  • 抓包工具
  • tcpdump -i eth0 port 80:抓取80端口数据包
  • 用于网络问题诊断

实用技巧:

1. 查找占用CPU最高的进程:

ps aux --sort=-%cpu | head -n 10

2. 查找占用内存最高的进程:

ps aux --sort=-%mem | head -n 10

3. 查看进程打开的文件数:

ls /proc/<pid>/fd | wc -l

4. 查看进程线程数:

cat /proc/<pid>/status | grep Threads

5. 实时监控进程:

watch -n 1 'ps aux | grep process_name'

调试流程建议:

  1. 使用ps/top确定问题进程
  2. 使用strace查看系统调用,定位卡住的地方
  3. 使用lsof查看打开的文件和网络连接
  4. 使用gdb附加进程,查看调用栈
  5. 使用valgrind检测内存问题
  6. 使用perf分析性能瓶颈
C++面试总结 文章被收录于专栏

本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务