LWN 翻译:Atomic Mode Setting 设计简介(下)

2023-11-03

译者注

紧接上篇文章,本篇翻译起来有难度,同时对读者的技术背景有一定要求,适合深入研究 DRM 驱动的开发人员阅读。通过阅读本文,你将了解如下内容:

  • DRM_MODE_ATOMIC_ALLOW_MODESET 标志位的由来及其作用
  • 驱动中随处可见的 ww_lock 到底是什么鬼?有什么作用?
  • atomic helper 与 legacy helper 相比,都做了哪些优化?
  • Atomic 框架中的 DPMS 为什么只有“ON”和“OFF”这两种状态?


原文链接:https://lwn.net/Articles/653466/

Atomic mode setting design overview, part 2

如今的图形应用程序需要完美帧的显示,同时也要利用嵌入式 GPU 中专用的、省电的硬件。为此,kernel 必须能够以原子的方式更新图形硬件的状态,即一次操作改变多个参数,这样用户就不会在参数修改过程中看到异常画面了。本系列的第一篇文章探讨了为什么需要使用 Atomic API、它是如何产生的、以及主要驱动程序接口是什么样子的。本篇,我们将基于前面的内容,深入探究更多的细节。

不过,在正式讲解之前,还是先简单地回顾一下主要的接口吧。Atomic 显示更新是通过对 display objects(如 plane、CRTC)进行 duplicate state (状态复制)并更新复制后的内容来实现的,这些内容统一收集在 struct drm_atomic_state 中。一旦完成复制内容的更新,新的 state 就会被完整地发送到驱动程序的 ->atomic_check() 回调接口中进行检查。如果检查通过,则最终会通过 ->atomic_commit() 接口将其提交给硬件。我们也可以使用 check-only 模式只对更新参数进行检查,这样就可以提前确定该操作是否可行了。

原子更新(Atomic Update)与核操作(Nuclear Option)

与 Android ADF 相比,upstream atomic mode-setting 的一个重大变化,就是 upstream 允许通过 atomic ioctl() 修改任意 display state,而 ADF 的 atomic 操作只允许修改 plane state,至于输出链路和 mode-set (显示模式)修改则需要在 ADF 的 userspace 侧单独调用。至少大致看下来, plane 更新和 mode-set 修改确实是两个不同的操作。一方面,atomic plane update 操作需要在下一帧显示完成时才会执行结束;另一方面,atomic mode-set 更新操作通常需要耗时几百毫秒,而且可能会出现短暂的黑屏,用户体验十分不友好。但是不管怎样,这两种更新方式都是以“要么全有或要么全无”的原子方式来更新显示状态的,并且这两种方式都可以使用同一个“property change list”进行 ioctl() 操作。因此,单从用户空间和 kernel 之间传递的数据结构组织形式来看(而不是从发起调用请求的语义来看),将它们视为同一种操作也是说得通的。

在讨论 upstream atomic 计划时,这两种更新操作之间的耦合关系造成了许多概念上的混淆。最终只是通过将 full mode set 称为“atomic mode set”,而将 plane update 称为“nuclear page flip”来纠正这一概念上的混淆。但是当你仔细观察时,full mode set 和需要同步到下一帧的 plane update 之间的区别很快就变得不那么清晰了。通常,板载 LCD 的 display mode 修改,仅仅只是需要重新调整一下 scaler 模块的缩放倍数,就可以将 buffer 中的显示区域最终显示到屏上。大多数的硬件都能实现这一功能,只需在下一帧进行一次小的修改,而不需要做 full mode set 和灭屏操作。而对于某些硬件而言,需要重新分配 FIFO 空间才能进行 plane 参数的更新,因此只能在 display pipeline 关闭时才能进行这一操作,这看起来反而很像一个 mode set 操作。

此外,用户空间通常希望将 full mode set 的更新操作推迟到更合适的时间点再执行。例如,平板电脑和智能手机的休眠唤醒实在太频繁了,以至于几乎所有的 mode set 操作都可以被延期执行,而不会因为一直保持着次优配置运行到下一次休眠而造成过多的电量浪费。因此,那些需要借助 full mode set 来修改 plane 参数的操作,在未经用户空间程序授权的情况下是不允许被执行的,以免给用户带来“不太完美”的视觉体验。

