Linux内核设计哲学:你我承载力的艺术(续)

news2026/4/3 21:07:41
第七部设备驱动——与不完美的世界和解7.1 你不是主人你是仆人设备驱动是内核中最“卑微”的组件。它不和用户直接打交道不参与核心决策甚至不拥有任何资源。它只是硬件的翻译官——把内核的标准请求翻译成硬件能懂的指令把硬件的状态翻译成内核能读的数据。但你猜怎么着这个“卑微”的位置恰恰最考验承载力。/** * struct device_driver - 设备驱动的基础结构 * name: 驱动名字没人记住 * bus: 挂在哪个总线上PCI、USB、I2C... * probe: 发现硬件时的初始化函数 * remove: 硬件拔出时的清理函数 * * 人生哲学 * 驱动不是主角硬件才是。 * 驱动的作用是“服务” * - 硬件需要初始化我来 * - 硬件需要读写我来 * - 硬件出错了我扛 * * 这就像团队里的“后勤”角色 * 没人注意你但没你不行。 * 不抢功、不抱怨、不邀功 * 只在需要的时候出现做完就消失。 * * 这是第11层承载力扛责而不揽权。 * 所有脏活累活自己干功劳归别人。 */ struct device_driver { const char *name; struct bus_type *bus; int (*probe)(struct device *dev); /* 硬件来了接待它 */ int (*remove)(struct device *dev); /* 硬件走了送别它 */ /* 运行时服务 */ int (*suspend)(struct device *dev); /* 系统要休眠安排好硬件 */ int (*resume)(struct device *dev); /* 系统醒了唤醒硬件 */ };驱动的第一个哲学你不是主人你是仆人。很多人追求“做主角”想站在聚光灯下。但内核告诉我们真正不可或缺的角色往往是那些默默服务的人。系统可以没有某个驱动而运行但不能没有所有驱动。卑微的位置需要最大的承载力。7.2 probe()第一次接触的艺术当一个硬件设备被发现比如你插上一个U盘内核会调用驱动的probe函数。这是驱动和硬件的“第一次见面”。第一次见面往往决定关系走向。/** * usb_probe - USB驱动的探测函数 * interface: USB接口硬件的一个功能单元 * id: 驱动支持的设备ID表匹配项 * * 返回值0表示成功负数表示失败 * * 人生哲学 * probe()是一个“相亲”函数。 * 硬件来了驱动要检查 * - 这是我该服务的设备吗ID匹配 * - 我能和它正常沟通吗初始化尝试 * - 我们长期合作需要什么资源分配内存、注册中断 * * 如果一切顺利probe返回0驱动和硬件“在一起”。 * 如果失败驱动要优雅退出释放已经申请的资源 * 不留下任何痕迹不污染系统。 * * 这就是“开始一段关系”的正确方式 * 先确认彼此合适再投入资源。 * 如果不合适好聚好散不纠缠。 * * 很多人一开始就盲目投入发现问题后又不愿退出 * 最后搞得一地鸡毛。 * probe教会我们该开始就开始该结束就结束。 */ static int usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct device *dev interface-dev; struct usb_driver *driver to_usb_driver(dev-driver); int ret; /* 1. 检查是否匹配已经由USB核心做好*/ /* 2. 尝试初始化硬件 */ ret driver-probe(interface, id); if (ret) { /* 失败清理已经申请的资源 */ dev_err(dev, probe failed: %d\n, ret); goto out; } /* 3. 分配驱动需要的数据结构 */ dev_set_drvdata(dev, kzalloc(sizeof(struct my_driver_data), GFP_KERNEL)); /* 4. 注册中断处理程序 */ ret request_irq(dev-irq, my_interrupt_handler, IRQF_SHARED, my_driver, dev); if (ret) { /* 失败释放已分配的内存 */ kfree(dev_get_drvdata(dev)); goto out; } /* 5. 一切顺利创建sysfs文件让用户空间能看到*/ device_create_file(dev, dev_attr_status); dev_info(dev, probe successful\n); return 0; out: /* 优雅退出不留下任何痕迹 */ return ret; }probe()告诉我们开始一段关系前确认彼此合适如果不合适及时止损干净退出。很多人被“沉没成本”困住已经投入了舍不得退出结果越陷越深。内核不会这样。它知道早失败比晚失败好干净退出比拖着强。7.3 中断处理你不重要但你不能出错驱动的中断处理函数是系统中最“紧急”但最“不重要”的角色。紧急因为它必须立即响应硬件。 不重要因为没人关心它做了什么只关心它别做错。/** * my_interrupt_handler - 驱动的中断处理函数 * irq: 中断号 * dev_id: 设备IDprobe时注册的 * * 返回值IRQ_HANDLED我处理了或IRQ_NONE不是我产生的 * * 人生哲学 * 中断处理函数是“消防员”。 * 警报响了中断来了必须立即出动。 * 但没人感谢消防员大家只关心火灭了没有。 * * 驱动的中断处理函数有一个铁律快速返回。 * 不能在中断上下文里做任何耗时操作 * - 不能睡眠内存分配、互斥锁 * - 不能做复杂计算 * - 不能等待任何事情 * * 为什么因为系统在等你。 * 你在中断处理函数里多待1微秒系统就延迟1微秒响应其他中断。 * 你待太久系统就丢中断、性能崩溃。 * * 这就是“紧急但不重要”的任务处理原则 * 快速确认、快速处理、快速退出。 * 不做多余的、不邀功、不拖延。 * * 很多人把“紧急”当成“重要” * 结果永远在救火永远没时间做真正重要的事。 * 中断处理函数告诉我们紧急的事要快做但别把它当重要的事。 */ static irqreturn_t my_interrupt_handler(int irq, void *dev_id) { struct device *dev dev_id; u32 status; /* 1. 读取硬件状态寄存器最快的事*/ status readl(dev-regs STATUS_REG); /* 2. 判断是不是我这个设备产生的中断 */ if (!(status MY_DEVICE_IRQ_BIT)) return IRQ_NONE; /* 不是我让别人处理 */ /* 3. 清除中断标志防止重复触发*/ writel(status | MY_DEVICE_IRQ_BIT, dev-regs STATUS_REG); /* 4. 如果是“需要处理数据”的中断调度下半部 */ if (status DATA_READY) { /* 快速拷贝数据如果硬件有FIFO*/ memcpy_fromio(dev-buffer, dev-regs DATA_REG, 64); /* 调度工作队列下半部做真正的处理 */ schedule_work(dev-work); } /* 5. 快速返回 */ return IRQ_HANDLED; } ​ /* 下半部在进程上下文做真正的工作 */ static void my_work_handler(struct work_struct *work) { struct device *dev container_of(work, struct device, work); /* 这里可以做任何事分配内存、获取锁、睡眠、通知用户... */ /* 因为这是在进程上下文不是中断上下文 */ /* 真正处理数据 */ process_data(dev-buffer, 64); /* 唤醒等待的用户进程 */ wake_up_interruptible(dev-wait_queue); }中断处理函数告诉我们紧急的事要快做重要的事要慢做别把两者混淆。很多人一辈子都在做紧急的事从来没时间做重要的事。结果就是永远在救火永远在忙碌但永远没有进步。7.4 电源管理在休眠和唤醒之间保持优雅现代设备需要省电所以驱动必须支持电源管理休眠suspend和唤醒resume。/** * my_suspend - 驱动休眠函数 * dev: 设备 * * 返回值0成功负数失败 * * 人生哲学 * 系统要休眠了驱动必须配合。 * 但驱动不能简单地说“我睡了” * 它要做很多工作 * - 保存硬件状态寄存器值 * - 停止硬件活动DMA、中断 * - 释放部分资源让给其他设备 * * 最重要的是无论做什么最终必须能让硬件进入低功耗状态。 * 如果某个驱动拒绝休眠整个系统都不能睡。 * * 这就是“配合大局”的智慧 * 你一个人的不配合会让整个团队的努力白费。 * 有时候为了更大的目标你需要“委屈”自己。 * * 但“委屈”不是“牺牲”。 * suspend函数会保存状态resume函数会恢复状态。 * 你只是暂时让出资源不是永久放弃。 */ static int my_suspend(struct device *dev) { struct my_driver_data *data dev_get_drvdata(dev); /* 1. 保存硬件状态 */ data-saved_regs[0] readl(dev-regs REG_CTRL); data-saved_regs[1] readl(dev-regs REG_CONFIG); /* 2. 停止DMA传输 */ writel(0, dev-regs DMA_CTRL); /* 3. 禁用中断系统休眠期间不需要中断*/ disable_irq(dev-irq); /* 4. 通知硬件进入休眠 */ writel(SLEEP_MODE, dev-regs POWER_CTRL); return 0; } ​ /** * my_resume - 驱动唤醒函数 * dev: 设备 * * 人生哲学 * 系统醒了驱动必须恢复。 * 恢复比休眠更复杂 * - 重新初始化硬件可能状态丢失 * - 恢复保存的寄存器 * - 重新启动DMA * - 重新申请中断 * * 但最重要的是恢复后系统应该和休眠前一样 * 用户感觉不到曾经休眠过。 * * 这就是“优雅”的体现 * 经历风雨休眠归来仍是少年恢复原样。 * 不在脸上留下沧桑不把负面情绪带给别人。 * * 很多人经历挫折后变得愤世嫉俗、怨天尤人。 * 驱动告诉我们睡一觉醒来就当什么都没发生。 * 保持状态继续工作。 */ static int my_resume(struct device *dev) { struct my_driver_data *data dev_get_drvdata(dev); /* 1. 重新初始化硬件保险起见*/ writel(0, dev-regs REG_CTRL); mdelay(10); /* 等待硬件稳定 */ /* 2. 恢复保存的状态 */ writel(data-saved_regs[0], dev-regs REG_CTRL); writel(data-saved_regs[1], dev-regs REG_CONFIG); /* 3. 重新启动DMA */ writel(DMA_ENABLE, dev-regs DMA_CTRL); /* 4. 重新申请中断 */ enable_irq(dev-irq); /* 5. 通知硬件退出休眠 */ writel(ACTIVE_MODE, dev-regs POWER_CTRL); return 0; }电源管理告诉我们暂时的退让是为了更长久的合作。不是所有事情都要一直在线。有时候你需要“休眠”——暂停活动、保存状态、释放资源。但重要的是你能在需要的时候“唤醒”——恢复状态、重新工作、回到巅峰。这就是“能屈能伸”的智慧。7.5 驱动的错误处理你能承受多少失败驱动面对的是最不可靠的硬件。硬件可能随时出错总线超时、寄存器读失败、DMA传输错误。/** * my_device_io - 安全的硬件IO操作 * dev: 设备 * reg: 寄存器地址 * val: 要写入的值如果是写操作 * write: 1表示写0表示读 * * 返回值读操作返回读到的值写操作返回0负数表示错误 * * 人生哲学 * 硬件IO可能失败驱动必须处理所有失败情况。 * 不能因为读寄存器失败就panic也不能假装成功。 * * 驱动的错误处理有三个原则 * 1. 承认失败返回错误码不隐瞒 * 2. 记录失败打印日志供调试 * 3. 尝试恢复重试、复位硬件 * * 如果恢复不了通知上层“我尽力了帮不了你。” * 但不崩溃、不panic、不污染系统。 * * 这就是“能承受失败”的承载力 * 失败是常态不是意外。 * 准备好处理失败而不是祈祷不要失败。 * 每次失败都是一次学习每次恢复都是一次成长。 */ static int my_device_io(struct device *dev, u32 reg, u32 *val, int write) { int retry 3; /* 最多重试3次 */ while (retry--) { if (write) { /* 写操作 */ writel(*val, dev-regs reg); /* 验证写入是否成功有些硬件支持读回验证*/ if (readl(dev-regs reg) *val) return 0; /* 成功 */ } else { /* 读操作 */ *val readl(dev-regs reg); /* 检查读到的值是否有效 */ if (*val ! 0xDEADBEEF) /* 0xDEADBEEF是硬件定义的“无效”值 */ return 0; /* 成功 */ } /* 失败等待一会儿再重试 */ udelay(10); } /* 重试3次都失败 */ dev_err(dev, IO failed for reg 0x%x after 3 retries\n, reg); /* 尝试复位硬件 */ my_device_reset(dev); return -EIO; /* 返回错误码让上层决定怎么办 */ }驱动的错误处理告诉我们失败不是终点放弃才是。硬件会失败但驱动不会因为一次失败就崩溃。它会重试、会记录、会恢复。只有恢复不了才放弃但放弃前已经尽力。这就像人生你会遇到失败但不能因为失败就放弃。你可以失败很多次但只需要成功一次。7.6 驱动的人生启示设备驱动教给我们关于承载力的几个层次第一层甘当配角不是主角但不可或缺不抢功、不抱怨、不邀功在需要的时候出现做完就消失第二层干净的开始和结束开始时确认合适再投入不合适时及时止损退出时不留痕迹第三层快慢分明紧急的事快做中断上下文重要的事慢做进程上下文不混淆紧急和重要第四层能屈能伸需要休眠时就休眠需要唤醒时就恢复暂时的退让为了更长远的合作第五层承受失败失败是常态不是意外重试、记录、恢复尽力之后才放弃这就是一个服务者的样子卑微但有价值简单但可靠能承受失败但从不主动放弃。第八部系统调用——通往内核的大门8.1 门卫的艺术系统调用是用户程序进入内核的唯一通道。就像一个公司的前台每天无数人进出每个人都说“我有事要找里面的人”。前台不能拒绝所有人那公司就封闭了也不能放所有人进去那公司就乱了。/** * syscall_handler - 系统调用入口 * nr: 系统调用号要做什么 * arg1, arg2, arg3: 参数怎么做 * * 返回值成功返回结果失败返回错误码 * * 人生哲学 * 系统调用是“门卫”的角色。 * 用户程序访客想进入内核内部区域做某些事 * 门卫的工作是 * 1. 检查请求是否合法参数有效吗权限够吗 * 2. 检查资源是否足够内存够吗锁能拿到吗 * 3. 执行请求如果真的合法 * 4. 返回结果成功或失败 * * 门卫不做的是 * - 不判断请求的“道德性”用户想做什么是用户的事 * - 不代替用户做决定用户说做什么就做什么只要合法 * - 不因为用户“态度不好”就拒绝不情绪化 * * 这就是第1层承载力接纳人性之私。 * 用户可能自私、可能愚蠢、可能恶意 * 但系统调用不生气、不指责、不教育用户。 * 只检查合法性然后执行或拒绝。 * * 很多人试图“教育”别人试图改变别人的“三观”。 * 系统调用告诉我们你改变不了别人只能检查请求是否合法。 * 合法就做不合法就拒绝不带情绪。 */ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) { struct file *file; int ret; /* 1. 检查参数合法性 */ if (!buf || !count) return -EINVAL; /* 无效参数拒绝 */ /* 2. 根据fd找到文件对象 */ file fget(fd); if (!file) return -EBADF; /* 文件描述符无效拒绝 */ /* 3. 检查权限用户有读这个文件的权限吗*/ if (!(file-f_mode FMODE_READ)) { ret -EBADF; goto out; } /* 4. 检查是否在用户空间不能读内核内存*/ if (!access_ok(VERIFY_WRITE, buf, count)) { ret -EFAULT; goto out; } /* 5. 执行真正的读操作 */ ret vfs_read(file, buf, count, file-f_pos); out: fput(file); return ret; }系统调用的第一个哲学接纳但不纵容检查但不情绪化。你的请求只要合法我就执行不合法我就拒绝。我不评判你的动机不改变你的想法不教育你的三观。这就是“边界感”的极致。8.2 copy_from_user信任但验证用户程序传递的数据在用户空间内核不能直接访问可能被篡改、可能分页错误。所以内核提供了copy_from_user函数从用户空间拷贝数据到内核空间。/** * copy_from_user - 从用户空间安全地拷贝数据 * to: 内核空间的目标地址 * from: 用户空间的源地址 * n: 要拷贝的字节数 * * 返回值0成功非0表示失败的字节数 * * 人生哲学 * copy_from_user是“信任但验证”的典范。 * 内核信任用户程序说“我的数据在地址X” * 但信任不代表不验证。 * * 它会检查 * - 地址X是否在用户空间不能是内核地址 * - 地址X到Xn是否都可访问 * - 用户程序是否有权限读这个地址 * * 如果任何检查失败拷贝失败返回错误。 * 不会因为“信任”而跳过验证。 * * 这就是“信任但验证”的智慧 * 你可以信任别人但不能依赖信任。 * 信任是态度验证是行动。 * 信任别人不代表放弃自己的判断。 * * 很多人要么完全不信任活得太累 * 要么盲目信任活得太傻。 * 系统调用告诉我们信任但要验证。 */ unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) { /* 1. 检查地址范围是否在用户空间 */ if (!access_ok(VERIFY_READ, from, n)) return n; /* 全部失败 */ /* 2. 检查页面是否都在内存中不会缺页*/ pagefault_disable(); /* 暂时关闭缺页处理 */ /* 3. 拷贝数据可能触发缺页但我们关闭了处理所以会直接失败*/ if (__copy_from_user_inatomic(to, from, n)) { pagefault_enable(); /* 4. 如果失败了用慢速路径允许缺页重试 */ pagefault_enable(); return __copy_from_user(to, from, n); } pagefault_enable(); return 0; /* 成功 */ }copy_from_user告诉我们信任是态度验证是行动。你可以信任别人但不能因为信任就放弃验证。信任可以让你更高效但验证让你更安全。8.3 错误码诚实的反馈系统调用返回一个整数成功返回非负数通常是0或字节数失败返回负数错误码。/** * 常见的错误码 * * 人生哲学 * 错误码是“诚实的反馈”。 * 系统调用不会说“我不舒服”不会说“你等一下” * 它会告诉你确切的问题 * - EINVAL: 参数无效你给错了 * - EPERM: 权限不足你不够格 * - ENOMEM: 内存不足我没办法 * - EAGAIN: 资源暂时不可用再试一次可能成功 * - EIO: IO错误硬件有问题 * * 诚实的反馈有几个好处 * 1. 调用者知道问题在哪可以修正 * 2. 调用者知道怎么重试EAGAIN就重试EINVAL就不重试 * 3. 调用者不会误以为成功 * * 很多人害怕说“不”害怕给负面反馈。 * 结果要么沉默用户不知道为什么失败 * 要么撒谎说成功但实际失败 * 要么含糊“出错了”但不说哪里错。 * * 系统调用告诉我们诚实的负面反馈比虚假的正面反馈更有价值。 * 说“你错了”不是羞辱是帮助。 * 说“我不行”不是示弱是诚实。 */ #define EPERM 1 /* 权限不足 */ #define ENOENT 2 /* 文件不存在 */ #define EINTR 4 /* 被信号打断 */ #define EIO 5 /* IO错误 */ #define ENOMEM 12 /* 内存不足 */ #define EACCES 13 /* 权限拒绝 */ #define EINVAL 22 /* 参数无效 */ #define ENOSPC 28 /* 磁盘空间不足 */ #define EAGAIN 11 /* 资源暂时不可用请重试 */错误码告诉我们诚实的反馈比虚假的安慰更有价值。很多人害怕说“不”害怕给负面反馈结果问题越积越多。系统调用不会这样。它会清楚地告诉你哪里错了为什么错怎么办。这就是“建设性批评”的极致。8.4 阻塞与非阻塞等待的智慧有些系统调用会“阻塞”如果资源暂时不可用就让调用者睡眠等资源可用时再唤醒。有些系统调用是“非阻塞”的资源不可用时立即返回EAGAIN。/** * do_blocking_read - 阻塞式读 * file: 文件对象 * buf: 用户缓冲区 * count: 要读的字节数 * * 人生哲学 * 阻塞式读是“耐心等待”的体现。 * 数据还没准备好没关系我等。 * 等多久不知道但我会一直等。 * * 内核会把我放入等待队列然后调度其他进程。 * 不浪费CPU不抱怨等待只是安静地等。 * * 这就是“有耐心的等待” * 知道等待是必要的愿意为结果付出时间。 * 不焦虑、不折腾、不放弃。 * * 很多人等不了总要“做点什么” * 结果在等待中消耗了大量精力。 * 阻塞读告诉我们有时候最好的行动就是等待。 */ static ssize_t do_blocking_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { struct my_device *dev file-private_data; /* 如果数据还没准备好 */ while (!data_ready(dev)) { /* 加入等待队列让自己睡眠 */ prepare_to_wait(dev-wait_queue, wait, TASK_INTERRUPTIBLE); /* 让出CPU等被唤醒 */ schedule(); /* 被唤醒了检查是被信号打断还是数据来了 */ if (signal_pending(current)) return -ERESTARTSYS; /* 被信号打断 */ } /* 数据准备好了读取它 */ return do_read(file, buf, count, pos); } ​ /** * do_nonblocking_read - 非阻塞式读 * file: 文件对象 * * 人生哲学 * 非阻塞式读是“快速反馈”的体现。 * 数据准备好了读走。 * 没准备好返回EAGAIN告诉调用者“现在不行等会儿再试”。 * * 调用者收到EAGAIN后可以去做别的事过会儿再来。 * 不浪费CPU在等待上也不浪费CPU在睡眠唤醒上。 * * 这就是“高效但不强迫” * 不要求别人等待也不强迫自己等待。 * 能做就做不能做就说“现在不行”。 * * 很多人在“立即放弃”和“无限等待”之间摇摆 * 非阻塞读给出了第三条路现在不行但可能以后行。 * 不放弃希望但不浪费时间。 */ static ssize_t do_nonblocking_read(struct file *file) { struct my_device *dev file-private_data; if (!data_ready(dev)) return -EAGAIN; /* 现在不行以后再来 */ return do_read(file, buf, count, pos); }阻塞与非阻塞告诉我们知道什么时候该等什么时候该走。不是所有等待都值得也不是所有放弃都明智。要根据情况选择重要的事值得等紧急的事不等。8.5 系统调用的人生启示系统调用教给我们关于承载力的几个层次第一层接纳但不纵容接受任何合法请求拒绝任何非法请求不带情绪不教育用户第二层信任但验证信任用户的意图验证用户的输入信任是态度验证是行动第三层诚实反馈成功说成功失败说失败清楚告诉原因帮助调用者改进第四层明智等待重要的事值得等阻塞紧急的事不等非阻塞知道什么时候该等什么时候该走这就是一个门卫的样子有边界、有验证、有反馈、有判断。第九部虚拟化——承载整个世界的容器9.1 一个容器能装下多少个世界虚拟化是内核中最“宏大”的组件。它让一台物理机变成多台虚拟机每台虚拟机运行自己的操作系统仿佛自己独占整个硬件。/** * struct kvm - 内核虚拟机KVM的核心结构 * vcpus: 虚拟CPU数组 * mm: 虚拟机的内存空间 * arch: 架构相关的数据 * * 人生哲学 * KVM是一个“容器”能装下多个“世界”。 * 每个世界虚拟机以为自己独占硬件 * 但实际上它们共享同一台物理机。 * * KVM的工作是 * - 让每个虚拟机觉得“我是唯一的” * - 在虚拟机之间公平分配硬件资源 * - 保护虚拟机之间的隔离不能让一个虚拟机看到另一个的内存 * * 这就是“容器”的哲学 * 能装下不同世界但保持每个世界的独立性。 * 不强迫一致不压制不同。 * 每个世界有自己的规则只要不互相干扰。 * * 这是第16层承载力能装得下不同意见。 * 层级越高越会遇到三观不同、立场不同、想法不同的人。 * 不强迫一致不压制不同兼容并蓄。 */ struct kvm { struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; /* 虚拟CPU */ struct mm_struct *mm; /* 虚拟机的地址空间 */ struct kvm_memslots *memslots; /* 虚拟内存区域 */ /* 统计信息 */ u64 stat[KVM_NR_STATS]; };虚拟化的第一个哲学一个容器能装下多个世界每个世界有自己的规则。不是所有人都要按同一套规则生活。虚拟化允许Windows、Linux、BSD在同一台机器上运行每个有自己的内核、自己的文件系统、自己的用户。这就是真正的包容尊重不同但不让不同互相伤害。9.2 VM-Exit当虚拟世界遇到现实虚拟机中的Guest OS以为自己在直接操作硬件但实际上是KVM在模拟硬件。当Guest OS试图执行特权指令比如关闭中断、读写CR3寄存器CPU会“陷入”到KVM这就是VM-Exit。/** * kvm_vcpu_run - 运行虚拟CPU * vcpu: 虚拟CPU * * 返回值VM-Exit的原因 * * 人生哲学 * VM-Exit是“理想遇到现实”的时刻。 * Guest OS觉得“我想做什么就能做什么” * 但当它尝试做“特权操作”时KVM介入 * “等等这个操作不能直接做我来帮你模拟。” * * Guest OS可能觉得被打扰了 * “为什么要打断我我在认真工作” * 但KVM知道如果不打断虚拟机之间就没法隔离。 * * 这就是“必要的打断” * 有时候为了更大的目标隔离、安全、公平 * 必须打断别人的“美梦”。 * 被打断的人可能不高兴但这是必要的。 * * 很多管理者不敢打断下属怕得罪人。 * 结果下属以为“我想怎样就怎样” * 团队失去秩序最终所有人受害。 * KVM告诉我们必要的打断不是坏事是责任。 */ int kvm_vcpu_run(struct kvm_vcpu *vcpu) { int r; /* 1. 检查是否需要注入中断 */ if (kvm_check_request(KVM_REQ_EVENT, vcpu)) kvm_vcpu_inject_irq(vcpu); /* 2. 进入Guest模式运行Guest OS*/ r vcpu_enter_guest(vcpu); /* 3. 从Guest模式退出VM-Exit发生了*/ /* 原因可能是Guest执行了IO、访问了MMIO、缺页、HLT... */ /* 4. 处理VM-Exit的原因 */ switch (vcpu-exit_reason) { case KVM_EXIT_IO: /* Guest试图读写IO端口模拟它 */ kvm_emulate_io(vcpu); break; case KVM_EXIT_MMIO: /* Guest试图访问内存映射IO模拟它 */ kvm_emulate_mmio(vcpu); break; case KVM_EXIT_HLT: /* Guest执行了HLT停机让它睡一会儿 */ kvm_vcpu_halt(vcpu); break; case KVM_EXIT_EPT_VIOLATION: /* Guest缺页但KVM负责处理 */ kvm_handle_page_fault(vcpu); break; } /* 5. 处理完重新进入Guest */ goto again; }VM-Exit告诉我们必要的打断不是坏事是责任。很多人不敢打断别人怕得罪人结果问题越积越多。KVM告诉我们为了更大的目标必须打断。但打断不是粗暴地中断而是打断-处理-恢复。被打断的人甚至感觉不到被打断过。这就是“温柔地打断”的艺术。9.3 内存虚拟化影子页表的孤独虚拟化中最复杂的是内存虚拟化。Guest OS有自己的物理地址GPA但真正的物理地址HPA由KVM管理。KVM需要维护GPA到HPA的映射。/** * struct kvm_mmu - KVM的内存管理单元 * root_hpa: 影子页表的物理地址 * translate: GPA到HPA的转换函数 * * 人生哲学 * 内存虚拟化是“翻译官”的角色。 * Guest OS说“我要访问地址0x1234” * 但0x1234是GPA不是真正的物理地址。 * KVM要翻译成真正的物理地址HPA。 * * 这个翻译对Guest OS是透明的 * Guest OS不知道自己在被翻译以为自己在直接访问。 * * 这就是“无声的服务” * 在别人看不见的地方做最复杂的事。 * 不求感谢不求理解只求结果。 * * 很多人做事要求“被看见” * 不被看见就觉得“白做了”。 * 影子页表告诉我们真正有价值的工作往往是不被看见的。 * 你不被看见是因为你做得太顺了别人感觉不到你的存在。 * 这其实是最大的成功。 */ struct kvm_mmu { gpa_t (*translate)(struct kvm_vcpu *vcpu, gpa_t gpa); hpa_t root_hpa; /* 影子页表 */ /* 统计被翻译了多少次 */ u64 translations; };影子页表告诉我们真正有价值的工作往往是不被看见的。很多人追求“被看见”追求存在感。但真正的高手是让事情变得如此顺畅以至于别人感觉不到他的存在。这就是“无为而治”的极致。9.4 CPU虚拟化公平但不平均KVM需要在多个虚拟机之间公平分配CPU时间。/** * kvm_schedule - KVM的CPU调度 * vcpu: 虚拟CPU * * 人生哲学 * CPU虚拟化是“公平但不平均”的体现。 * 每个虚拟机以为自己独占所有CPU * 但实际上KVM在后台调度 * - 这个虚拟机运行100ms * - 切换到下一个虚拟机 * - 再运行100ms * * 调度是公平的每个虚拟机都有机会。 * 但不是平均的重要的虚拟机可以分到更多时间。 * * 这就是“公平”的真谛 * 公平不是每人分到一样多 * 公平是每人分到应得的那份。 * * 很多管理者误解了公平 * 以为公平就是“一模一样” * 结果奖励懒汉、惩罚勤快人。 * KVM告诉我们公平是按需分配、按重要性分配。 * 重要的虚拟机实时系统优先 * 不重要的虚拟机测试环境让路。 */ void kvm_schedule(struct kvm_vcpu *vcpu) { struct kvm *kvm vcpu-kvm; /* 1. 当前虚拟机的时间片用完了 */ if (vcpu-time_slice 0) { /* 2. 选择下一个要运行的虚拟机 */ vcpu select_next_vcpu(kvm); /* 3. 给新的虚拟机分配时间片 */ vcpu-time_slice get_time_slice(vcpu-priority); } /* 4. 运行选中的虚拟机 */ kvm_vcpu_run(vcpu); }CPU虚拟化告诉我们公平是按需分配不是平均分配。不同的人、不同的任务需要不同的资源。好的管理者知道这一点会根据实际情况分配资源而不是机械地“一人一份”。9.5 虚拟化的人生启示虚拟化教给我们关于承载力的几个层次第一层容器的胸怀能装下多个世界每个世界有自己的规则不强迫一致不压制不同第二层必要的打断为了更大目标必须打断打断-处理-恢复让被打断者无感温柔的打断不是粗暴的中断第三层无声的服务在最复杂的地方做最不被看见的工作不求感谢不求理解太顺了所以不被看见这是最大的成功第四层公平但不平均每人得到应得的不是相同的按需分配按重要性分配公平不是一刀切这就是一个容器的样子能包容、能打断、能服务、能公平。第十部内核的终极哲学——承载力我们走了很远。从调度器到内存管理从中断处理到文件系统从网络协议栈到同步机制从设备驱动到系统调用最后到虚拟化。每一部分都在告诉我们同一件事承载力是内核的核心竞争力。10.1 承载力的九个层次回顾整个内核我们可以看到承载力的九个层次第0层接纳一切调度器不拒绝任何请求不问来路每个人有位置不带情绪处理第1层在混乱中建立秩序调度器内存管理不等待完美信息在模糊中决策做团队的定心丸第2层承受孤独OOM Killer kswapd做最艰难的决定无人理解无人分担默默守护不求感谢第3层压力减震内存管理中断处理提前预警不等到枯竭后台处理不影响前台内部消化不传递焦虑第4层快速响应从容处理中断处理紧急的事快做重要的事慢做不混淆紧急和重要第5层扛责不揽功文件系统驱动好事归大家坏事自己扛利益让一步风险冲在前卑微但不可或缺第6层拥抱不可控网络协议栈接受一切可能为每种可能准备方案不抱怨环境只调整自己第7层不转移压力同步机制压力自己消化安静地等待不发泄、不迁怒、不抱怨第8层容器的胸怀虚拟化能装下不同世界尊重不同但不让不同互相伤害能包容、能打断、能服务、能公平10.2 内核给人生的一堂课Linux内核能运行在从手表到超级计算机的所有设备上不是因为它的代码多么精妙而是因为它学会了承载。承载硬件的差异、承载驱动的错误、承载并发的冲突、承载攻击者的恶意。这和你的人生一模一样。你的承载力决定你的人生高度。能扛住多少混乱就能掌控多少秩序。 能承受多少孤独就能匹配多少荣耀。 能接纳多少意外就能拥有多少从容。 能消化多少压力就能承担多少责任。内核的每一行代码都在告诉你不必羡慕别人的高度先练出与之匹配的承载力。 承载力到了所有机会、地位、财富都会自然向你靠近。这就是Linux内核设计哲学的核心。这就是能承载多少就能走多远的最好诠释。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2480093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…