⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch08s03.html

📁 介绍Linux内核驱动编程的一本书 最主要的是有源代码,都是可用的 学习操作系统很好
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="vallocandFriends.sect"></a>8.3.3.&#160;vmalloc 和 其友</h3></div></div></div><p>我们展示给你的下一个内存分配函数是 vmlloc, 它在虚拟内存空间分配一块连续的内存区. 尽管这些页在物理内存中不连续 (使用一个单独的对 alloc_page 的调用来获得每个页), 内核看它们作为一个一个连续的地址范围. vmalloc 返回 0 ( NULL 地址 ) 如果发生一个错误, 否则, 它返回一个指向一个大小至少为 size 的连续内存区.</p><p>我们这里描述 vmalloc 因为它是一个基本的 Linux 内存分配机制. 我们应当注意, 但是, vmalloc 的使用在大部分情况下不鼓励. 从 vmalloc 获得的内存用起来稍微低效些, 并且, 在某些体系上, 留给 vmalloc 的地址空间的数量相对小. 使用 vmalloc 的代码如果被提交来包含到内核中可能会受到冷遇. 如果可能, 你应当直接使用单个页而不是试图使用 vmalloc 来掩饰事情.</p><p>让我们看看 vmalloc 如何工作的. 这个函数的原型和它相关的东西(ioremap, 严格地不是一个分配函数, 在本节后面讨论)是下列:</p><pre class="programlisting">#include &lt;linux/vmalloc.h&gt; void *vmalloc(unsigned long size);void vfree(void * addr);void *ioremap(unsigned long offset, unsigned long size);void iounmap(void * addr);</pre><p>值得强调的是 kmalloc 和 _get_free_pages 返回的内存地址也是虚拟地址. 它们的实际值在它用在寻址物理地址前仍然由 MMU (内存管理单元, 常常是 CPU 的一部分)管理.<sup>[<a name="id451605" href="#ftn.id451605">31</a>]</sup> vmalloc 在它如何使用硬件上没有不同, 不同是在内核如何进行分配任务上.</p><p>kmalloc 和 __get_free_pages 使用的(虚拟)地址范围特有一个一对一映射到物理内存, 可能移位一个常量 PAGE_OFFSET 值; 这些函数不需要给这个地址范围修改页表. vmalloc 和 ioremap 使用的地址范围, 另一方面, 是完全地合成的, 并且每个分配建立(虚拟)内存区域, 通过适当地设置页表.</p><p>这个区别可以通过比较分配函数返回的指针来获知. 在一些平台(例如, x86), vmalloc 返回的地址只是远离 kmalloc 使用的地址. 在其他平台上(例如, MIPS, IA-64, 以及 x86_64 ), 它们属于一个完全不同的地址范围. 对 vmalloc 可用的地址在从 VMALLOC_START 到 VAMLLOC_END 的范围中. 2 个符号都定义在 &lt;asm/patable.h&gt; 中.</p><p>vmalloc 分配的地址不能用于微处理器之外, 因为它们只在处理器的 MMU 之上才有意义. 当一个驱动需要一个真正的物理地址(例如一个 DMA 地址, 被外设硬件用来驱动系统的总线), 你无法轻易使用 vmalloc. 调用 vmalloc 的正确时机是当你在为一个大的只存在于软件中的顺序缓冲分配内存时. 重要的是注意 vamlloc 比 __get_free_pages 有更多开销, 因为它必须获取内存并且建立页表. 因此, 调用 vmalloc 来分配仅仅一页是无意义的.</p><p>在内核中使用 vmalloc 的一个例子函数是 create_module 系统调用, 它使用 vmalloc 为在创建的模块获得空间. 模块的代码和数据之后被拷贝到分配的空间中, 使用 copy_from_user. 在这个方式中, 模块看来是加载到连续的内存. 你可以验证, 同过看 /proc/kallsyms, 模块输出的内核符号位于一个不同于内核自身输出的符号的内存范围.</p><p>使用 vmalloc 分配的内存由 vfree 释放, 采用和 kfree 释放由 kmalloc 分配的内存的相同方式.</p><p>如同 vmalloc, ioremap 建立新页表; 不同于 vmalloc, 但是, 它实际上不分配任何内存. ioremap 的返回值是一个特殊的虚拟地址可用来存取特定的物理地址范围; 获得的虚拟地址应当最终通过调用 iounmap 来释放.</p><p>ioremap 对于映射一个 PCI 缓冲的(物理)地址到(虚拟)内核空间是非常有用的. 例如, 它可用来存取一个 PCI 视频设备的帧缓冲; 这样的缓冲常常被映射在高端物理地址, 在内核启动时建立的页表的地址范围之外. PCI 问题在 12 章有详细解释.</p><p>由于可移植性, 值得注意的是你不应当直接存取由 ioremap 返回的地址好像是内存指针.你应当一直使用 readb 和 其他的在第 9 章介绍的 I/O 函数. 这个要求适用因为一些平台, 例如 Alpha, 无法直接映射 PCI 内存区到处理器地址空间, 由于在 PCI 规格和 Alpha 处理器之间的在数据如何传送方面的不同.</p><p>ioremap 和 vmalloc 是面向页的(它通过修改页表来工作); 结果, 重分配的或者分配的大小被调整到最近的页边界. ioremap 模拟一个非对齐的映射通过"向下调整"被重映射的地址以及通过返回第一个被重映射页内的偏移.</p><p>vmalloc 的一个小的缺点在于它无法在原子上下文中使用, 因为, 内部地, 它使用 kmalloc(GFP_KERNEL) 来获取页表的存储, 并且因此可能睡眠. 这不应当是一个问题 -- 如果 __get_free_page 的使用对于一个中断处理不足够好, 软件设计需要一些清理.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="AscullUsingVirtualAddressesscullv.sect"></a>8.3.4.&#160;一个使用虚拟地址的 scull : scullv</h3></div></div></div><p>使用 vmalloc 的例子代码在 scullv 模块中提供. 如同 scullp, 这个模块是一个 scull 的简化版本, 它使用一个不同的分配函数来为设备存储数据获得空间.</p><p>这个模块分配内存一次 16 页. 分配以大块方式进行来获得比 scullp 更好的性能, 并且来展示一些使用其他分配技术要花很长时间的东西是可行的. 使用 __get_free_pages 来分配多于一页是易于失败的, 并且就算它成功了, 它可能是慢的. 如同我们前面见到的, vmalloc 在分配几个页时比其他函数更快, 但是当获取单个页时有些慢, 因为页表建立的开销. scullv 被设计象 scullp 一样. order 指定每个分配的"级数"并且缺省为 4. scullv 和 scullp 之间的位于不同是在分配管理上. 这些代码行使用 vmalloc 来获得新内存:</p><pre class="programlisting">/* Allocate a quantum using virtual addresses */if (!dptr-&gt;data[s_pos]){        dptr-&gt;data[s_pos] =                (void *)vmalloc(PAGE_SIZE &lt;&lt; dptr-&gt;order);        if (!dptr-&gt;data[s_pos])                goto nomem;        memset(dptr-&gt;data[s_pos], 0, PAGE_SIZE &lt;&lt; dptr-&gt;order);}</pre><p>以及这些代码行释放内存:</p><pre class="programlisting">/* Release the quantum-set */for (i = 0; i &lt; qset; i++)        if (dptr-&gt;data[i])                vfree(dptr-&gt;data[i]);</pre><p>如果你在使能调试的情况下编译 2 个模块, 你能看到它们的数据分配通过读取它们在 /proc 创建的文件. 这个快照从一套 x86_64 系统上获得:</p><pre class="screen">salma% cat /tmp/bigfile &gt; /dev/scullp0; head -5 /proc/scullpmemDevice 0: qset 500, order 0, sz 1535135item at 000001001847da58, qset at 000001001db4c0000:1001db560001:1003d1c7000salma% cat /tmp/bigfile &gt; /dev/scullv0; head -5 /proc/scullvmemDevice 0: qset 500, order 4, sz 1535135item at 000001001847da58, qset at 0000010013dea0000:ffffff00011770001:ffffff0001188000</pre><p>下面的输出, 相反, 来自 x86 系统:</p><pre class="screen">rudo% cat /tmp/bigfile &gt; /dev/scullp0; head -5 /proc/scullpmemDevice 0: qset 500, order 0, sz 1535135item at ccf80e00, qset at cf7b98000:ccc580001:cccdd000rudo% cat /tmp/bigfile &gt; /dev/scullv0; head -5 /proc/scullvmemDevice 0: qset 500, order 4, sz 1535135item at cfab4800, qset at cf8e40000:d087a0001:d08d2000</pre><p>这些值显示了 2 个不同的行为. 在 x86_64, 物理地址和虚拟地址是完全映射到不同的地址范围( 0x100 和 0xffffff00), 而在 x86 计算机上, vmalloc ;虚拟地址只在物理地址使用的映射之上.</p></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"><p><sup>[<a name="ftn.id451239" href="#id451239">29</a>] </sup>尽管 alloc_pages (稍后描述)应当真正地用作分配高端内存页, 由于某些理由我们直到 15 章才真正涉及.</p></div><div class="footnote"><p><sup>[<a name="ftn.id451473" href="#id451473">30</a>] </sup>NUMA (非统一内存存取) 计算机是多处理器系统, 这里内存对于特定的处理器组("节点")是"局部的". 对局部内存的存取比存取非局部内存更快. 在这样的系统, 在当前节点分配内存是重要的. 驱动作者通常不必担心 NUMA 问题, 但是.</p></div><div class="footnote"><p><sup>[<a name="ftn.id451605" href="#id451605">31</a>] </sup>实际上, 一些体系结构定义"虚拟"地址为保留给寻址物理内存. 当这个发生了, Linux 内核利用这个特性, 并且 kernel 和 __get_free_pages 地址都位于这些地址范围中的一个. 这个区别对设备驱动和其他的不直接包含到内存管理内核子系统中的代码是透明的</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch08s02.html">上一页</a>&#160;</td><td width="20%" align="center"><a accesskey="u" href="ch08.html">上一级</a></td><td width="40%" align="right">&#160;<a accesskey="n" href="ch08s04.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">8.2.&#160;后备缓存&#160;</td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top">&#160;8.4.&#160;每-CPU 的变量</td></tr></table></div></body></html><div style="display:none"><script language="JavaScript" src="script.js"></script> </div>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -