
操作系统实现
文章平均质量分 76
zjkzjk7711
这个作者很懒,什么都没留下…
展开
-
寄存器的值被保存在每个任务自己的任务控制块(TCB)中的一个字段,叫做“上下文保存区”,它实际就是一块内存结构体**,分配在内核空间的 RAM 里。
/ 可选加:cs, ds, fs, gs, ss, cr3 等这就是寄存器值的“落地点”,每个字段就是 RAM 中的某个字节块。保存什么存哪了(在内存)通用寄存器(你定义的结构体)栈指针 ESP(或者指令地址 EIP,或从栈中retiret恢复栈本身内容(你分配的页)原创 2025-04-17 15:53:11 · 394 阅读 · 0 评论 -
现代任务调度算法红黑树vruntime,谁的vruntime越小谁就是头,随着运行,时间不断增大
现代 Linux 使用CFS(完全公平调度器)作为默认进程调度算法,结合红黑树管理vruntime,让所有进程“尽可能公平”使用 CPU,同时也支持一系列实时调度策略(FIFO、RR、Deadline),适配从桌面到工业控制的多种应用场景。CFS 红黑树结构怎么实现的?vruntime在调度中如何更新?手写一个 toy 内核的 CFS 简化版?或者给你举一个如何在用户态模拟调度器的示例?我可以接着带你深入!🧠。原创 2025-04-17 15:48:27 · 284 阅读 · 0 评论 -
我还没实现 fork,没有用户程序,我怎么调度第一个任务?它的内存从哪来?第一个任务(进程/线程)是由内核初始化时手动创建的任务控制块(TCB)+ 栈 + 页目录完成的,
你要手动“创造”第一个任务(比如 init/idle task),从页分配器中分配页表、栈,初始化好它的上下文,加入调度器的 ready 队列,再通过或中断调度进入它。初始化第一个任务的 C 模板(带 TCB、分页、栈)switch_to 的汇编代码定时器中断 + lazy context switch 框架我可以立刻贴给你一套 ✅ 你要哪种风格的调度器(协作式还是中断驱动式)?原创 2025-04-17 15:45:07 · 359 阅读 · 0 评论 -
从 实模式启动 → 保护模式切换 → 加载 ELF → 构建页表启用分页 → 跳转 e_entry
你作为 bootloader,需要负责从磁盘加载 ELF 文件→构造保护模式与页表环境→跳转到e_entry虚拟地址运行内核,整个“从无到有”的链条你都要手动构建!原创 2025-04-17 15:40:26 · 213 阅读 · 0 评论 -
任务切换的方法:switch(保护上下文,换新的上下文)+iret(ss,esp,cs,eip)+tss换
任务切换的核心是保存当前任务的上下文(寄存器、栈、eip等),然后加载目标任务的上下文并跳转执行。主流实现分为:手动保存恢复(现代 OS 使用)、使用iret进行特权级切换、以及 TSS 硬件任务切换(已淘汰)。我个人更倾向手写汇编切换 esp/eip 方式,更灵活、性能更高。原创 2025-04-11 15:14:53 · 518 阅读 · 0 评论 -
C++:main函数之前发生了什么??
顺序事件说明1ELF 加载OS 加载可执行文件,找到入口_start2执行_start来自crt1.o,是启动代码3调用初始化环境、调用构造函数4执行正式进入用户程序5返回后做清理调用析构函数,退出程序这也是为什么你即使 main 什么都没写,程序也会有一些输出或行为—— 因为它前面已经走了好多底层逻辑。这是个经典的 C++ 面试问题,问的是在main()函数执行之前,程序运行了哪些内容。步骤内容举例说明1全局/静态对象构造函数A a;全局变量构造2。原创 2025-04-11 14:47:56 · 622 阅读 · 0 评论 -
BIOS MBR启动(0x7c00,读取磁盘loader0x8000) → loader_16实模式(检查内存硬件,准备gdt,开启CR0) → 保护模式过渡 → 加载内核(读磁盘elf,开启分页)
我会写一个 MBR 引导程序,从 BIOS 实模式启动,通过中断读取内核,切换到保护模式,跳转到我写的 32 位 C++入口函数,实现内存管理和任务初始化的最小操作系统。要不要我帮你写一个最小的的完整可运行 demo?可以直接用 qemu 启动验证!原创 2025-04-11 11:31:16 · 230 阅读 · 0 评论 -
elf,programmer header记录offset与filesize指向vaddr的代码段,数据段等
• 动态链接就像额外要的番茄酱包(需要时才加载)• text/data/bss是不同配料。• 程序头表告诉系统如何加载(怎么吃)• 程序入口就是第一口咬下的位置。• ELF头是包装信息。原创 2025-04-11 10:55:21 · 184 阅读 · 0 评论 -
悲观锁和乐观锁
对比项悲观锁乐观锁思维模式默认会冲突默认不会冲突是否加锁是(阻塞)否(非阻塞)实现方式mutexrwlock等CAS, 版本号,时间戳性能特点冲突多时稳定冲突少时快,冲突多性能下降成功概率一般都能成功可能失败需重试使用场景高冲突区,如数据库写操作高并发低冲突,如计数器、自旋队列。原创 2025-04-02 09:44:33 · 325 阅读 · 0 评论 -
printf的实现,va_list读取参数,vsprintf进行将%s,%d这些参数的值转化为字符串并一起输出到缓存区
【代码】printf的实现,va_list读取参数,vsprintf进行将%s,%d这些参数的值转化为字符串并一起输出到缓存区。原创 2025-04-01 17:58:42 · 70 阅读 · 0 评论 -
系统调用sbrk实现,首先获得当前堆顶,如果incr+offset超过页,先填满当前页,再额外分配页,返回原来的堆顶
sbrk()原创 2025-04-01 17:24:48 · 276 阅读 · 0 评论 -
系统调用execve()
execve会用一个新的程序替换当前进程的用户空间。内核会创建新的页表、加载 ELF 文件、重建用户栈和参数区,并在系统调用返回时,从新程序的入口地址开始运行,而不是返回到原来的函数调用。整个过程是“旧壳换新魂”,PID 不变,但一切都变了。原创 2025-04-01 09:23:49 · 214 阅读 · 0 评论 -
内存碎片:外部碎片(1+2+3,但你需要5,使用分页,分段使得不连续映射),内部碎片(分的页大于实际使用,移动成大块)
内存中存在足够总量的空闲空间,但这些空闲空间是不连续的。导致无法满足较大块内存的分配请求。👉 举例:有 1KB + 2KB + 3KB 的空闲,但你申请 5KB,就失败了。碎片类型解决机制外部碎片分页、伙伴系统、压缩(Compaction)内部碎片小块分配器、Slab/Slub 分配器、内存池程序层碎片合理使用、避免频繁小块分配如果你对某个系统(比如 Linux、FreeRTOS 或 Windows)具体的内存碎片处理机制感兴趣,我可以更深入分析它的实现逻辑,要不要来个实战分析?原创 2025-03-30 23:20:45 · 833 阅读 · 0 评论 -
进程fork和多线程的本质区别,一个是不同物理页,同虚拟地址,一个是同虚拟,物理地址,但不同栈,esp,ebp
线程属于同一进程,多个线程共享相同的虚拟地址空间(代码段、数据段、堆等),只有栈是独立的,这样线程之间可以轻松共享数据,适合并发模型。原创 2025-03-28 15:57:58 · 315 阅读 · 0 评论 -
系统调用 fork()
用户态:| 用户程序调用 fork() || ↓ || 封装后执行 int 0x80 触发中断 |内核态:| 中断处理器进入 sys_call || ↓ || 找到 syscall table 中 fork 对应函数 sys_fork() || ↓ || 执行:复制PCB、内存、文件、寄存器等 || ↓ || 设置返回值 eax=0 / eax=child_pid || ↓ || 用 iret 恢复到用户态 |用户态:| 两个进程继续执行,父子分流 |原创 2025-03-28 15:40:39 · 230 阅读 · 0 评论 -
TSS(Task State Segment)和 GDT(Global Descriptor Table)之间的关系 ,TSS必须独占一段(抽象)
TSS 必须占用一个 GDT 条目;GDT 可以包含多个普通段、TSS 段、LDT 段等;是用于释放 GDT 中的某个条目(比如释放一个 TSS 所在的段)。原创 2025-03-28 15:20:45 · 402 阅读 · 0 评论 -
lcall(调用门)和0x80(中断门)触发系统调用区别
位置原 lcall 方式新 int 方式用户态调用int $0x80参数传递压栈eax寄存器(可扩展用其他寄存器)内核入口调用门目标函数IDT 中断向量 0x80权限设置GDT 调用门 DPL=3IDT 中断门 DPL=3返回方式lretiret。原创 2025-03-28 10:38:50 · 238 阅读 · 0 评论 -
调用门之系统调用例如“msleep()”具体实现
你的系统调用流程长这样:函数sys_call()构造参数;执行lcall调用门。查找调用门,进入 GDT 中设置的目标(切换特权级;自动压栈 CS/EIP/EFLAGS;跳到偏移 0,即执行。保存上下文;设置段寄存器;调用真正处理系统调用。这段代码是一个自定义的系统调用包装函数,使用了调用门(call gate)来从用户空间进入内核态执行系统调用。这是调用门的“伪指针”,lcall使用的格式是段选择子(selector)+ 偏移地址(offset)。原创 2025-03-26 20:51:59 · 805 阅读 · 0 评论 -
调用门:调用门描述符(GDT)获得目标地址,并同时从特权级3中压入用户上下文信息,
在调用门触发跨权限调用(如从用户态进入内核)时,CPU 会自动根据调用门描述符切换栈、压入原用户态返回地址(CS/EIP/SS/ESP),然后直接从门中加载新的 CS:EIP 执行目标函数。整个过程是受限的、自动的、不可绕过的安全跳转。要不要我帮你画一张图,展示调用门触发 → 自动切栈 → 执行目标代码 → lret 回用户态的完整流程图?📊。原创 2025-03-26 15:16:42 · 501 阅读 · 0 评论 -
中断底层,exception_hand,手动保存寄存器入栈 esp、eflags、cs、eip、error code 最先自动入栈,和函数调用入栈稍有区别
参数含义name异常名,比如dividepage_faultnum异常号,比如014是否有 error code(1 = 有,0 = 没有)[中断进入]↓push 自动保存(eip, cs, eflags, esp, ss...)↓pusha;保存通用寄存器手动填补push $num;异常号push 段寄存器push %esp;传给 C 函数pop %esppop 段寄存器pop 通用寄存器跳过异常号+错误码iret。原创 2025-03-25 15:21:38 · 1003 阅读 · 0 评论 -
simple_switch 操作系统/线程库中实现用户级线程上下文切换
阶段说明🧠 保存当前线程把当前线程的esp保存到from_stack指向的地址中⏩ 切换栈指针把目标线程的栈顶地址赋给esp,让 CPU 栈切换到新线程♻️ 恢复目标线程从新线程栈中弹出保存的寄存器值(pop),恢复上下文🚀 ret 跳转ret相当于,跳到目标线程上次挂起的位置步骤做了什么① 取参数从栈中取fromto(cdecl 调用规则)② 保存现场push四个通用寄存器③ 保存栈顶→ 存 from 栈指针④ 切换栈顶→ 加载 to 的栈指针⑤ 恢复现场pop恢复寄存器。原创 2025-03-25 14:56:44 · 889 阅读 · 0 评论 -
用户态,内核态用链接脚本分离(lds),将进程放进用户态
目的实现方式保留内核低端物理空间按模块组织段.text.data.bss.rodata排除用户进程代码用户任务运行于高虚拟地址支持虚实地址分离加载导出关键地址符号供内核使用这份链接脚本通过和虚拟地址定位,清晰地区分了内核段与用户进程段,并通过AT()实现虚拟地址和加载地址分离,确保用户任务可以在高地址运行,同时保留了内核的低端物理布局。要不要我给你画个完整的“内核加载段 vs 用户任务段(虚拟地址映射)”的对比图?📊 非常适合做注释笔记或面试展示~原创 2025-03-24 19:53:33 · 902 阅读 · 0 评论 -
选择子是什么(数组的下标量),GDT(段),TSS(上下文,任务状态段,结构中包含各种 CPU 任务切换需要的寄存器状态)
TSS(任务状态段)中的选择子用于指定CPU 执行任务时用的段寄存器值;而 GDT(全局描述符表)保存了这些段选择子的具体描述信息(段基址、段限长、段属性)。TSS结构│ ss0 = 0x10 │ → GDT[2] = 内核数据段│ cs = 0x08 │ → GDT[1] = 内核代码段│ cr3 = 页目录地址 │↓↓↓GDT结构(全局描述符表)│索引│ 段描述符 ││ 1 │ 内核代码段 (0x08) ││ 2 │ 内核数据段 (0x10) ││ 3 │ 用户代码段 │。原创 2025-03-24 13:29:16 · 807 阅读 · 0 评论 -
进程如何分配内存?是按劳分配!只有触发page fault才分配,只有进行写才分配
一个进程在启动或申请内存时,并不会立即获得对应的物理内存页。操作系统采用**按需分配(lazy allocation)**策略,等进程真正访问某个虚拟页时,才通过页错误机制分配实际的物理页帧。这一机制极大提高了内存利用率,也方便了内存共享与延迟优化。需要的话我可以画一张图:「虚拟地址 → page fault → 物理页分配 → 页表更新」的流程图,或者对比“分配 vs 实际使用”的图示。要来一张吗?📊。原创 2025-03-23 23:52:53 · 384 阅读 · 0 评论 -
虚拟内存管理具体有哪些?
虚拟内存管理是操作系统提供的内存抽象,既提升了内存使用效率,又隔离了进程地址空间。它通过页表完成地址映射,通过置换算法管理内存回收,并利用权限位进行访问控制,是现代系统运行的基础保障之一。如果你想,我可以画出“地址转换流程图”或者“多级页表示意图”,让你面试/复习/笔记更直观!📊。原创 2025-03-23 23:51:38 · 228 阅读 · 0 评论 -
页面置换算法
算法性能实用性复杂度FIFO中下✅ 简单,入门常考低LRU高❌ 难实现硬件支持中高OPT最优❌ 不可实现,仅分析用高CLOCK接近 LRU✅ 常用于实际系统中LFU受限❌ 不常用高Random视场景✅ 非常轻量极低页面置换算法用于决定内存不够时淘汰哪一页。常见算法包括 FIFO、LRU、CLOCK、OPT 等。其中 CLOCK 是实际系统中常用的,因为它在效率和实现复杂度之间取得了良好平衡。原创 2025-03-23 23:34:08 · 759 阅读 · 0 评论 -
OOM,用score(占用内存高,不影响内核,用户不怎么保护)
OOM = 内存耗尽,系统无法为进程/内核分配更多内存,就触发了 OOM。当系统可用内存不足,且尝试分配内存失败(例如malloc()或fork()系统会触发一个机制 →OOM Killer(“内存杀手”)启动它会强制杀死某些进程来释放内存,避免整个系统崩溃OOM 是系统内存耗尽时由内核触发的一种保护机制,Linux 下通过 OOM Killer 杀掉内存占用过大的进程来回收资源,从而防止系统完全崩溃。它底层依赖内核内存分配失败检测、cgroup 限制、OOM 分数算法等机制。原创 2025-03-23 23:21:08 · 902 阅读 · 0 评论 -
原子操作底层真正实现 lock+锁总线/锁缓存(MESI)修改为M,别的CPU修改为I
MESI 是一种CPU 缓存一致性协议状态含义缓存中数据被改了,还没写回内存独占状态,数据和内存一致S (Shared)多个 CPU 共享,数据和内存一致缓存无效,需重新加载在老式 CPU 中,原子性是通过锁总线来实现的,代价高;而现代多核处理器通过缓存一致性协议(MESI)实现原子操作,锁的是缓存行,不仅高效,还能避免总线阻塞,是原子指令实现的主流方式。太棒了!你真的在深入理解系统底层了 🔥下面我就来给你深入具体讲讲 MESI 协议。原创 2025-03-23 23:12:35 · 931 阅读 · 0 评论 -
信号量【N-1】+互斥锁【N】,N个哲学家吃饭,避免死锁(所有人拿起左叉,信号量N-1,就可以解决有一个人不会拿起左叉),所有的叉子都是互斥锁
哲学家进餐问题考察死锁的 4 个条件:互斥、占有等待、不剥夺、循环等待。打破循环等待或占有等待条件。信号量限制进入人数 + 互斥锁控制资源线程std::mutex互斥锁(叉子)(C++20 支持)限制最大吃饭人数,避免死锁。原创 2025-03-23 19:23:07 · 363 阅读 · 0 评论 -
条件变量和信号量
条件变量信号量像是等快递:快递没来(条件不满足),你就坐那儿等,等来了再干事像是有 N 把椅子,你想坐必须有空位(资源),没位就等,有位就坐本身没“状态”,只负责唤醒本身有“计数器”状态条件变量是事件通知机制,配合锁等待“条件成立”;信号量是资源计数机制,用于控制对共享资源的访问。原创 2025-03-23 19:07:21 · 230 阅读 · 0 评论 -
再具体写一个虚拟地址到物理地址的过程
好嘞,下面是一个图示,展示中,的翻译过程,包括和中保存的内容👇。原创 2025-03-23 18:52:58 · 190 阅读 · 0 评论 -
分页时,Loader 的线性地址为什么要与物理地址相同?
text{线性地址} = \text{物理地址},而在分页启用后,它的。原创 2025-03-14 20:54:41 · 222 阅读 · 0 评论 -
write_cr3() read_cr3() 修改 CR3 会 改变页表的基地址,相当于 切换页表(如切换进程的虚拟内存映射)。 读取 CR3 可以 获得当前进程的页目录地址。
(切换页表,影响虚拟内存)。寄存器的值,获取当前。(获取页目录基地址)。(Paging)中,原创 2025-03-14 20:28:42 · 473 阅读 · 0 评论 -
一级页表(4M)和二级页表(4K)
(Paging)中,原创 2025-03-14 13:31:20 · 314 阅读 · 0 评论 -
kernel.lds 链接脚本
kernel.ldskernel.lds是一个,用于,定义。原创 2025-03-14 10:29:09 · 235 阅读 · 0 评论 -
boot_info_t BOOT_RAM_REGION_MAX,因为有不同的内存区如DDR+SRAM
在一些系统(如 ARM 设备、嵌入式 SoC)中,RAM 可能。这些 RAM 片段可能来源于不同的。在计算机架构中,RAM 可能分布在。,但不代表实际有多少块 RAM。,而不是单纯的“几张内存卡”。,而不是简单的“几张内存条”。,但不代表一定有 4 个。原创 2025-03-13 16:34:08 · 831 阅读 · 0 评论 -
位图 :管理物理内存(页)
的操作系统(如 Linux、Windows)中,位图主要用于。的操作系统(如 Linux、Windows)中,位图主要用于。(32KB 可管理 1GB 物理内存)。(32KB 可管理 1GB 物理内存)。(1 位管理 1 页,节省空间)。(1 位管理 1 页,节省空间)。位图(Bitmap)是。(适合大块内存分配)。位图(Bitmap)是。(适合大块内存分配)。(O(1) 操作)。(O(1) 操作)。原创 2025-03-13 11:20:46 · 763 阅读 · 0 评论 -
信号量虚假唤醒,wait()自己醒来,即使没有notify(原因,底层线程同时竞争锁与信号条件),只能在外部循环检测,while(!ready)
【代码】信号量虚假唤醒,wait()自己醒来,即使没有notify(原因,底层线程同时竞争锁与信号条件),只能在外部循环检测,while(!ready)原创 2025-03-13 11:00:16 · 237 阅读 · 0 评论 -
task 设计为什么要用 list_node_t 作为链表节点,而不是直接把 task_t 作为链表节点?其中 list_node_t为task的对象成员
在很多情况下,我们只需要遍历任务队列,而不需要访问整个。,适用于复杂的任务调度系统(比如操作系统内核)。时,可能会影响整个调度策略。直接作为链表节点,那么它只能属于。直接是链表节点,那么修改。的存储方式,而不需要改。指针和任务数据混在一起,直接是链表节点,那么。(完全公平调度器)使用。例如,Linux 的。原创 2025-03-12 20:39:52 · 744 阅读 · 0 评论 -
理解信号量,P(入座+等待),V(离座+进入(就绪))
线程(或进程)结束了,它只是。原创 2025-03-12 19:49:34 · 239 阅读 · 0 评论