在对这个问题进行了大量探讨之后,最终合入的解决方案是增加 DRM_MODE_ATOMIC_ALLOW_MODESET 标志位。这样,用户空间就可以表明它是否允许接受一个完整的 mode set(缺点是可能会引入短暂的黑屏,并且可能需要较长的执行时间)。当然,这个标志也适用于 test-only 模式。其实大多数驱动程序不需要操心该标志位,因为 atomic helper 会帮你去 check 该标志位 —— 只要驱动程序正确地告诉 helper 代码它是否需要在上面某种特殊场景下执行 full mode set。

神奇的锁 —— 等待/缠绕互斥锁(wait/wound mutex)

接下来是关于如何处理并发更新的问题。不久前,DRM 为每个 CRTC 添加了一把各自独立的锁(per-CRTC lock),允许在不同的 display pipeline 上进行并发更新和修改操作。而反过来,我们也需要为每个 plane 添加一把锁(per-plane lock),因为 plane 可以切换它们当前所要连接的 CRTC(如果硬件支持的话)。另外,还有一个锁是用来保护所有的输出链路和 connector 状态的 —— 这些状态的修改通常需要至少几百毫秒,因此无论如何都会造成时间上的延迟(stall)。不过这种操作通常发生的频率较低,且是一种系统级的操作,因此更细粒度的锁保护只会造成资源的浪费。

但是,atomic ioctl 面临的一个问题是,用户空间有可能会按任意顺序传递参数更新列表(以 object_idproperty_idvalue 三元组的形式)。如果按列表的顺序来处理,则很容易导致死锁,因为不同的 object 需要不同的锁。用户空间可以通过创建两个更新操作来故意引发 AB-BA 死锁,这两个更新操作需要使用相同的两个 object,但顺序不同。

首先,我们可以通过按 object 对整个三元组列表进行排序来解决该问题,但不幸的是,这只能推迟问题发生的时间。在上一篇文章中曾提到过 atomic state-checking 代码只允许查看或访问正在更新操作中的 object state,这保证了并发更新不会互相干扰。不幸的是,这也意味着驱动程序必须要在它的 ->atomic_check() 回调中的 drm_atomic_state 结构中添加任意 object 及其 state。这将允许回调函数检查跨对象限制(cross-object limits),并确保共享资源没有被用尽。因为硬件是疯狂的,这不能在驱动程序代码之外完成,因为每个硬件都有自己特有的共享状态。仅仅提前获取所有可能需要的锁也不是一个好主意,因为这样只会使所有更新操作以同步方式进行,从而使所有细粒度的锁操作变得毫无意义。

这意味着我们需要一种可以按任意顺序获取锁的持锁机制,幸运的是,内核为我们提供了 wait/wound 互斥锁(顺便说一句,这也是 GPU 驱动骇客们为了对复那些讨厌的内存管理问题而添加的)。wait/wind 互斥锁本身是一个很大的话题,真的,但简而言之,它们允许你以任意顺序获取锁,能够可靠地检测死锁,并能够从死锁中退出,而不会在窗口期丢失公平性,也不需要耗费大量的自旋时间。除此之外,ww 锁还相当复杂。

没有人愿意强迫驱动程序编写人员去使用复杂的锁操作,因此锁操作都很好地隐藏在了 atomic core 所提供的函数中。当你想要把某个 plane 切换到其它 CRTC 上时,只需调用 drm_atomic_set_crtc_for_plane(),并将所有错误码正确地返回给调用端即可。同样的,如果驱动程序需要在其 state-validation 代码中查询其他 state object(例如检查共享资源的限制),它可以将任何想要查询的状态复制(duplicate)到所需的 atomic update 操作中,并在后台正确地持锁。这里唯一的重点就是能正确的返回 -EDEADLK 错误码,并始终使用 Atomic 框架提供的函数来复制和更新 state 结构体。有了它,魔法就产生了。当然啦,前提是驱动程序的 atomic check 代码编写正确,且不需要访问硬件状态或任何常驻在 state 结构体之外的东西。

