Linux内核(八股文)

1.Linux内存管理 页表和虚拟地址

  • 每个进程访问的都是虚拟地址,而不是直接操作物理地址。

  • 每个进程的虚拟地址空间是独立的,不会影响其他进程。

  • 每个进程都有自己的页表,用来记录虚拟地址到物理地址的映射关系

  • CPU 通过页表找到物理地址,然后才能真正访问内存。

这样就实现了进程隔离内存保护,同时也能让物理内存更高效地管理和分配。

物理内存不够:

  • 使用 Swap(交换分区/交换文件),把部分内存数据写入磁盘。
  • 按需分配(Demand Paging),避免无用的内存分配。
  • 触发 OOM 机制,杀死占用过多资源的进程。


2. 每个进程的地址空间同样大吗

  • 地址空间大小理论上相同,但进程实际分配和使用的内存因程序不同而不同
  • 物理内存有限时,进程可能无法用满整个地址空间,会触发 swap 或 OOM 机制。

3. 多进程 多线程 线程池区别 使用场景举例

  • 多进程:稳定性高(一个崩了不影响其他),但进程间通信(IPC)复杂,开销大。
  • 多线程:稳定性低(一个线程崩溃可能影响整个进程),但数据共享和通信简单,切换速度快。
  • 线程池:管理多个线程,避免频繁创建销毁线程,提高效率,资源管理更高效。
需求选择方案
CPU 密集型计算(如图像处理、AI 计算)多进程
I/O 密集型(如爬虫、日志处理、Web 服务器)多线程/线程池
需要稳定性(崩溃不影响整体)多进程
需要高并发但任务较小线程池
需要大量数据共享多线程/线程池

如果你的任务是:

  • 计算量大(CPU 密集)多进程
  • 涉及大量 I/O 操作(I/O 密集)多线程/线程池
  • 大量短时任务线程池

进程:

1.进程号:PID  

ps -aux查看进程信息

进程的环境变量:env

export 

export LINUX_APP=123456 # 添加 LINUX_APP 环境变量

命令还可以添加一个新的环境变量或删除一个环境变量

使用"export -n LINUX_APP"命令则可以删除 LINUX_APP 环境变量
。可在运行时动态进行内存分配的一块区域,譬如使用 malloc() 分配的内存空间,就是从系统堆
内存中申请分配的。
内存管理
虚拟地址会通过硬件 MMU(内存管理单元)映射到实际的物理地址空间中,建立虚拟地址到物理地址的映射关系后,对虚拟地址的读写操作实际上就是对物理地址的读写操作。

