面试题型来源于网上收集的面经,答案仅供参考,如果错误,及时指正。
1. 进程间通信机制
1. 共享内存
2. 套接字
3. 消息队列
4. 管道
5. 信号量
6. 信号
2. 线程间同步手段
1. 互斥量
2. 条件变量
3. 读写锁
4. 信号量
5. 信号
3. 进程,线程和协程的区别
进程是程序运行的实体,是操作系统分配资源的基本单位。进程有自己独立的地址空间。线程是程序运行的一个执行流,是操作系统调度的基本单位,线程共享进程的地址空间,线程有独立的栈。协程是在用户态实现的线程,创建和调度都是在用户态运行,有自己的调度器,创建开销和调度开销要小于线程。
4. IO复用方式
Linux常见IO复用方式有三个:select、poll、epoll
5. 程序的内存模型
32位操作系统进程地址空间有4G,0~3G为用户空间,3~4G为内核空间。用户空间从低地址到高地址分别为:代码段、初始化数据段、未初始化数据段、堆段、栈段。
6. 程序的编译过程
编译四部曲:
1. 预处理,主要处理宏定义以及导入引用的头文件。
2. 编译,将预处理得到的文本文件翻译成汇编文件。
3. 汇编,将汇编文件翻译成机器语言文件
4. 链接,导入引用的动态库和静态库
7. gcc的编译参数有哪些
-l链接库,-L库路径,-S得到汇编文件,-E得到预处理文件。等等
8. select、poll、epoll的区别
Select底层采用数组存储文件描述符,存在以下四个问题:
1. 单进程监听的文件描述符最大数量存在限制,32位操作系统上是1024
2. 内核/用户态空间内存拷贝问题,select复制大量的句柄数据结构,产生巨大开销
3. select仅支持水平触发模式,存在重复触发风险
4. select返回后应用程序需要遍历整个数组,效率低。
Poll底层采用队列存储文件描述符,因此没有文件描述符数量限制,select其它三个缺点它也有。
Epoll解决select和poll的缺点,底层采用队列加红黑树方式实现,除了不受文件描述符数量限制之外,还支持边缘触发模式。
9. 函数参数入栈顺序
从右向左进栈
10. 堆栈溢出的一般原因
堆溢出一般是内存泄漏,例如手动申请内存忘记释放。栈溢出一般是递归调用层次太深。
11. 内存分配的三种方式
主要指内存分配的位置。
1. 静态分配,全局变量和局部静态变量属于静态分配,分配在程序的静态数据区。
2. 动态分配,手动调用malloc这种,在堆区分配。
3. 自动分配,系统自动分配在栈区,主要用于函数调用参数入栈和出栈。
12. fork和vfork的区别
Fork创建子进程,返回2个值,等于0表示子进程,大于0表示父进程。子进程共享父进程的地址空间以及继承打开的文件描述符,Linux采用写时复制技术,只有当父子进程尝试修改地址空间的时候,才会触发缺页中断处理程序执行真正拷贝。
Vfork和fork类似,也是采用写时复制技术,不过vfork保证子进程优先运行,直到子进程调用exec或者exit。
13. Fork创建子进程继承父进程打开的文件描述符,如何不让子进程继承
可以在打开文件的时候设置FD_CLOEXEC标志位,创建子进程的时候自动关闭文件。
14. 介绍僵尸进程和孤儿进程
僵尸进程指的是子进程退出后,父进程没有回收子进程的资源。僵尸进程过多导致系统资源不足。
孤儿进程指的是父进程退出,子进程变成孤儿进程,孤儿进程由init进程进行资源回收。
15. 如何解决僵尸进程过多问题
1. 可以在父进程的SIG_CHLD信号处理函数中回收子进程资源。
2. 设置父进程的SIG_CHLD信号处理函数位SIG_IGN,子进程会自动回收。
16. 如何查看操作系统共享内存,命令是啥
查看共享内存命令是ipcs,ipcrm
17. exit和_exit的区别
Exit内部调用_exit,区别在于exit会额外执行三个步骤:
1. 关闭打开的文件描述符
2. 刷新缓冲区
3. 执行注册的清理函数
18. 解释阻塞、非阻塞,同步和异步区别
阻塞指的是系统调用得不到满足,进程挂起。
非阻塞指的是系统调用得不到满足,程序立即返回。
同步和异步主要指的是IO复制操作是否同步进行。同步也包括阻塞和非阻塞。
19. 系统调用和库函数区别
系统调用是操作系统提供给应用程序的接口,库函数是应用程序的接口,库函数封装了大部分系统调用。
20. 调度类别和调度策略
Linux常用两种调度类,实时类和完全公平调度类。
实时类有两种调度策略,分别是先来先服务和时间片轮转:
1. 先来先服务(FIFO),谁先来谁先运行,直到进程自己退出、或者执行阻塞调用让出CPU、或者被更高优先级进程抢占。
2. 时间片轮转(RR),相同优先级的进程分配相同的时间片,采用队列方式组织,进程退出有如下几种原因,进程的时间片运行完、进程自身退出、进程执行阻塞调用让出CPU、进程被更高优先级进程抢占。
完全公平调度类(CFS)的实现基于红黑树,每个进程分配一个动态时间片,进程退出有如下几种方式:进程的时间片运行完、进程自身退出、进程执行阻塞调用让出CPU、进程被更高优先级进程抢占。
我们普通创建的进程就是CFS,操作系统提供了sched_setscheduler()接口用来修改进程的调度类和调度策略。
21. 惊群效应以及解决
一组睡眠线程等待同一个资源,当资源满足的时候,操作系统唤醒所有线程争抢资源,最终只有一个线程能够抢到,剩余线程继续睡眠。此种方式涉及大量系统调用开销,没有必要。解决方式是使用锁保护加队列方式,当资源满足的时候,只唤醒第一个线程即可。Linux条件变量的实现即采用了这种方式。
22. 死锁现象、必要条件以及解决方式
现象:线程A持有资源A,请求资源B,线程B持有资源B,请求资源A,双方互不释放拥有的资源,一直等待,此即死锁。
四个必要条件:互斥、请求与保持、不剥夺、循环等待。
解决方式:顺序请求资源。
23. 解释可重入函数
一个函数是可重入的,是指这个函数可以重复进入自身执行而不引起任何不良后果。
特点:不使用全局变量或者局部静态变量。不调用任何不可重入函数。
24. top命令各个参数意义,cache、buffer
Cache主要是文件缓存,buffer主要是磁盘缓存。
25. 查看磁盘、网络、虚拟内存的命令
Iostat查看磁盘。
Netstat查看网络
Vmstat查看虚拟内存
26. 介绍常用的Linux命令(awk, sed)
自称会shell不能不懂awk和sed。Awk主要用于列处理,sed用于行处理。此外,开发常用命令包括grep搜索,wc统计,tcpdump抓包等等。
27. 守护进程概念以及创建方式
守护进程不与任何终端关联,独自在后台运行。操作系统提供了daemon接口创建守护进程。还有一种手动创建方式,在《Linux环境编程:从应用到内核》书里有介绍。
28. 进程的七个状态
运行态、可中断的睡眠状态、不可中断的睡眠状态、停止态、跟踪态、僵尸态、死亡态。
29. 常见信号
SIG_CHLD,SIG_KILL, SIG_TERM, SIG_ALARM,SIG_USER1, SIG_USER2, SIG_PIPE, SIG_BUS等等,要知道自己列举的信号是干嘛的。
30. 内存泄漏怎么检查
Linux提供了两种工具可以检测内存泄漏,valgrind和mstrace,可以没用过,但是要知道。此外就是通过top观察内存占用。
31. Linux虚拟内存的优点
1. 提供进程独立地址空间。
2. 提供比物理内存更大的地址空间。
3. 还有很多,此处省略。
32. Linux内存管理机制
Linux采用段页机制管理内存,访问内存的时候先经过分段单元转换成线性地址,然后经过分页单元转换成物理地址。此外,使用伙伴管理算法和slab算法管理内存,这里要了解伙伴关系算法和slab算法都是做啥用的。
33. 如何实现一个内存池
先申请一块大内存,然后将大内存分割成小内存,使用队列串起来,每次申请内存直接去队列里面申请。(内存池的知识很重要,面试经常考,要懂得原理。原理和伙伴关系算法以及C++ allocator两级空间配置器类似,建议小伙伴阅读《STL源码解析》以及《深入理解Linux内核》相关章节)
34. 程序的运行流程
通常在Linux shell下执行一个程序流程这样:shell调用fork创建子进程,子进程执行exec类调用加载新程序上下文,加载完成后开始执行新程序。
35. 管道和有名管道的区别
无名管道只能用在有亲缘关系的进程之间,有名管道可以用在任意进程之间。这一点和无名信号量与有名信号量类似。
36. 软连接和硬链接的区别
简单说,软连接像一个快捷方式、硬链接相当于拷贝。硬链接只能在同一个文件系统上执行,且不能对目录执行。软连接没有这些限制。
37. Linux文件类型有哪些
7类。普通文件,目录文件,管道文件,套接字文件,链接文件,块设备文件,字符设备文件。
38. 为什么系统调用开销大于函数调用开销
两点。
1. 系统调用运行在内核态,用户调用运行在用户态,切换的时候要执行栈的切换以及上下文保持,可能导致硬件高速缓存失效。
2. 系统调用特权级比较高,需要执行一系列特权级检查和参数检查。
39. 信号量的实现
底层采用锁、计数器、队列实现。
40. 介绍Linux Swap分区
Linux系统下的swap分区即交换区,Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序。这些被释放的空间被临时保存到swap空间中,等到那些程序需要运行时,再从swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行swap交换。
41. 单核处理器是否需要加锁
需要加锁,当单线程访问临界区的时候,可能被操作系统调度出去,此时其它线程就可能访问临界区,因此,需要加锁保护。
42. 缺页置换算法
当访问一个内存中不存在的页,并且内存已满,则需要从内存中调出一个页或将数据发送至磁盘swap分区替换一个页,这种现象叫做缺页置换。
常见的缺页置换算法如下:
1. 先进先出算法(FIFO):置换最先调入内存的页面,即置换在内存中驻留时间最久的页面。按照进入内存的先后次序排列队列,从队尾进入,队首删除。
2. 最近最少使用(LRU)算法:置换最近一段时间以来最少使用过的页面,根据程序的局部性原理,刚被访问的页面,可能马上就要被访问。反之相反。
43. 可靠信号和不可靠信号
可靠和不可靠信号的区别在于前者不会丢失,后者会丢失。不可靠是1~31之间的信号,底层采用的是位图的方式实现,多个相同信号只会记录依次,因此会出现信号丢失。可靠信号底层采用队列的方式实现,收到信号则在队列里添加一个元素。但是,值得注意的是,可靠信号也不是完全可靠,因为队列的容量有限,超过一定限制也会丢失,这一点在《Linux环境编程:从应用到内核》中有详细讲过。
44. 进程状态转换图
45. 定时器实现方案
这个不一定考,我项目里面有用到定时器,所以考到了。Linux定时器的实现,网上有一篇非常好的讲解文章,建议阅读下,地址:IBM Developer
这里给出两种方式:
1. 基于链表和信号实现定时器。
2. 基于时间轮 (Timing-Wheel) 方式实现的定时器,知道时间轮的大致实现就好,原理蛮简单的。
46. 非递归锁的错误码
非递归锁会返回EDEADLK,递归锁非同一个加锁线程会返回EPERM.
47. 线程池的实现方案
线程池的基本构成包括工作队列,任务线程以及条件变量。任务线程空闲的时候就去工作队列取任务,没有任务则等待条件变量。这个网上有很多例子,了解就好。
48. 有了线程为啥还需要进程
1. 进程地址空间相互隔离,安全性高。线程之间一个挂掉,整个进程都会退出。
2. 进程地址空间有限,创建的线程数有限。
3. 进程是程序的一个实体,线程是程序的一个执行流。有很多不同的程序需要进程运行。
49. 设计无锁编程
参考CAS(compare and swap)设计。
50. 存在依赖关系的库之间编译顺序有关系吗?
有,存在依赖关系的库编译顺序是被依赖的放在后面。如果存在循环依赖的话,gcc有个编译选项xlinker可以解决此问题
51. 五种IO模式
阻塞、非阻塞、IO多路复用、异步IO、信号驱动
52. 系统上电到运行init进程之间做了什么
1. 运行上电POST自检程序。
2. 读取MBR主引导记录。
3. 加载bootloader最小引导系统。
4. 加载内核。
5. 执行init进程。
额外推荐参考书:
1. 《深入理解Linux内核》
2. 《Linux环境编程:从应用到内核》
3. 《深入理解计算机系统》
4. 《Linux环境高级编程》