对于 DRM-internal atomic 接口的用户来说,情况要稍微复杂一点。我们使用 drm_modeset_acquire_ctx 结构体来跟踪 wait/wound 的锁定状态,该结构体在 drm_modeset_acquire_init() 中进行初始化。在将 state object 复制到 atomic update 过程中,为了确保所有隐藏的 lock-taking 都能生效,该结构体也同时保存在了 drm_atomic_state->ctx 中。和驱动程序一样,在 duplicate state object 过程中,将隐式地获取所有必须的锁;而唯一需要显式处理的便是以 -EDEADLK 错误码发出的死锁信号。

由于死锁回退(deadlock backoff)逻辑将会释放锁,已经复制的 state object 可能不再是最新状态,因此需要先用 drm_atomic_state_clear() 对其进行释放操作,然后调用 drm_modeset_backoff() 来删除所有已获得的 mode-set lock,并一直 block 在当前发生竞争的锁上(contended lock),直到这些锁变得可用为止。一旦再次获取到这些锁,并且在 acquire context 初始化完成后,整个序列需要立即重新启动(acquire context 不能被重新初始化,因为它包含了 queue ticket,用于反映 wait/wound 锁定算法的当前进展)。最后,当所有操作都顺利完成后,就可以调用 drm_modeset_drop_locks() 来删除锁,并使用 drm_modeset_acquire_fini() 来结束 acquire context 的使用。

顺便提一下,测试所有这些特定的回退代码其实非常简单:开启 lock debug 机制并使能 CONFIG_DEBUG_WW_MUTEX_SLOWPATH,将会在 wait/wound 互斥锁代码中嵌入虚假的死锁条件。这样,所有的死锁回退代码都可以用单线程测试用例进行完整的测试。当然,wait/wind 互斥锁完全支持 Linux 锁的所有调试手段和测试工具,比如 lockdep。

Helper 库的设计

另一个困扰我们多年的问题也终于得到了解决,它就是驱动程序 helper (辅助)库的设计。像 ADF 那样加入一个中间层(mid-layer)的方案是不可取的,因为硬件有太多的特殊使用情况,无法创建这样一个不太复杂的框架。不管怎样,大多数驱动程序都已经使用了大量的 helper 函数,所以最好是驱动程序在转换为 Atomic 更新操作时,不必重写所有的 callback 和 support 代码。直接重用现有回调接口仅仅只是完成了概念验证(proof-of-concept)层面的转换,但从根本上讲,现有的 helper 库及其驱动回调接口还是存在不少问题的:

  • 他们并没有真正支持跨对象约束(cross-object constraint)的检查,而原子操作的一个主要目标就是正确地检查出这些约束条件。通过将 DRM_MODE_ATOMIC_TEST_ONLY 标志位导出给用户空间,来检查这种任意的跨设备约束(cross-device constraint),这在上一篇文章中有提到过。

  • 而且没有哪个驱动的 plane 支持 Atomic 更新操作,对于具有锁定位(lockout bit)的高级硬件(只有当所有需要更新的参数全部写入寄存器后,更新操作才开始生效),这很容易通过提交前(pre-commit)和提交后(post-commit)的回调接口来实现。但总的来说,硬件需要更大的灵活性。同时也没有什么规范将 plane check 与 plane update 的 commit 阶段区分开来。

  • 任何输出链路的修改都需要更新并使能 primary plane,通常这不是我们所期望的。例如,为 letterbox 或 pillarbox 显示黑色背景的视频叠加层(video overlay)时,根本不需要什么 primary plane。硬件可以做到这一点,所以软件就应该支持这样的能力。

  • 最后,legacy helper 是为第一个 XRandR 实现而设计的。在过去的十年里, DRM 开发人员积累了许多经验教训,包括哪些可以正常工作,哪些应该被简化(通过对 helper 提供更严格的保证),以及在哪些方面需要更大的灵活性来让 helper 变得更有用。