进程的常见状态
状态描述
运行(Running)进程正在运行,或者正在准备运行(被 CPU 调度)
就绪(Ready)进程可执行,但 CPU 还未调度它
阻塞(Blocked)进程正在等待某个事件(如 I/O)
挂起(Stopped)进程暂停运行(如 Ctrl+Z),可用 SIGCONT 继续
终止(Terminated)进程已执行完成或被杀死(exit()kill

STAT 状态字段

  • R:运行状态(Running)
  • S:睡眠(Sleeping)
  • D:不可中断睡眠(I/O 等待)
  • Z:僵尸进程(Zombie)
  • T:暂停(Stopped)
进程和线程的区别
对比项进程(Process)线程(Thread)
定义资源分配的基本单位CPU 调度的基本单位
地址空间进程独立的地址空间线程共享进程的地址空间
资源进程有独立的内存、文件描述符等线程共享进程的资源
通信进程间通信(IPC)复杂,需要共享内存、管道等线程间通信简单,可直接共享变量
开销创建、切换成本高,需要分配独立资源创建、切换成本低,只需切换寄存器
稳定性一个进程崩溃不会影响其他进程一个线程崩溃可能导致整个进程崩溃
适用场景适用于大型独立任务(如 Web 服务器中的多个进程)适用于密集计算、并发任务(如 Web 服务器的多线程处理请求)

总结:进程更独立,安全性高但开销大;线程更轻量,效率高但共享资源需要注意同步。

fork()创建子进程
fork() 调用成功后,将会在父进程中返回子进程的 PID ,而在子进程中返回值是 0;如果调用失败,父进 程返回值 -1 ,不创建子进程,并设置 errno
子进程是父进程的一个副本,譬如子进程拷贝了父进程的数据段、堆、栈以及继承 了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储 空间的完全复制,执行 fork()之后,每个进程均可修改各自的栈数据以及堆段中的变量,而并不影响另一个 进程。
虽然子进程是父进程的一个副本,但是对于程序代码段(文本段)来说,两个进程执行相同的代码段, 因为代码段是只读的,也就是说父子进程共享代码段,在内存中只存在一份代码段数据。

init进程
进程号为 1 的进程便是所有进程的父进程,通常称为 init 进程,它是 Linux 系统启动之后运行的
第一个进程,它管理着系统上所有其它进程, init 进程是由内核启动,因此理论上说它没有父进程。 init 进程的 PID 总是为 1 ,它是所有子进程的父进程,一切从 1 开始、一切从 init 进程开始!

僵尸进程与孤儿进程

孤儿进程:父进程先于子进程结束

僵尸进程:父进程没有回收子进程的资源,子进程先于父进程结束。   “Z” zombie ,僵尸)
守护进程:
是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生.
守护进程 Daemon,通常简称为 d ,一般进程名后面带有 d 就表示它是一个守护进程。
"ps -ajx"查看系统所有的进程,
进程同步:
同步方式特点适用场景
互斥锁(Mutex)保证同时只有一个进程访问资源临界区保护
信号量(Semaphore)可用于多个进程同步或控制资源并发数量资源池控制(数据库连接池)
文件锁(File Lock)锁定文件,防止多个进程同时写入日志文件、数据库文件
管道(Pipe)进程间传递小量数据父子进程通信
共享内存(Shared Memory)最快的通信方式,多个进程共享数据高性能进程间通信
消息队列(Message Queue)进程安全的消息传递方式任务调度
信号(Signal)进程发送异步通知进程控制(kill、alarm)

进程之间的通信:

管道:

普通管道 pipe :通常有两种限制,一是单工,数据只能单向传输;二是只能在父子或者兄弟进程间 使用;
流管道 s_pipe :去除了普通管道的第一种限制,为半双工,可以双向传输;只能在父子或兄弟进程间使用;
有名管道 name_pipeFIFO
信号(Signal):

进程间发送简单的通知信号

例如:

