一、内核有哪几种锁或者同步机制
自旋锁(Spinlock)
自旋锁是一种简单的锁机制,适用于短时间锁定的情况。如果一个线程获取了自旋锁,其他试图获取同一锁的线程将会不停地循环检查锁是否可用(即“自旋”),直到锁被释放。
优点是实现简单且在无竞争情况下开销小;缺点是在高竞争情况下会导致CPU空转,浪费资源。
读写锁(Read-write Lock, RWLock)
读写锁允许多个读者同时访问资源,但当有写者时,则不允许任何其他读或写的操作。它区分了读和写两种模式,从而提高了并发性。
这种锁适合于读多写少的场景,可以提高系统的吞吐量。
互斥锁(Mutex)
互斥锁用于保证同一时间只有一个线程能够进入临界区,即对特定资源进行独占访问。
相较于自旋锁,如果一个线程不能立即获取到互斥锁,它会进入睡眠状态而不是持续循环,因此更节省CPU资源。
信号量(Semaphore)
信号量分为二进制信号量和计数信号量。二进制信号量类似于互斥锁,而计数信号量则允许多个单位的资源被不同线程持有。
信号量不仅可以用来保护临界区,还可以用于进程间通信和同步。
顺序锁(Seqlock)
序列锁是一种乐观锁,主要用于读-改-写类型的原子操作。它通过版本号来追踪数据的变化,读者可以在没有锁的情况下读取数据,并检查版本号以确认数据的一致性。
特别适合于读操作远多于写操作的情形,如时间戳更新等。
RCU(Read-Copy Update)
RCU 是一种特殊的同步机制,它允许读者在几乎不受限制的情况下遍历数据结构,而更新者则创建数据的新副本并最终替换旧副本。
RCU 非常适合那些读操作远远超过写操作的数据结构,例如路由表、文件系统元数据等。
完成量(Completion)
完成量是一种同步工具,通常用于等待某个事件的发生。它不是一种锁,而是用于通知机制,表示某些初始化或者任务已经完成。
屏障(Barrier)
内存屏障(Memory Barrier)确保指令按照指定的顺序执行,并且所有之前的操作都已完成,这对于多处理器系统中的内存一致性非常重要。
I/O屏障用于确保I/O操作按顺序执行。
二、linux中用户模式和内核模式
用户模式:一种受限模式,只能访问自己的地址空间,不能直接访问硬件和执行特权指令
内核模式:内核模式拥有对所有硬件资源和系统功能的完全控制权。只有经过特殊设计并编译进内核的代码才能在此模式下执行。
进入内核模式方式:系统调用,异常/中断
从arm架构寄存器来讲,armv7看cpsr寄存器,armv8看el寄存器,其中 EL0 是用户模式,而 EL1 或更高则是特权模式。
三、linux内核怎么申请大块内存
1、vmalloc()用于申请大块内存,线性地址连续,物理地址不连续,不能直接用于dma,释放函数vfree()
2、kmalloc用于分配小块内存,最大32*page-16,线性地址,物理地址连续,可用于dma,kfree()释放
四、直接与伙伴分配器交互的内核接口
alloc_pages/alloc_pages_node/__get_free_page/____get_free_pages
五、直接与slab分配器交互的内核接口
kmem_cache_create/kmem_cache_alloc()
六、linux内核空间和用户空间如何划分
32位系统
虚拟地址上:高1G地址,0xc0000000-0xffffffff供内核使用,低3G 0x00000000-0xbfffffff供用户空间使用,
64位系统:
虚拟地址:实际用户空间和内核空间只占用了48位,内核空间0xffff_8000_0000_0000- 0xffff_ffff_ffff_ffff,用户空间0x0000_0000_0000_0000- 0x0000_7fff_ffff_ffff
七、使能mmu的系统中,内核程序和用户程序分别运行在虚拟地址模式还是物理地址模式
都运行在虚拟地址模式,没有使能mmu的系统是不区分内核空间和用户空间的,区分也只是形式上区分,uclinux与linux区别就在于内存管理,uclinux针对没有mmu单元的soc,不能使用虚拟内存管理技术
八、arm通过几级页表进行存储空间映射
两级页表
九、中断延迟处理机制有哪几种
软中断,任务队列tasklet,工作队列,中断线程化
十、为什么自旋锁临界区不允许睡眠
1、首先,并不是睡眠了一定会出问题,而是在spinlock中睡眠是一个坏主意:可能有死锁的风险
例:睡眠时调度了的进程此时正好也去spinlock同一个锁,此时将锁住cpu
2、假如对自旋锁的竞争发生在了真正并发执行的两条路径上,一旦持锁的执行路径执行时间过长,甚至被换出处理器,另一个自旋等待的执行路径就要等待更长时间,尤其是在中断上下文等待过长时间甚至卡住对系统的稳定性是极大的破坏
像这种用在中断函数的自旋锁,作为自旋锁的确定规则临界区必须时原子操作,不能睡眠。
十一、spin_lock与spin_lock_irqsave
如果中断中也有用spin_lock,则在非中断进程中调用spin_lock_irqsave,防止中断死锁
十二、为什么自旋锁要关抢占
1、防止死锁:如果一个进程已经持有锁,这时被高优先级进程抢占,高优先级得不到锁而一直等待
2、提高性能:减少上下文切换开销
十三、硬件中断号和Linux内核的IRQ号它们是如何映射的
虚拟irq:virq代表内核中唯一的中断标识符。该 VIRQ 已经通过 irq_alloc_desc() 或类似函数预先分配,
硬件irq:硬件中断号(HWIRQ),由具体的硬件平台定义,表示来自物理中断源的中断编号
domain:指向 irq_domain 结构体的指针,表示当前处理的中断域。每个 irq_domain 对象通常对应一个特定的中断控制器或一组相关的中断线
两者通过domain数据结构关联:domain->linear_revmap[hwirq] = irq_data->irq
十四、为什么说中断上下文不能执行睡眠操作
所谓的睡眠,就是调用 schedule 让出cpu,调度器选择另外个进程继续执行,这个过程涉及进程栈空间的切换。
1、如果睡眠调用schedule让出cpu,内核中没有替中断保存task_struct之类的结构题,这就导致再次循环调度时,再也无法返回中断上下文中。
2、中断上下文处于关中断中,需要发送个 EOI 通知 GIC 中断处理结束,GIC 和CPUinterface 才会进入下一次中断处理。如果中途 schedule,那么整个系统的中断都会被屏蔽掉,没法进入中断了。
十五、软中断,tasklet能不能执行睡眠操作
软中断是打开中断但是关抢占的,软中断有可能在中断上下文或者softirqd进程上下文中执行的,所以如果有睡眠,第一在中断上下文睡眠无法返回了,第二关抢占情况下睡眠如果调度的进程有同样的锁会死锁,第三性能也不符合要求,软中断一般要求时间执行尽可能短
十六、工作队列是运行在中断上下文,还是进程上下文?它的回调函数是否允许睡眠?
工作队列运行在进程上下文,所以它的回调函数是运行睡眠的
十七、Linux软中断和工作队列作用是什么?
Linux 内核中的软中断(softirq)和工作队列(workqueue)是两种用于处理下半部(bottom half)任务的机制,它们各自有不同的特点和适用场景。
软中断 (SoftIRQ)
- 快速响应:软中断设计用来快速处理那些不能在硬中断上下文中完成的任务。这些任务通常是时间敏感的,但又不适合直接放在硬中断处理程序中执行,因为硬中断需要尽可能快地返回以确保系统的实时响应性。
- 高效处理:软中断非常适合处理高频率、低延迟的任务,比如网络数据包处理或定时器事件。它们运行在中断上下文中,因此不允许睡眠,并且必须非常高效。
- 不可重入:每个软中断向量在同一时刻只能在一个 CPU 上运行,保证了任务执行的一致性和避免了潜在的竞态条件。
- 优先级:软中断有明确的优先级顺序,这允许内核根据任务的重要性来决定先执行哪个软中断。
- 应用场景:软中断常用于实现网络协议栈、设备驱动程序等需要高效、及时响应的任务。
工作队列 (Workqueue)
- 复杂任务处理:工作队列适合处理那些可能需要较长时间运行或者涉及复杂逻辑的任务。由于它们运行在进程上下文中,所以可以安全地进行可能导致阻塞的操作,如等待 I/O 或者分配内存。
- 允许睡眠:与软中断不同,工作队列中的任务可以在执行过程中睡眠,这意味着它们可以等待某些条件满足后再继续执行,这使得工作队列非常适合处理复杂的、长时间运行的任务。
- 异步任务调度:工作队列提供了一种将任务从一个上下文推迟到另一个上下文的方法,从而避免在不允许睡眠的地方执行可能引起睡眠的操作。例如,从中断上下文中提交任务给工作队列,让这些任务在稍后的时间点被执行。
- 并发控制:工作队列可以是单线程或多线程的,多线程工作队列允许多个工作项并发执行,而单线程工作队列则确保所有提交的任务按照提交顺序串行化执行。
- 应用场景:工作队列广泛应用于文件系统操作、用户空间通信、定时任务等需要处理复杂逻辑或长时间运行的任务场景。
十八.中断现场保存在什么地方?
中断处理程序会将通用寄存器,sp, lr, pc, pstate保存到内核的栈中
十九.什么是中断现场?中断现场中需要保存哪些内容?
中断处理程序会将通用寄存器,sp, lr, pc, pstate保存到内核的栈中
struct pt_regs {
union {
struct user_pt_regs user_regs;
struct {
u64 regs[31];
u64 sp;
u64 pc;
u64 pstate;
};
};
u64 orig_x0;
#ifdef __AARCH64EB__
u32 unused2;
s32 syscallno;
#else
s32 syscallno;
u32 unused2;
#endif
u64 orig_addr_limit;
u64 unused; // maintain 16 byte alignment
u64 stackframe[2];
};
二十、多个cpu能同时执行同一类型的软中断/tasklet吗
二十一、Linux通过什么样方式实现系统调用
通过svc产生同步异常实现进入内核模式(异常级别el1)
二十二、如果多个work挂入一个工作线程当中执行,当某个work的回调函数执行了阻塞操作时,那么剩下的work怎么办?
kthread_worker_fn会循环调度加入到worker的work,一个work阻塞,其他的也被阻塞
二十三、Linux操作系统的ARM系统把bootloader烧录进去后,通电后串口没有任何输出反应,此时你应该去检查软件和硬件?
1、串口工具本身设置有无问题
2、串口usb工具是否有问题
3、系统板是否上电正常
4、可是尝试下一个正常的进去看看是否整个正常
5、最后这些都验证ok,那么可能是bootloader烧入或者bootloader本身有问题
二十四、Linux中为什么要把中断分为上半部分和下半部分?MMU和Cache基础原理?
Linux中的上半部与下半部
Linux内核将中断处理分为两部分是为了提高系统性能和响应性。上半部指的是立即执行的关键代码,这部分代码尽可能快地完成,通常只做最小的工作,比如读取硬件状态或确认中断。因为上半部是在中断上下文中运行的,所以不能睡眠或等待事件的发生。
下半部则是在稍后某个时间点执行的非关键代码,它可以在进程上下文中运行,因此可以进行更复杂的操作,包括睡眠、调度等。这样做的好处是可以让系统尽快从硬中断中返回,减少延迟,并且可以更好地利用多核处理器的能力。
MMU和Cache基础原理
MMU(Memory Management Unit):MMU是现代计算机体系结构的一部分,用于实现虚拟内存到物理内存的映射。它使得每个进程都有自己的地址空间,提高了安全性和稳定性。MMU还支持内存保护和页面共享等功能。
Cache:Cache是一种高速缓冲存储器,位于CPU和主内存之间。它的目的是加速数据访问速度。CPU首先检查所需的数据是否存在于缓存中(称为命中),如果存在,则直接从缓存读取;如果不存在(称为未命中),则需要从较慢的主存中加载数据到缓存中。为了保持一致,Cache还需要解决一致性问题,即确保缓存中的数据与主存中的副本同步。
在Linux操作系统中,当涉及到MMU和Cache时,操作系统必须小心管理它们,以保证正确的内存映射和有效的缓存策略,同时避免可能导致系统不稳定的问题,如缓存不一致或非法内存访问。
二十五、嵌入式的Linux操作系统通电开始到系统起来的主要过程是什么?
第一:bootloader阶段
主要完成第一阶段系统的引导和初始化工作。
系统上电后,首先是cpu开始工作,此时引导程序(通常是汇编代码)从ROM被加载到RAM 中,对 cpu 的寄存器进行初始化,启动核 0,如果是核1或者其他核则处于休眠状态,等待核 0 在内核启动正常后通过中断或者事件将其唤醒(图中10的位置)。
接下来就是完成 flash 和 ddr 初始化,为后面从 flash 加载 bin 文件,在 DDR 中运行程序提供基本环境,一般情况下都是从flash为0的位置,将flash中的bootloader 镜像文件加载到 RAM 或 DDR 中,该引导程序最终将uboot从flash 复制到 DDR 中,然后交接给 uboot 进行下一步处理。
第二:uboot 阶段
首先对 dram,flash,串口进行检查,保障 uboot 的运行环境是 ok 的,同时为 linux分配内存空间;接下来初始化网络配置,包括mac地址的设置、通信端口的工作模式设置,可以让用户通过网络加载程序;最后将linux从flash中复制到内存中。
linux 镜像文件中包含内核、文件系统和设备管理树 (FDT) 三个基本组件,uboot 会根据设备管理树定义的信息进行检查,然后依次加载到内存中 。包括序号4~5,这个阶段最后将 linux 加载后,就可以交接给内核进行操作。
第三:内核启动阶段
主要负责创建进程、绑定进程、划分内存、唤醒核1、加载文件系统等基本工作。
首先,会生成一个初始进程 init0,后续创建的所有进程都是从该进程中产生,该进程还负责对内存和虚拟内存进行划分。
然后 vfork 出一个主核进程,绑定到核 0 上,接下来由 cpu 主核(核0)唤醒其他的核,从 init 进程 vfork 出副核进程,并将该进程绑定到核1上。
最后是基于设备管理树完成对系统中其他设备的初始化。
二十六、Linux驱动当中字符设备和块设备区别?
字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。
1、字节为单位
2、无需缓冲读写
3、只能顺序读写
块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。
1、以块为单位进行数据输入输出
2、对I/0请求有对应的缓冲区
3、可以随机访问
4、工作在vfs之下
二十七、与spinlock相比,信号量有哪些特点?
1、信号量阻塞睡眠,2、可能导致上下文切换导致要额外开销,3、信号量一般不用于中断
4、信号量更可能由于低优先级进程持有锁导致高优先级进程等待而导致优先级反转
二十八、描述信号量是如何实现的?
二十九、Linux内核已经实现信号量机制,为什么还要单独设置一个互斥锁机制?
1、语义清晰
2、性能优化
3、支持优先级继承,拿到锁的任务有高优先级等待则会提高该优先级
三十、RCU相比读写锁有哪些优势?
1、rcu读锁不需要获取锁
2、写操作会在后台逐步完成,更少的延迟阻塞
#include <linux/rcupdate.h>
#include <linux/slab.h>struct node {
struct node *next;
int data;
};struct list_head {
struct node *head;
};// 添加节点到链表尾部(写操作)
void rcu_list_add(struct list_head *list, struct node *new_node) {
new_node->next = NULL;
struct node **last = &list->head;// 遍历找到链表尾部
while (*last != NULL)
last = &(*last)->next;// 安全地更新链表尾部指针
rcu_assign_pointer(*last, new_node);
}// 删除节点(写操作)
void rcu_list_delete(struct list_head *list, struct node *old_node) {
struct node **pprev = &list->head;// 找到要删除的节点
while (*pprev && *pprev != old_node)
pprev = &(*pprev)->next;if (*pprev == old_node) {
struct node *next = old_node->next;// 安全地更新前驱节点的next指针
rcu_assign_pointer(*pprev, next);// 注册回调函数来释放旧节点
call_rcu(&old_node->rcu, free_node_callback);
}
}// 读取链表中的数据(读操作)
void rcu_list_traverse(const struct list_head *list) {
struct node *node;rcu_read_lock();
for (node = rcu_dereference(list->head); node; node = rcu_dereference(node->next)) {
// 处理节点数据...
}
rcu_read_unlock();
}// 回调函数用于释放旧节点
static void free_node_callback(struct rcu_head *head) {
struct node *node = container_of(head, struct node, rcu);
kfree(node);
}
三十一、U-boot启动时重定位是如何实现?
1.rom soc厂商代码加载一部分uboot代码到ram中执行
2. 准备环境
在重定位之前,U-Boot 需要完成一些基本的硬件初始化工作,例如设置 SDRAM 控制器、配置 MMU(如果有的话)、初始化定时器等。这些步骤确保了有足够的 RAM 可用,并且系统能够稳定地运行。
3. 计算目标地址
U-Boot 确定了最终想要运行的位置,通常是 SDRAM 的起始地址或者某个特定的偏移量。这个地址由编译时定义的宏或配置项决定,也可以通过命令行参数动态指定。
4. 复制自身
一旦准备工作完成,U-Boot 将自己从当前位置(如 Flash)复制到计算出的目标地址(RAM)。这一步骤涉及到逐字节地读取 U-Boot 的代码段和数据段,并将其写入新的位置。注意,某些架构可能还需要处理不同内存区域之间的端序转换等问题。
5. 更新全局指针(Global Data Pointer, gd)
在 ARM 架构上,U-Boot 使用一个名为 gd 的全局指针来指向包含全局变量的数据结构。这个指针在重定位前后必须被正确更新,以反映新的基地址。否则,所有依赖于 gd 的操作都将失败。
6. 修正相对地址引用
U-Boot 中可能存在许多基于初始加载地址的绝对或相对地址引用。为了使这些引用在新的位置仍然有效,必须对它们进行适当的调整。这包括但不限于修复函数指针、跳转表、常量字符串以及其他任何形式的静态链接信息。
7. 跳转到新位置
最后,U-Boot 通过修改程序计数器(PC)来跳转到新位置继续执行。此时,所有的符号都已经更新为相对于新地址的有效值,U-Boot 可以正常运行在更快的 RAM 上面。
三十二、如何分析一个oops错误日志?
1. 识别基本元数据
首先查看日志顶部的基本信息,包括:
时间戳:发生错误的时间。
内核版本:正在运行的内核版本号。
架构信息:例如 x86_64, ARM 等。
CPU 和进程信息:哪个 CPU 核心上发生了错误,以及涉及的具体进程 ID (PID) 和线程 ID (TID)。
2. 查找错误类型
Oops 日志通常会指出具体的错误类型,例如:
NULL pointer dereference
Array index out of bounds
Division by zero
Invalid memory access
了解错误类型可以帮助你更快地定位问题。
3. 分析寄存器状态
Oops 日志中包含了大量的寄存器内容,如程序计数器 (PC),栈指针 (SP),链接寄存器 (LR) 等。这些值对于理解内核在崩溃时的状态非常重要。特别注意:
EIP/PC:指向导致错误的指令地址。
ESP/SP:当前的栈指针位置。
EBP/RBP:基址指针,用于跟踪函数调用链。
4. 解析回溯(Backtrace)
Oops 日志通常包含了一个回溯(Backtrace),显示了导致错误的函数调用序列。这个部分是解决问题的关键,因为它揭示了错误发生的路径。你可以使用以下工具来解析符号化的地址:
addr2line:将内存地址转换为源代码行号。
objdump:反汇编二进制文件,帮助理解具体指令。
gdb:加载内核镜像并使用 info line *address 来查找对应的源代码位置。
5. 检查模块信息
如果错误发生在某个内核模块中,日志可能会列出加载的模块及其地址范围。这有助于确定是否是第三方驱动或模块引起的错误。
6. 利用调试工具
现代 Linux 发行版提供了多种调试工具,可以帮助进一步分析 Oops 日志:
kdump:配置 kdump 可以捕获内核崩溃后的内存转储,供后续分析。
crash 工具:结合 vmcore 文件进行更深入的分析。
SystemTap 或 ftrace:用于动态跟踪和监控内核行为。
三十三、如何在内核代码中添加一个跟踪点?
1、在头文件里使用 TRACE_EVENT 宏来定义跟踪点,参数包括跟踪名,参数列表,存储事件中的字段,给字段赋值,打印参数列表
2、在需要跟踪的地方调用跟踪点
3、用perf recode -e 跟踪事件 sleep 时长或“/sys/kernel/debug/tracing/events/”去查看调试信息
三十四、什么是printk输出等级?具体有哪些输出等级?
以下是 printk 支持的具体日志等级,按照严重性从高到低排列:
KERN_EMERG (0) - 紧急:
系统不可用。这是最高优先级的消息,通常意味着系统即将崩溃或者已经处于非常严重的状态。
KERN_ALERT (1) - 警报:
必须立即采取行动。这类消息表明出现了需要立刻处理的问题,如硬件故障等。
KERN_CRIT (2) - 临界:
临界条件。指出了可能影响系统稳定性的关键问题,但还不至于导致系统完全无法工作。
KERN_ERR (3) - 错误:
错误条件。报告错误情况,例如设备驱动程序未能正确初始化。
KERN_WARNING (4) - 警告:
警告条件。虽然不是致命的错误,但是需要注意并可能需要修正的问题。
KERN_NOTICE (5) - 注意:
正常但重要的条件。提供一些需要注意的信息,但对于大多数用户来说,这并不是一个问题。
KERN_INFO (6) - 提示:
信息性消息。用于提供一般性的信息,比如模块加载或卸载时的通知。
KERN_DEBUG (7) - 调试:
调试级别的消息。主要用于开发和调试目的,包含详细的内部操作信息。
三十五、同一类型软中断是否允许多个CPU并行执行?
可以在多个cpu上并行,tasklet是用的同一个软中断,同一类型的tasklet是串行的
三十六、请说明内核使用内存屏障的场景?
1. 同步多处理器系统中的内存访问,防止优化造成指令顺序重排导致不是预期结果
2、内存屏障可以确保 CPU 在启动 DMA 操作之前已经完成了所有必要的内存准备,并且在 DMA 完成后,CPU 能够看到最新的数据。
三十七、ARM64处理器架构当中,如何实现独占访问内存?
独占访问的工作原理
加载独占 (LDXR):从指定地址加载数据到寄存器,并标记该地址为“正在被独占访问”。如果同一地址再次被其他核心使用 LDXR 指令加载,则之前的独占状态会被清除。
存储独占 (STXR):尝试将寄存器中的值存储回指定地址。如果自从上次 LDXR 之后没有其他核心对该地址执行过任何访问,则返回成功并完成存储;否则返回失败且不执行存储。
清理独占标志 (CLREX):显式地清除当前核心上的所有独占标记,通常用于异常处理路径。
内存屏障:为了保证指令的顺序性和可见性,通常需要配合使用内存屏障指令,如 DMB(Data Memory Barrier)或 DSB(Data Synchronization Barrier)。
三十八、用户进程通信主要几种方式?
三十九、请描述原子操作、自旋锁、信号量、互斥锁以及RCU的特点和使用规则?
1、管道
int pipefd[2];
pipe(pipefd); // 创建匿名管道
2、消息队列
msgget():获取或创建消息队列。
msgsnd():向消息队列发送消息。
msgrcv():从消息队列接收消息。
3、信号量
系统 V 信号量:通过 semget()、semop() 等函数操作。
POSIX 信号量:支持命名和无名信号量,分别用 sem_open() 和 sem_init() 创建。
4、共享内存
shmget():获取或创建共享内存段。
shmat():将共享内存段附加到调用进程的地址空间。
shmdt():分离共享内存段。
shmctl():控制共享内存段。
5、socket
套接字不仅限于本地进程间的通信,还可以用于网络上不同主机之间的进程通信。它支持多种协议,如 TCP 和 UDP。
类型:
Unix 域套接字:用于同一台机器上的进程间通信。
Internet 域套接字:用于跨网络的进程间通信。
6、signal
信号是一种简单的通知机制,用于在进程之间传递简短的信息。常见的信号包括 SIGINT(中断)、SIGTERM(终止)等。每个进程都有一个信号处理程序,可以定义收到特定信号时的行为。
使用函数:
kill():发送信号给指定进程。
signal() 或 sigaction():设置信号处理函数
四十、在内核启动时内核映像重定位是如何实现的?
内核启动时的重定位过程涉及到将内核映像从加载地址移动到实际运行地址。这个过程在不同的操作系统和架构上可能会有所不同,但通常包括以下几个步骤:
加载内核映像:引导程序负责将内核映像加载到内存中的某个位置。这个位置通常是固定的,或者由引导程序传递给内核。
确定目标地址:内核需要知道它应该运行在哪个物理地址空间。这可以通过多种方式确定,比如通过引导程序提供的信息、内核自身的配置等。
计算偏移量:如果加载地址与目标地址不同,内核需要计算出一个偏移量,以便后续调整所有代码和数据的地址。
重定位符号表:内核会遍历其符号表,根据计算出的偏移量调整每个符号的实际地址。这一步骤确保了所有的函数调用和数据访问都能指向正确的地址。
修改跳转指令和其他指针:除了符号表,内核还需要调整代码段中的跳转指令和其他类型的指针,以反映新的基址。
完成初始化:一旦所有必要的重定位操作完成,内核就可以开始执行其初始化过程,最终进入正常运行状态。
2026

被折叠的 条评论
为什么被折叠?