总之,完全重写 helper 代码是合理的,同时也让驱动的转换变得相当容易。当然,最大的变化是 helper 库不仅为每个回调接口提供了整体式模板函数(monolithic template function),而且还提供了局部的独立函数,例如只处理 plane update 或者只处理输出配置修改。除了 Atomic 本身在语义上的一些限制外,它们还可以按任意顺序进行调用,这样才能最大化适应不同的硬件行为。这样驱动程序就可以在 helper 代码和他们自己实现的代码之间进行混合调用并相互匹配,或者根据需要扩充 helper 代码(例如用于检查或修改全局状态)。

那些支持 Runtime PM(运行时电源管理)的驱动程序就是一个很好的例子,在 output disable 和(使用新的配置参数) enable 之间,老的 legacy helper 会去更新 plane 参数。atomic helper 为了保持最好的向后兼容性也是这么做的,但这对于支持 Runtime PM 的驱动程序来说并不是最好的操作流程。当 display pipeline 关闭时,这些驱动程序将 disable 硬件,因此如果在此期间进行更新操作,plane 的更新就会不起作用。所以在提交更新到硬件寄存器时,最好是将 plane 的更新操作作为整个流程的最后一步,事实上 atomic helper 是可以做到这一点的。

新 helper 库的另一个实用特点是,任何用于引导控制流程的派生状态都存储在 state 结构体中 —— 由于 check 和 commit 流程已经被拆分开来,因此它必须存储在 state 结构体中。它允许驱动程序直接覆盖和控制 helper 程序的细节,例如,当需要调整某些全局共享资源时,驱动程序可以强制发起一次 full mode set(以及所需的状态预计算),即使 helper 本身不需要执行 full mode set。由于 helper 库所提供的 check 函数是幂等的 (译者注:所谓幂等,即任意多次执行所产生的效果均与一次执行的效果相同),因此当后面的步骤发现需要在原子更新中包含更多 object 时(同样是由于资源共享),可以重新运行这些 check 函数。

当发现 plane 更新需要一个 full mode set 操作时,这个方法非常管用。但反过来一个 full mode set,可能需要重新计算 plane state,进而产生一个循环,而将 helper 函数设置成幂等和可随意调用的形式将有助于打破这种循环。为了避免发生意外,默认的 check 函数 drm_atomic_helper_check() 会进一步调用下一级的 helper 来检查 plane 更新参数和 mode set 参数变化。但是,对于在这两种类型的更新操作之间具有复杂依赖关系的硬件(比如前面的例子, plane 更新可能需要一个 full mode set,甚至还会发生在不相关的 CRTC 上),可以通过多次调用 helper 函数来应对这一情况,直到预计算状态(precomputed state)达到稳定状态。请注意,这只是为了方便状态校验(state validation),实际提交到硬件的原子更新操作仍应一次性完成,而不需要任何迭代步骤。

此外,所有使用 atomic 接口实现的 legacy 回调函数都已被导出,并可以被明确调用。那些不需要专门处理 legacy 入口函数的驱动程序,只需将当前提供的 helper 函数直接写入 DRM 驱动程序的 ops 函数表中即可。这样驱动程序就可以仍然使用旧平台的代码来保留一些只支持 legacy 接口的功能。这对于那些迭代了十年的硬件驱动来说是非常实用的,在这种情况下,再去为每种老的特殊用法实现对应的 atomic 支持是毫无意义的。

另一个大的变化是驱动程序的语义变得更简单了。由于原子更新,新的 helper 函数可以更好地跟踪 state 的变化,并保证 enable/disable 回调接口不会被冗余调用,而老的 helper 函数则通常会碰到这种问题。新的 helper 函数还屏蔽了许多 legacy 接口潜在的灵活性,比如用于 Runtime PM 输出的四种不同的显示电源管理信号(DPMS)状态 —— atomic helper 只有“ON”和“OFF”两种状态,这是因为现代硬件不再支持 DPMS 中间状态了。除了一些骨灰级的硬件,在任何其他硬件上,驱动程序都只是将 DPMS 中间状态强制转换为 disable 状态。我们还允许 atomic helper 程序重用 Runtime PM 的 enable 和 disable 接口,就像正常 enable/disable output 一样,这简化了驱动程序的编写。当然我们仍然支持 ->dpms() 这样的回调接口,以便简化驱动程序从 legacy mode setting 转换为 atomic 版本的开发过程,但我们还是不推荐使用这些接口。转换后的驱动程序应该只需要实现 ->enable()->disable() 接口即可,而且应该可以完全删除 DPMS 处理代码了。