1.譬如在终端上按下 CTRL + C 组合按键可以产生中断信号(SIGINT,通过这个方法可以终止在前台运行的进程;按下 CTRL + Z 组合按键可以产生 暂停信号(SIGCONT,通过这个方法可以暂停当前前台运行的进程。

2.过 kill 命令将信号发送给其它进程

消息队列:
消息队列是 UNIX 下不同进程之间实现共享资源的一种机制​​​​
套接字
Socket 是一种 IPC 方法,是基于网络的 IPC 方法,允许位于同一主机(计算机)或使用网络连接起来 的不同主机上的应用程序之间交换数据
共享内存
共享内存就是映射一段能被其它进程所访问的内存,这段共享内存由一个进程创建,但其它的多个进程 都可以访问,使得多个进程可以访问同一块内存空间。共享内存是最快的 IPC 方式,
通信方式特点适用场景
管道(Pipe)单向通信,适用于父子进程适合简单数据传输
命名管道(FIFO)可以跨无关进程,但仍然是单向通信适用于本机进程间通信
消息队列(Message Queue)内核维护队列,进程间发送结构化消息适用于多个进程的消息传递
共享内存(Shared Memory)速度最快,多个进程共享同一块内存,但需同步机制(如信号量)适用于大数据量、高速通信
信号量(Semaphore)控制进程同步,避免竞争适用于进程同步、互斥
信号(Signal)进程间发送简单的通知信号适用于异步事件通知
Socket(套接字)适用于本地进程和远程网络进程通信适用于分布式系统、网络通信

线程:

线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。
线程是程序最基本的运行单位。
线程不单独存在、而是包含在进程中;
线程是参与系统调度的基本单位;
并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
共享进程资源。同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程 都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址; 此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等。
gcc -o testApp testApp.c -lpthread
使用-l 选项指定链接库 pthread ,原因在于 pthread 不在 gcc 的默认链接库中,所以需要手动指定。
pthread_join():
          pthread_join() 获取其返回状态、回收线程资源
pthread_detach():
        不关心线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。

1. pthread_join()

  • 作用:阻塞等待线程执行完毕,并回收该线程的资源。
  • 特点
    • 调用 pthread_join() 的线程会阻塞,直到目标线程结束。
    • 线程结束后,其资源会被回收,可以获取线程的返回值
  • 适用场景需要等待线程执行完成,例如计算任务。

2. pthread_detach()

  • 作用:让线程自动释放资源不阻塞调用线程
  • 特点
    • 线程终止后,系统会自动回收资源,但无法 pthread_join() 获取返回值。
    • 适用于不关心线程返回状态的场景。
  • 适用场景:后台任务、守护线程,不需要等待其结束。

线程的通信方式:

  • 多线程通信主要依赖共享内存,但需要互斥锁、条件变量、信号量等同步手段。
  • 多进程通信(IPC)由于地址空间独立,可以使用管道、共享内存、信号量、消息队列、套接字等方式。
  • 多线程适用于高效任务并行,多进程适用于独立任务的隔离执行
通信方式适用于多线程适用于多进程说明
共享内存线程天然共享内存,多进程需要 shmget 等创建共享内存段。
互斥锁(Mutex)线程同步方式,防止数据竞争。
条件变量(Condition Variable)线程间等待某个条件满足。
信号量(Semaphore)可用于线程同步,也可用于进程同步。
管道(Pipe)进程间数据传输,线程不需要。
消息队列(Message Queue)进程间发送结构化数据。
套接字(Socket)进程间或跨机器通信。

线程同步:

互斥锁,读写锁,条件变量,自旋锁,条件变量,信号量。

读写锁:读多写少。

同步方式特点适用场景
互斥锁(Mutex)保证同一时刻只有一个线程访问资源线程间互斥访问共享数据
读写锁(RWLock)允许多个线程同时,但写操作互斥读多写少的场景(如数据库缓存)
自旋锁(Spinlock)线程忙等,适合短时间锁定轻量级锁,适用于高频访问
条件变量(Condition Variable)线程等待某个条件后再执行生产者-消费者模型
信号量(Semaphore)控制多个线程同时访问共享资源资源池控制(如连接池)

 用户态和内核态

方式触发原因示例
系统调用用户进程主动请求 OS 服务read(), write(), fork()
异常进程运行时出现错误缺页异常、除零错误
中断设备事件或外部信号键盘输入、网络数据

(1)用户态(User Mode)
  • 权限受限:只能访问自己的虚拟地址空间,不能直接操作硬件(CPU、内存、IO)。
  • 只能执行普通指令,不能执行特权指令(如 cli 关闭中断)。
  • 应用程序(如 Chrome、VS Code)都运行在用户态,通过系统调用与内核交互。
(2)内核态(Kernel Mode)
  • 权限最高:可以访问所有硬件资源(CPU、内存、IO 设备)。
  • 能执行特权指令,如内存管理、进程调度、文件系统操作。
  • 操作系统内核代码运行在内核态(Linux 内核、Windows 内核)。

 linux查看内存占用

命令用途
top实时监视进程的 CPU/内存占用
htoptop 的增强版,更直观
free -h查看系统整体内存使用情况
ps aux查看每个进程的内存占用
/proc/meminfo查看系统内存详细信息
vmstat 1 5监视内存、CPU、IO 使用情况
smem查看每个进程的真实物理内存(PSS)

进程和线程的调度

调度(Scheduling)就是操作系统决定哪个进程/线程获得 CPU 资源的过程。

对比项进程调度线程调度
调度单位进程(Process)线程(Thread)
上下文切换需要切换整个进程(包括虚拟地址空间、寄存器、页表等)只需要切换线程的寄存器,不需要切换地址空间
调度粒度较大,进程开销大较小,线程切换更快
调度控制操作系统内核负责内核调度,部分语言(如 Java)支持用户态调度
开销(进程切换涉及内存管理)(线程切换更快)
适用场景多进程适用于隔离性要求高的任务,如浏览器多标签页多线程适用于高并发计算,如 Web 服务器

select vs. epoll 实现机制对比

对比项select(轮询)epoll(事件驱动)
监听方式遍历整个 fd_set,逐个检查基于回调事件通知,触发时才返回
开销,每次调用都要拷贝 fd_set 并遍历所有 fd,仅在 fd 变化时内核才通知
性能O(n)fd 多时效率极低O(1) 或 O(log n),适用于高并发
fd 限制默认 1024,可修改几乎无限制(受内存影响)
支持触发方式仅 LT(水平触发)支持 LT 和 ET(边缘触发)
适用场景小规模并发高并发服务器,如 Nginx

LT 与 ET 的区别总结

**ET(边缘触发)**模式确实只在 事件发生的瞬间 执行
特性LT(水平触发)ET(边缘触发)
触发条件当文件描述符的状态满足条件时,持续触发只在文件描述符的状态发生变化时触发(例如:从不可读到可读)
通知次数持续通知,只要文件描述符的状态不改变一次通知,文件描述符的状态变化后,若不处理数据,不会再次通知
处理要求只需按需读取数据,不需要处理完所有数据必须一次性处理所有数据,否则不会再收到通知
性能较低效,可能会多次通知同一个文件描述符更高效,只通知状态改变的 fd,避免无谓的通知

线程的生命周期

C++11 中的线程生命周期管理主要依靠 std::thread 类来实现。每个线程从创建到执行到结束,都需要正确地管理其生命周期:

  • 创建线程:使用 std::thread 对象。
  • 结束线程:使用 join()detach()
  • 清理线程资源:确保线程被正确地回收,否则可能导致资源泄漏。

通过合理使用 join()detach(),可以确保程序在并发执行时的稳定性与效率。

特性TCPUDP
连接方式面向连接,建立连接(三次握手)无连接,直接发送数据
可靠性提供可靠性保证(数据完整性、重传、顺序保证)不保证可靠性(可能丢包、乱序)
传输速度速度较慢(由于控制机制、确认、重传等)速度较快(没有过多控制和确认)
开销高(需要连接管理、控制信息)低(仅数据和少量控制信息)
流量控制与拥塞控制提供流量控制和拥塞控制,避免网络过载不提供流量控制和拥塞控制
顺序保证保证顺序,接收方按顺序接收数据不保证顺序,接收方可能乱序接收数据
应用场景文件传输、电子邮件、网页浏览等需要可靠传输的应用实时通信、视频流、在线游戏、DNS 查询等对速度要求高、容忍丢包的应用

视频通话需要进行 实时数据传输,主要涉及语音和视频流的即时传输。如果数据传输的延迟过高,用户会感到 卡顿 或者声音与画面不同步,极大影响通话体验。因此,在视频通话中,传输的延迟是一个非常重要的考虑因素。

  • TCP 在传输过程中有很多 可靠性控制重传机制,这虽然确保了数据的完整性,但在出现丢包或网络延迟时,TCP 会阻塞等待确认和重传,从而增加了延迟。

  • UDP 不进行任何确认、重传或顺序控制,其数据包一旦发送就会尽快到达接收方,即使数据丢失,也不会阻塞数据的传输。这种方式有助于减少传输的延迟,使得视频和语音流更加流畅。

UDP 的低延迟与高效性

  • 无需建立连接:与 TCP 不同,UDP 是一个 无连接协议,这意味着在数据传输前不需要建立三次握手连接。TCP 的握手过程会增加时间延迟,而 UDP 可以直接发送数据,减少了连接建立的时间。

  • 较低的协议开销:UDP 协议头部较小,只有 8 字节,而 TCP 的头部最小为 20 字节,这意味着 UDP 在数据传输上具有更低的开销,这对于高频数据(如视频帧、音频数据)至关重要。

  • 丢包容忍性:在视频通话中,偶尔丢失几个数据包不会对体验产生太大影响。视频流和音频流具有 冗余性,丢失某些数据包时,画面或声音会稍微变差,但不至于造成严重影响。UDP 的设计正是为了容忍丢包,因此适合实时性要求较高的应用。

I/O

1.1 阻塞I/O
阻塞I/O是指在进行I/O操作时,如果数据没有准备好,进程会被挂起(阻塞),直到数据准备好为止。比如,当程序调用read()函数从文件中读取数据时,如果文件中没有数据,程序会被挂起,直到有数据可读。

1.2 非阻塞I/O
非阻塞I/O是指在进行I/O操作时,如果数据没有准备好,进程不会被挂起,而是直接返回一个错误(例如EAGAIN或者EWOULDBLOCK),告知程序数据没有准备好。程序可以选择稍后重试,或进行其他任务。

1.3 I/O复用(select和poll)
I/O复用是一种机制,允许单个进程能够同时监听多个I/O流(如多个文件描述符、网络连接等),当其中一个或多个I/O流准备好时,进程可以执行相应的操作。通过I/O复用,程序不需要为每个I/O流创建一个单独的线程或进程,从而提高了资源的使用效率。

常见的I/O复用方式包括:

  • selectselect函数允许进程等待多个I/O事件的发生,并在事件发生时返回,告诉进程哪些文件描述符已经准备好进行读、写或者异常处理。

  • pollpollselect类似,也是用于等待多个I/O事件。不同之处在于poll支持更大的文件描述符集合,并且没有select的最大文件描述符限制。poll使用一个pollfd结构来描述文件描述符的状态。

### 关于Linux嵌入式内核驱动的常见面试问题及最佳实践 #### 1. 多任务处理机制的区别 在讨论Linux嵌入式系统的多任务处理时,理解不同调度单位之间的差异至关重要。FreeRTOS 和 Linux 都支持多任务并发执行,允许多个任务(或进程/线程)在CPU上交替执行[^1]。然而,在更细粒度的任务划分方面,Linux中的进程和线程具有不同的特性:进程有独立的堆区和栈区;相比之下,线程共享同一进程内的堆空间但各自保留单独的栈区域。这种设计使得在同一进程中创建多个线程能够减少资源开销并提高通信效率。 #### 2. 协程的概念及其优势 除了传统的进程和线程模型外,现代编程实践中还引入了协程这一概念。作为一种用户态下的轻量级线程形式,协程允许开发者在一个用户线程之上运行若干个协作式的子程序实例。由于其不在操作系统层面进行上下文切换,因此能显著提升单核心处理器上的并发性能,并简化异步逻辑的设计与实现[^2]。 #### 3. 堆栈的工作原理及其应用场景 对于任何涉及函数调用或者递归运算的操作而言,了解底层使用的数据结构——即所谓的“堆栈”,是非常重要的。作为典型的后进先出(LIFO)存储器抽象层,它不仅负责记录每次方法调用后的返回位置信息,还在诸如表达式解析等领域发挥着不可或缺的作用[^3]。当编写设备驱动或其他低级别代码时,熟悉这些基础知识有助于更好地管理和优化内存分配策略。 #### 4. 设备文件系统与字符设备注册流程 针对具体的硬件接口开发工作,则需掌握如何通过`register_chrdev()` API向VFS(虚拟文件系统)注册新的字符型节点对象。此过程通常涉及到定义相应的file_operations表来描述特定I/O行为模式,以及设置必要的权限位掩码以确保安全访问控制。 #### 5. 中断服务例程(ISR)的设计原则 为了响应外部事件触发信号源的变化情况,中断处理机制成为连接物理世界同软件环境之间的重要桥梁之一。一个好的ISR应该尽可能简洁高效地完成预定动作而不干扰正常业务流;同时考虑到实时性的需求,有时还需要借助下半部(deferred work)技术进一步延展复杂计算至稍后时刻再做处理。 ```c static irqreturn_t my_interrupt_handler(int irq, void *dev_id) { // 执行快速且必要的操作... return IRQ_HANDLED; } ``` #### 6. 同步原语的选择依据 面对竞争条件(race condition),死锁(deadlock)等问题频发场景下,合理选用合适的同步工具显得尤为关键。无论是自旋锁(spinlock),读写屏障(mutex),还是信号量(semaphore),每种方式都有各自的适用范围及优缺点所在,应根据实际应用背景灵活判断采用何种手段最为恰当有效。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值