最后,我们还提供了 transitional helper(过渡型 helper),用来处理 legacy helper 和新的 atomic helper 之间 plane 操作不匹配的问题。这其中包含像 drm_helper_crtc_mode_set_base() 这样的 helper 函数,它使用新的 atomic helper 来实现 legacy helper 的回调。这样可以让驱动程序编写者切换到新的回调接口,从而在内部进行 plane 更新操作,同时又能使用相同的代码和完整的控制流程。这有助于大型驱动程序的转换过程,允许转换发生在多个阶段,每个阶段只处理驱动程序的一部分。还有一个“how to”包含了 atomic 转换的所有细节,内容包括“需要做什么”以及“怎么做”。

总之,有许多驱动程序已经转换到了 atomic helper 上,而且新的 helper 看起来运行得很不错。虽然有时需要对回调接口做一些小的修改,但到目前为止,atomic 语义本身并没有出现什么大的问题。其中一个很重要的原因就是,新开发的 atomic 代码其实是基于 i915.ko 所特有的 mode-set 框架为原型的,该框架是在过去几年里被开发出来的,目的就是为了实现原子更新操作。

从 i915.ko 中吸取到的重大经验教训,促成了 Atomic 代码与 i915 的主要差异:原子更新操作在真正提交到硬件之前,到底是如何组成的。在 i915.ko 中,所有更新参数都暂存到各个 mode-setting object 内嵌的一组 new_ foo 成员变量中,这些成员变量都事先拷贝了一份现有的数据结构和指针。这种方法的主要缺点是,回滚更新操作(可能是因为参数超出了限制条件,也可能是因为用户空间只想使用 TEST_ONLY 模式来检查约束限制)需要小心地撤消所有这些更改。将更新参数暂存到 object 中同样会让多个并发更新操作变得更加困难。在已合入的 atomic 框架中,更新操作都是从完全独立的 state object 中组合而成的,在将 state 真正提交给硬件之前,是不会接触到任何 mode-setting object 的,这使得回滚和并发更新操作变得更加简单。

当前 helper 中还未解决的断层问题就是 non-blocking 更新的处理。到目前为止,驱动程序需要为原子更新的异步 tail 操作管理自己的工作队列,并且当更新依赖于全局 state 时,会进行适当的同步操作,就像他们不得不使用 legacy ioctl() 进行 primary plane page flip 操作一样。目前关于在 atomic helper 中去实现这一方案的想法有很多,但是暂时还没有找到一种方法既能非常简单地改善现状,又能对许多驱动仍然保持实用。

结论

经过三年多的努力,upstream kernel 终于为驱动程序添加了 mode-setting 接口和基础框架的支持。它涵盖了所有的使用场景,从需要专用 plane 硬件的低功耗片上系统(SoCs),到需要支持每帧的完美更新,再到支持多屏的超大桌面系统。这是第一次,因为最初的 kernel mode setting 几乎把嵌入式系统留给了 fbdev。但考虑到正在开发或已经合入的 atomic 转换接口,以及支持该转换的全新驱动程序的数量,看起来面向 SoC 的 upstream graphics 终于出现了,而且一定会持续下去,好吧,至少在 GPU 的 mode-setting 方面会是这样。但即便如此,对于 Android 厂商 kernel tree 的所有 upstreaming 工作来说,这也将是一次巨大的改变和伟大的进步!

让我们一起拥抱 atomic mode-setting 时代吧!美好的未来从现在开始 !


上一篇:LWN 翻译:Atomic Mode Setting 设计简介(上)

文章汇总: DRM (Direct Rendering Manager) 学习简介

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LWN 翻译:Atomic Mode Setting 设计简介(下) 的相关文章

  • 在内核空间中存储结构体数组,Linux

    我相信我可能有点过度思考这个问题 我的文件系统上有一个文本文件 我在启动时解析该文件并将结果存储到结构数组中 我需要将此数组从用户空间复制到内核空间 copy from user 并且必须让内核随时可以访问此数据 内核空间中的数据需要通过
  • 如何制作内核模块二进制 blob

    我想为各种发行版分发专有的 Linux 模块 而不需要为所有发行版预先构建模块 例如我有以下文件 wrapp c mod c fops c 所有wrapp c都是我正在使用的所有内核函数的包装器 unsigned int wrap iore
  • 如何安排Makefile来编译具有多个.c文件的内核模块?

    如何安排Makefile来编译具有多个 c文件的内核模块 这是我当前的 Makefile 它是由自动生成的KDevelop http www kdevelop org TARGET nlb driver OBJS nlb driver o
  • 根据.config剥离Linux内核源代码

    是否有任何有效的方法 也许通过滥用 gcc 预处理器 来获取一组剥离的内核源代码 其中根据 config 不需要的所有代码都被省略 好吧 我们采取了一些解决方案 首先 可以通过以下方式获取所使用的编译器命令 make KBUILD VERB
  • 在 anaconda 中更新 Spyder 后出现内核错误 [重复]

    这个问题在这里已经有答案了 我将 Spyder 更新到版本 4 1 0 以及 anaconda 中的所有其他软件包 Spyder 本身工作正常 但内核无法工作 我收到以下错误 但不知道如何解决它 An error ocurred while
  • Linux 内核驱动程序的探测函数何时被调用?

    我正在尝试更新Android的内核驱动程序 我添加了一些printk来调试它 调用了 init函数 但没有调用probe函数 我缺少什么 何时 如何调用探测函数 该代码可在以下位置获取 https github com lamegopint
  • /arm64/Image 到 zImage 或 boot.img

    大家好 我一直在试图弄清楚如何使我的 android 内核成为 zImage 或 boot img 我试图弄清楚但没有运气 有人告诉我 zImage 不适用于我的设备 因为它是 arm64 内核 但我想我会再问一次 如果是这种情况 我会尝试
  • 在执行期间访问.eh_frame数据

    我正在尝试访问以下内容 eh frame正在运行的程序的一部分 具体来说 该程序是 Linux 内核 2 6 34 8 这 eh frame包含用于异常处理的有用数据 我想在内核代码内部使用它 该部分已经由以下人员编写gcc readelf
  • Linux 内核:Spinlock SMP:为​​什么 spin_lock_irq SMP 版本中有 preempt_disable()?

    Linux内核中的原始代码是 static inline void raw spin lock irq raw spinlock t lock local irq disable preempt disable spin acquire l
  • 转储 $mft 文件的内容

    对于一些商业的我正在做的项目我需要能够读取 mft 文件中存储的实际数据 我找到了一个gpl lib http www codeproject com KB files NTFSParseLib aspx artkw ntfs这可能会有所帮
  • “do { ... } while (0)”在内核代码中到底做了什么? [复制]

    这个问题在这里已经有答案了 可能的重复 当我们定义宏时 do while 0 有什么用 https stackoverflow com questions 923822 whats the use of do while0 when we
  • 在中断时获取 current->pid

    我正在Linux调度程序上写一些东西 我需要知道在我的中断到来之前哪个进程正在运行 当前的结构可用吗 如果我在中断处理程序中执行 current gt pid 我是否可以获得我中断的进程的 pid 你可以 current gt pid存在并
  • 没有设备的设备驱动程序?

    我正在创建一个需要使用一些内核级模块的应用程序 为此我将应用程序分为 2 个 一个用户级程序和一个内核级程序 在阅读了有关设备驱动程序并浏览一些教程后 我有点困惑 是否可以存在没有任何特定设备与之关联的设备驱动程序 除了设备驱动程序 内核代
  • Linux内核页表更新

    在linux x86 中分页 每个进程都有它自己的页面目录 页表遍历从 CR3 指向的页目录开始 每个进程共享内核页目录内容 假设三个句子是正确的 假设某个进程进入内核 模式并更新他的内核页目录内容 地址映射 访问 权利等 问题 由于内核地
  • 在Linux中断上下文中运行用户线程

    我正在编写一些定制的应用程序 并允许更改 Linux 内核中的中断处理程序代码 我有一个用户线程正在等待中断发生 如果发生中断 那么我要做的第一件事就是执行该用户线程 有什么办法让它发挥作用吗 Thanks 创建一个字符设备 这就是内核所做
  • 内核开发和 C++ [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 从我know https stackoverflow com questions 580292 what languages are windo
  • 如何在Linux内核源代码中打印IP地址或MAC地址

    我必须通过修改 Linux 内核源代码来稍微改变 TCP 拥塞控制算法 但为了检查结果是否正确 我需要记录 MAC 或 IP 地址信息 我使用 PRINTK 函数来打印内核消息 但我感觉很难打印出主机的MAC IP地址 printk pM
  • 内核makefile中的$(call cmd,tags)这里的cmd指的是什么?

    在内核 Makefile 中我发现如下代码 ctags CTAGS CSCOPE HEADERS SOURCES ETAGS ETAGSFALGS HEADERS SOURCES call cmd ctags 另外 在哪里可以找到宏或函数
  • 如何编译一个简单的 multiboot2 裸机可执行文件?

    我想开始写一个操作系统内核 然后 我找到了一个document http nongnu askapache com grub phcoder multiboot pdf引入 multiboot2 规范 有三个示例代码文件 名为boot S
  • 检测从内核扩展的文件复制

    我正在尝试构建 POC 该 POC 可以使用 fileop 范围回调来识别来自基于 kauth 的内核扩展的文件复制活动 但是 复制文件似乎涉及两个单独的身份验证操作 从中打开 src 文件并创建新文件 我的目标相当简单 在填充数据后检测新

随机推荐

  • SQLServer创建用户登录

    创建用户登录注意事项 密码是区分大小写的 只有创建SQL Server登录时 才支持对密码预先进行哈希运算 如果指定MUST CHANGE 则CHECK EXPIRATION和 CHECK POLICY必须设置为 ON 否则 该语句将失败
  • 硬件问题总结

    STM32最小系统 F103 1 STM32最小系统 1 1 供电 3 3V 通常使用ASM1117进行5V转3 3V 注意 给单片机供电时要加0 1uf滤波电容 电容要尽可能靠近单片机 1 2外部晶振电路 一般使用外部8M晶振来作为STM
  • C++访问类中私有成员变量的方法

    原则上 C 类中私有变量不允许在类之外的其他任何地方访问 一般来说功能完善的类都会提供get set方法来操作类属性值 还有就是就是通过友元访问 但是 但如果没有get set方法都没有提供 也没有定义友元 比如使用的是第三方提供的 o 或
  • 华为OD机试(B卷)

    华为OD机试 B卷 有幸接到了华为OD的机试邀请 三道算法题 比较幸运的是 最后一题 也相对来说不算太难 没有抽到动态规划 还是花了大约90分钟 三道题都通过了 最终拿到了满分 第一题第二题都相对来说比较基础 由于平时也没有刷题的习惯 用自
  • Using MySQL Enterprise Backup Tools

    本文主要介绍MySQL Enterprise Backup的安装以及使用 1 安装 从官方网站下载MySQL Enterprise Backup安装包 推荐下载rpm软件包 root mydb01 rpm qpl meb 4 1 1 el7
  • Uncaught (in promise) DOMException: Failed to execute 'open' on 'XMLHttpRequest': Invalid URL

    解决方案 url前面一定要加http
  • Python版的BS期权定价模型和希腊值分析

    我比较懒 主要是打理自己的github的更新 是关于量化投资 机器学习策略相关的项目 https github com Neural Finance 这次更新一个我在学习期权定价过程中 Black Scholes Model 和相关的希腊值
  • webpack 中压缩代码

    阅读 深入浅出的webpack 压缩代码 一 为什么要压缩代码 浏览器通过服务器访问网页时获取的JavaScript CSS资源都是文本形式的 文件越大 网页加载的时间越长 对这些资源进行压缩 1 可以提升网页加载速度和减少网络传输流量 2
  • Java基础知识查阅表(四)[线程、网络编程、注解、java8新特性]

    文章目录 Java中的线程 线程的分类 线程调度规则 获取线程的优先级 其他几个方法 线程的通信 守护线程 线程的生命周期 线程安全问题 线程安全的类 ReentrantLock加锁 关于锁的面试题 定时器Timer Java网络编程 两个
  • 数据结构—顺序表基本操作(c语言代码)

    顺序表 计算机内部存储一张线性表是用一组连续地址内存单元 这种存储结构即为顺序存储结构 这种结构下的线性表叫顺序表 顺序表有两种定义方法 1 静态定义 2 动态生成 顺序表是最简单的一种线性存储结构 优点 构造简单 操作方便 通过顺序表的首
  • python装饰器原理

    装饰器作用 装饰器在实际开发中应用广发 如 1 引入日志 2 函数执行时间统计 3 执行函数前预备处理 4 执行函数后清理功能 5 权限校验等场景 6 缓存 装饰器可以实现在不修改之前已经写好并且封装好的代码的前提下对之前的代码进行功能上的
  • LASlib/LAStools:Win10 + VS2017 编译LASlib/LAStools

    一 下载解压 下载地址 http lastools github io download LAStools zip 解压地址 G LAStools 二 编译 2 1 打开 用VS2017打开lastools dsw 历史原因 一直点确定就可
  • Linux shell 从文件中随机选择内容

    如果需要从文件中随机选择一定行的内容 可以借助sort 命令 如下 使用sort 命令将文件随机排序 选择前100行 sort random sort file head n 100
  • 《自然语言处理》第二次作业:语言模型和文本分类

    文章目录 作业要求 代码 读取数据集 建立二元语法模型 朴素贝叶斯分类 分类和评估 计算困惑度 完整代码 运行结果 作业要求 题目 语言模型和文本分类 数据集 text classification data用户评论 包括训练集 开发测试集
  • 三位数除以两位数怎么算竖式_四年级数学上册三位数除以两位数竖式笔算专项练习(10套)...

    四年级数学上册三位数除以两位数竖式笔算专项练习 一 三位数除以两位数的除法 包括以下两部分 一 三位数除以整十数 如 二 三位数除以两位数 二 除数是两位数的除法法则 从被除数左边的高位起 先用除数试除被除数的前两位数 如果它比除数小 再试
  • 深入浅出讲解 NAT 和 UDP/TCP 点对点通讯

    深入浅出讲解 NAT 和 UDP TCP 点对点通讯 转自 http blog csdn net g brightboy article details 12704933 一 什么是NAT 为什么要使用NAT NAT是将私有地址转换为合法I
  • Java面向对象(基础总结)

    Java面向对象 基础总结 面向对象是一种编程思想 面向对象的三大基本特征 封装 继承 多态 面向对象的编程思想就是把事物看作一个整体 从事物的特征 属性 和行为 方法 两个方面进行描述 面向对象的过程就是找对象 建立对象 使用对象 维护对
  • angular中涉及rxjs请求beego接口跨域问题解决

    今天遇到一个调用服务端接口跨域问题 我用本地的angular运行项目 访问本地的beego接口 发现请求接口状态404 并且接口方法还是OPTIONS 一查知道是跨域了 在网上搜索一些跨域访问的方法 发现跨域时访问可以了 但正常post接口
  • Java中的代理(二)--JDK动态代理

    JDK动态代理借助接口实现 目标类需是接口形式 代理类继承InvocationHandler类 通过反射方式动态创建目标类 1 目标对象 public interface ByShoot void byShoot String size p
  • LWN 翻译:Atomic Mode Setting 设计简介(下)

    译者注 紧接上篇文章 本篇翻译起来有难度 同时对读者的技术背景有一定要求 适合深入研究 DRM 驱动的开发人员阅读 通过阅读本文 你将了解如下内容 DRM MODE ATOMIC ALLOW MODESET 标志位的由来及其作用 驱动中随处