Linux内存管理:Fixmaps(固定映射地址)和ioremap【转】
转自:https://rtoax.blog.csdn.net/article/details/114749083
目录
Fix-Mapped
地址是一组特殊的编译时地址,其对应的物理地址不必是线性地址减__START_KERNEL_map;
ioremap 功能是将给定的物理地址映射为虚拟地址。
Fixmaps和ioremap
Fix-Mapped
地址是一组特殊的编译时地址,其对应的物理地址不必是线性地址减__START_KERNEL_map
。每个固定映射的地址都映射一个页面框架,内核将其用作永远不会更改其地址的指针。这就是这些地址的重点。如评论所述:to have a constant address at compile time, but to set the physical address only in the boot process
。您可能还记得,在最早的部分中,我们已经设置了level2_fixmap_pgt
:
-
NEXT_PAGE(level2_fixmap_pgt)
-
.fill 506,8,0
-
.quad level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
-
.fill 5,8,0
-
-
NEXT_PAGE(level1_fixmap_pgt)
-
.fill 512,8,0
如您所见level2_fixmap_pgt
,紧随其后的level2_kernel_pgt
是内核代码+数据+ bss。每个修订映射的地址都由一个整数索引表示,该整数索引fixed_addresses
在arch / x86 / include / asm / fixmap.h中的枚举中定义。例如,它包含以下各项的条目VSYSCALL_PAGE
-如果启用了对旧版vsyscall页面的仿真,FIX_APIC_BASE
则为本地apic等。在虚拟内存中,固定映射区域位于模块区域:
-
+-----------+-----------------+---------------+------------------+
-
| | | | |
-
|kernel text| kernel | | vsyscalls |
-
| mapping | text | Modules | fix-mapped |
-
|from phys 0| data | | addresses |
-
| | | | |
-
+-----------+-----------------+---------------+------------------+
-
__START_KERNEL_map __START_KERNEL MODULES_VADDR 0xffffffffffffffff
基本的虚拟地址和fix-mapped
区域的大小由以下两个宏表示:
这__end_of_permanent_fixed_addresses
是fixed_addresses
枚举的一个元素,正如我上面所写:每个固定映射的地址都由一个整数索引表示,该索引在中定义fixed_addresses
。PAGE_SHIFT
确定页面的大小。例如,我们可以通过1 << PAGE_SHIFT
表达式获得一页的大小。
在我们的例子中,我们需要获取修订映射区域的大小,而不仅仅是一个page,这就是为什么我们__end_of_permanent_fixed_addresses
要获取修订映射区域的大小的原因。的__end_of_permanent_fixed_addresses
是最后索引fixed_addresses
枚举或换句话说,__end_of_permanent_fixed_addresses
包含在一个固定的映射区域页面量。因此,如果将__end_of_permanent_fixed_addresses
页面大小值的乘积,我们将获得固定映射区域的大小。以我的为例,它超过了536
千字节。在您的情况下,它可能是一个不同的数字,因为大小取决于修订映射地址的数量,而修订映射地址的数量取决于内核的配置。
第二个FIXADDR_START
宏只是从固定映射区域的最后一个地址中减去固定映射区域的大小,以获取其基本虚拟地址。FIXADDR_TOP
是从vsyscall空间的基地址开始的四舍五入地址:
#define FIXADDR_TOP (round_up(VSYSCALL_ADDR + PAGE_SIZE, 1<<PMD_SHIFT) - PAGE_SIZE)
该fixed_addresses
枚举被用作指数由获得虚拟地址fix_to_virt
的功能。该功能的实现很容易:
-
static __always_inline unsigned long fix_to_virt(const unsigned int idx)
-
{
-
BUILD_BUG_ON(idx >= __end_of_fixed_addresses);
-
return __fix_to_virt(idx);
-
}
首先,它检查为该fixed_addresses
枚举给出的索引是否大于或等于__end_of_fixed_addresses
该BUILD_BUG_ON
宏,然后返回该__fix_to_virt
宏的结果:
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
在这里,我们向左移动fix-mapped
区域的给定索引,该索引PAGE_SHIFT
决定了我在上面所写的页面大小,并从区域FIXADDR_TOP
的最高地址那里减去它fix-mapped
:
-
+-----------------+
-
| PAGE 1 | FIXADDR_TOP (virt address)
-
| PAGE 2 |
-
| PAGE 3 |
-
| PAGE 4 (idx) | x - 4
-
| PAGE 5 |
-
+-----------------+
有一个反函数,用于获取与给定虚拟地址相对应的修订映射区域的索引:
-
static inline unsigned long virt_to_fix(const unsigned long vaddr)
-
{
-
BUG_ON(vaddr >= FIXADDR_TOP || vaddr < FIXADDR_START);
-
return __virt_to_fix(vaddr);
-
}
在virt_to_fix
采用虚拟地址,检查该地址是间FIXADDR_START
和FIXADDR_TOP
并调用__virt_to_fix
其实现为宏:
#define __virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)
正如我们可以看到的,__virt_to_fix
宏清除第一12
在给定的虚拟地址位,从最后地址的减去它fix-mapped
区域(FIXADDR_TOP
),并转移该结果正确的PAGE_SHIFT
是12
。让我解释一下它是如何工作的。
与前面的示例一样(在__fix_to_virt
宏中),我们从修复映射区域的顶部开始。我们还从上到下从头到尾搜索与给定虚拟地址相对应的固定映射区域的索引。如您所见,首先我们将12
使用x & PAGE_MASK
表达式清除给定虚拟地址中的前几位。这使我们能够获取页面的基地址。如果给定的虚拟地址指向页面的开头/中间或结尾的某个位置,而不指向页面的基地址,则需要执行此操作。在下一步中,从中减去此值FIXADDR_TOP
,这将为我们提供修订映射区域中相应页面的虚拟地址。最后,我们将这个地址的值除以PAGE_SHIFT
。这为我们提供了与给定虚拟地址相对应的固定映射区域的索引。看起来可能很难,但是如果您逐步进行此操作,则可以确保该__virt_to_fix
宏非常简单。
就这样。现在,我们对fix-mapped
地址有所了解,但是接下来就足够了。
Fix-mapped
地址在linux内核中的不同位置使用。IDT
描述符存储在此处,英特尔可信执行技术UUID存储在fix-mapped
从FIX_TBOOT_BASE
索引,Xen bootmap等开始的区域中...我们已经在linux内核初始化fix-mapped
的第五部分中看到了一些地址。我们fix-mapped
在早期ioremap
初始化中使用area 。让我们更仔细地看一下它,并尝试了解ioremap
它是什么,如何在内核中实现它以及它与fix-mapped
地址的关系。
映射
Linux内核提供了许多不同的原语来管理内存。此时此刻,提出I/O memory
。每个设备都通过对其寄存器的读/写操作来控制。例如,驱动程序可以通过写入设备的寄存器来关闭/打开设备,或者通过读取设备的寄存器来获取设备的状态。除寄存器外,许多设备还具有缓冲区,驱动程序可以在其中写入或读取内容。众所周知,目前有两种访问设备寄存器和数据缓冲区的方法:
- 通过I / O端口;
- 将所有寄存器映射到存储器地址空间;
在第一种情况下,设备的每个控制寄存器都具有多个输入和输出端口。设备驱动程序可以从一个端口读取和写入两个in
和out
指示,我们已经看到了。如果您想了解当前注册的端口区域,可以通过访问来了解它们/proc/ioports
:
-
$ cat /proc/ioports
-
0000-0cf7 : PCI Bus 0000:00
-
0000-001f : dma1
-
0020-0021 : pic1
-
0040-0043 : timer0
-
0050-0053 : timer1
-
0060-0060 : keyboard
-
0064-0064 : keyboard
-
0070-0077 : rtc0
-
0080-008f : dma page reg
-
00a0-00a1 : pic2
-
00c0-00df : dma2
-
00f0-00ff : fpu
-
00f0-00f0 : PNP0C04:00
-
03c0-03df : vesafb
-
03f8-03ff : serial
-
04d0-04d1 : pnp 00:06
-
0800-087f : pnp 00:01
-
0a00-0a0f : pnp 00:04
-
0a20-0a2f : pnp 00:04
-
0a30-0a3f : pnp 00:04
-
0cf8-0cff : PCI conf1
-
0d00-ffff : PCI Bus 0000:00
-
...
-
...
-
...
/proc/ioports
提供有关哪个驱动程序使用I/O
端口区域的哪个地址的信息。所有这些存储器区域的,例如0000-0cf7
,用所要求保护的request_region
功能从包括/ LINUX / ioport.h。实际上request_region
是一个宏,定义为:
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)
如我们所见,它带有三个参数:
start
-地区开始;n
-区域的长度;name
-请求者名称。
request_region
分配I/O
端口区域。通常在check_region
之前调用该函数request_region
以检查给定的地址范围是否可用以及release_region
释放存储区域的函数。request_region
返回指向该resource
结构的指针。该resource
结构表示系统资源的树状子集的抽象。我们已经resource
在内核初始化过程的第五部分中看到了该结构,它看起来如下:
-
struct resource { //该resource结构表示系统资源的树状子集的抽象
-
resource_size_t start;
-
resource_size_t end;
-
const char *name;
-
unsigned long flags;
-
struct resource *parent, *sibling, *child;
-
};
并包含资源,名称等的开始和结束地址每一个resource
结构包含指向的parent
,sibling
和child
资源。因为它有一个父母和一个孩子,这意味着资源的每个子集都具有根resource
结构。例如,对于I/O
端口,它是以下ioport_resource
结构:
-
struct resource ioport_resource = {
-
.name = "PCI IO",
-
.start = 0,
-
.end = IO_SPACE_LIMIT,
-
.flags = IORESOURCE_IO,
-
};
-
EXPORT_SYMBOL(ioport_resource);
或者iomem
,它是iomem_resource
结构:
-
struct resource iomem_resource = {
-
.name = "PCI mem",
-
.start = 0,
-
.end = -1,
-
.flags = IORESOURCE_MEM,
-
};
正如我之前提到的,request_regions
它用于注册I / O端口区域,并且该宏在内核中的许多地方都使用。例如,让我们看一下drivers/char/rtc.c。此源代码文件在linux内核中提供了Real Time Clock接口。作为每个内核模块,rtc
模块包含module_init
定义:
module_init(rtc_init);
这里rtc_init
是rtc
初始化函数。此功能在同一rtc.c
源代码文件中定义。在rtc_init
函数中,我们可以看到对函数的几个调用,例如rtc_request_region
包装request_region
:
r = rtc_request_region(RTC_IO_EXTENT);
rtc_request_region被调用
:
r = request_region(RTC_PORT(0), size, "rtc");
这RTC_IO_EXTENT
是内存区域的大小,它是0x8
,"rtc"
是该区域的名称,并且RTC_PORT
是:
#define RTC_PORT(x) (0x70 + (x))
因此,request_region(RTC_PORT(0), size, "rtc")
我们使用来注册一个存储区域,该存储区域的起始位置为0x70
,大小为0x8
。让我们看一下/proc/ioports
:
-
~$ sudo cat /proc/ioports | grep rtc
-
0070-0077 : rtc0
所以,我们明白了!好的,就是I / O端口了。与驱动程序通信的第二种方法是使用I/O
内存。如前所述,这是通过将控制寄存器和设备内存映射到内存地址空间来实现的。I/O
内存是设备通过总线向CPU提供的一组连续地址。内核没有直接使用任何内存映射的I / O地址。有一个特殊ioremap
功能允许我们将总线上的物理地址转换为内核虚拟地址。换句话说,ioremap
映射I / O物理内存区域以使其可从内核访问。该ioremap
函数有两个参数:
- 内存区域的开始;
- 内存区域的大小;
I / O内存映射API提供了用于检查,请求和释放作为I / O内存的内存区域的功能。为此,有三个功能:
request_mem_region
release_mem_region
check_mem_region
-
~$ sudo cat /proc/iomem
-
...
-
...
-
...
-
be826000-be82cfff : ACPI Non-volatile Storage
-
be82d000-bf744fff : System RAM
-
bf745000-bfff4fff : reserved
-
bfff5000-dc041fff : System RAM
-
dc042000-dc0d2fff : reserved
-
dc0d3000-dc138fff : System RAM
-
dc139000-dc27dfff : ACPI Non-volatile Storage
-
dc27e000-deffefff : reserved
-
defff000-deffffff : System RAM
-
df000000-dfffffff : RAM buffer
-
e0000000-feafffff : PCI Bus 0000:00
-
e0000000-efffffff : PCI Bus 0000:01
-
e0000000-efffffff : 0000:01:00.0
-
f7c00000-f7cfffff : PCI Bus 0000:06
-
f7c00000-f7c0ffff : 0000:06:00.0
-
f7c10000-f7c101ff : 0000:06:00.0
-
f7c10000-f7c101ff : ahci
-
f7d00000-f7dfffff : PCI Bus 0000:03
-
f7d00000-f7d3ffff : 0000:03:00.0
-
f7d00000-f7d3ffff : alx
-
...
-
...
-
...
这些地址的一部分来自该e820_reserve_resources
函数的调用。我们可以在arch/x86/kernel/setup.c找到对此函数的调用,并且函数本身在arch/x86/kernel/e820.c.中定义。e820_reserve_resources
遍历e820映射并将内存区域插入到根iomem
资源结构中。e820
插入iomem
资源中的所有内存区域具有以下类型:
-
static inline const char *e820_type_to_string(int e820_type)
-
{
-
switch (e820_type) {
-
case E820_RESERVED_KERN:
-
case E820_RAM: return "System RAM";
-
case E820_ACPI: return "ACPI Tables";
-
case E820_NVS: return "ACPI Non-volatile Storage";
-
case E820_UNUSABLE: return "Unusable memory";
-
default: return "reserved";
-
}
-
}
我们可以在/proc/iomem
(如上所示)中看到它们。
ioremap
工作原理
ioremap
功能允许我们将总线上的物理地址转换为内核虚拟地址
现在,让我们尝试了解其ioremap
工作原理。我们已经了解了一点ioremap
,我们在第五部分中了解了Linux内核初始化。如果您已阅读此部分,则可以记住arch / x86 / mm / ioremap.c中的early_ioremap_init
函数调用。的初始化是分为两个部分:有,我们可以前的正常使用初期可用且正常这是后可用初始化和调用。我们暂时一无所知,所以让我们考虑对的早期初始化。首先检查在页面中间目录边界上对齐的内容:ioremap
ioremap
ioremap
vmalloc
paging_init
vmalloc
ioremap
early_ioremap_init
fixmap
BUILD_BUG_ON((fix_to_virt(0) + PAGE_SIZE) & ((1 << PMD_SHIFT) - 1));
关于BUILD_BUG_ON
您的更多信息,可以在第一部分中阅读有关Linux Kernel初始化的信息。因此,BUILD_BUG_ON
如果给定的表达式为true,则macro会引发编译错误。在检查之后的下一步中,我们可以early_ioremap_setup
从mm / early_ioremap.c中看到该函数的调用。此函数提供的通用初始化ioremap
。early_ioremap_setup
函数slot_virt
使用早期Fixmap的虚拟地址填充数组。所有早期的修订图都__end_of_permanent_fixed_addresses
在内存中。它们从FIX_BITMAP_BEGIN
(顶部)开始,以FIX_BITMAP_END
(向下)结束。实际上512
,早期有一些临时的启动时映射ioremap
:
和early_ioremap_setup
:
-
void __init early_ioremap_setup(void)
-
{
-
int i;
-
-
for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
-
if (WARN_ON(prev_map[i]))
-
break;
-
-
for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
-
slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
-
}
的slot_virt
和其它阵列在相同的源代码文件中定义:
-
static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata;
-
static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata;
-
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;
slot_virt
包含区域的虚拟地址fix-mapped
,prev_map
数组包含早期ioremap区域的地址。请注意,我在上面写过:Actually there are 512 temporary boot-time mappings, used by early ioremap
并且您可以看到所有数组都使用__initdata
属性定义,这意味着该内存将在内核初始化过程后释放。后early_ioremap_setup
完成其工作,我们正在页中间目录,早期的ioremap与开始early_ioremap_pmd
刚刚得到页全局目录的基地址和计算给定的地址页面中间目录功能:
-
static inline pmd_t * __init early_ioremap_pmd(unsigned long addr)
-
{
-
pgd_t *base = __va(read_cr3_pa());
-
pgd_t *pgd = &base[pgd_index(addr)];
-
pud_t *pud = pud_offset(pgd, addr);
-
pmd_t *pmd = pmd_offset(pud, addr);
-
return pmd;
-
}
之后,我们bm_pte
用零填充(早期的ioremap页面表条目)并调用pmd_populate_kernel
函数:
-
pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN));
-
memset(bm_pte, 0, sizeof(bm_pte));
-
pmd_populate_kernel(&init_mm, pmd, bm_pte);
pmd_populate_kernel
带有三个参数:
init_mm
-init
进程的内存描述符(您可以在上一部分中了解它);pmd
ioremap
-Fixmaps 开头的页面中间目录;bm_pte
-早期ioremap
页面表条目数组,其定义为:
static pte_t bm_pte[PAGE_SIZE/sizeof(pte_t)] __page_aligned_bss;
该pmd_populate_kernel
函数在arch / x86 / include / asm / pgalloc.h中定义,并pmd
使用给定的页面表项(bm_pte
)填充作为参数提供的页面中间目录():
-
static inline void pmd_populate_kernel(struct mm_struct *mm,
-
pmd_t *pmd, pte_t *pte)
-
{
-
paravirt_alloc_pte(mm, __pa(pte) >> PAGE_SHIFT);
-
set_pmd(pmd, __pmd(__pa(pte) | _PAGE_TABLE));
-
}
在哪里set_pmd
:
#define set_pmd(pmdp, pmd) native_set_pmd(pmdp, pmd)
并且native_set_pmd
是:
-
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
-
{
-
*pmdp = pmd;
-
}
就这样。早就ioremap
可以使用了。early_ioremap_init
函数中有几项检查,但是它们并不是那么重要,无论如何它们的初始化ioremap
已完成。
早期ioremap的使用
一旦ioremap
成功完成早期设置,我们就可以使用它。它提供两个功能:
- early_ioremap
- early_iounmap
用于将I / O物理地址映射/取消映射到虚拟地址。两种功能均取决于CONFIG_MMU
配置选项。内存管理单元是内存管理的特殊模块。该块的主要目的是将物理地址转换为虚拟地址。存储器管理单元pgd
从cr3
控制寄存器中了解高级页表地址()。如果CONFIG_MMU
options设置为n
,则early_ioremap
仅返回给定的物理地址,early_iounmap
而不执行任何操作。如果CONFIG_MMU
option设置为y
,则early_ioremap
调用__early_ioremap
将使用三个参数:
phys_addr
-I/O
存储器区域的基本物理地址,以映射到虚拟地址上;size
-I/O
内存区域的大小;prot
-页表入口位。
首先__early_ioremap
,我们将遍历所有早期的ioremap fixmap插槽,并搜索prev_map
数组中的第一个空闲插槽。当我们找到它时,我们会记住它在slot
变量中的编号并设置大小:
-
slot = -1;
-
for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {
-
if (!prev_map[i]) {
-
slot = i;
-
break;
-
}
-
}
-
...
-
...
-
...
-
prev_size[slot] = size;
-
last_addr = phys_addr + size - 1;
在下一个spte中,我们可以看到以下代码:
-
offset = phys_addr & ~PAGE_MASK;
-
phys_addr &= PAGE_MASK;
-
size = PAGE_ALIGN(last_addr + 1) - phys_addr;
在这里,我们PAGE_MASK
用于清除phys_addr
除前12位之外的所有位。PAGE_MASK
宏定义为:
#define PAGE_MASK (~(PAGE_SIZE-1))
我们知道页面的大小是4096字节或1000000000000
二进制。PAGE_SIZE - 1
将是111111111111
,但使用~
,我们将获得000000000000
,但是使用时,~PAGE_MASK
我们将111111111111
再次获得。在第二行中,我们执行相同的操作,但是清除了前12位,并在第三行中获得了页面对齐的区域大小。我们获得了对齐的区域,现在我们需要获取新ioremap
区域所占据的页面数,并fixed_addresses
在接下来的步骤中计算出修正映射索引:
-
nrpages = size >> PAGE_SHIFT;
-
idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;
现在,我们可以fix-mapped
使用给定的物理地址填充区域。在循环的每次迭代中,我们__early_set_fixmap
从arch/x86/mm/ioremap.c调用该函数,将给定的物理地址增加页面大小(即4096
字节),并更新addresses
索引和页面数:
-
while (nrpages > 0) {
-
__early_set_fixmap(idx, phys_addr, prot);
-
phys_addr += PAGE_SIZE;
-
--idx;
-
--nrpages;
-
}
该__early_set_fixmap
函数使用以下命令获取bm_pte
给定物理地址的页表条目(存储在中,请参见上文):
pte = early_ioremap_pte(addr);
在下一步中,early_ioremap_pte
我们使用pgprot_val
宏检查给定的页面标志,并根据给定的标志进行调用set_pte
或pte_clear
:
-
if (pgprot_val(flags))
-
set_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));
-
else
-
pte_clear(&init_mm, addr, pte);
如您在上方所见,我们将FIXMAP_PAGE_IO
标记传递给__early_ioremap
。FIXMPA_PAGE_IO
扩展为:
(__PAGE_KERNEL_EXEC | _PAGE_NX)
标记,因此我们调用set_pte
函数来设置页表条目,该条目的工作方式与set_pmd
PTE相同(参见上面的内容)。PTEs
在循环中进行所有设置后,我们现在可以看一下该__flush_tlb_one
函数的调用:
__flush_tlb_one(addr);
此函数在arch/x86/include/asm/tlbflush.h中定义,并根据的值进行调用__flush_tlb_single
或:__flush_tlb
cpu_has_invlpg
-
static inline void __flush_tlb_one(unsigned long addr)
-
{
-
if (cpu_has_invlpg)
-
__flush_tlb_single(addr);
-
else
-
__flush_tlb();
-
}
该__flush_tlb_one
函数使TLB中的给定地址无效。如您所见,我们更新了分页结构,但TLB
没有通知更改,因此我们需要手动进行此操作。有两种方法可以做到这一点。首先是更新cr3
控制寄存器,然后__flush_tlb
函数执行此操作:
native_write_cr3(__native_read_cr3());
第二种方法是使用invlpg
指令来使TLB
条目无效。让我们看一下__flush_tlb_one
实现。如您所见,首先进行功能检查cpu_has_invlpg
,其定义为:
如果CPU支持该invlpg
指令,我们将调用__flush_tlb_single
扩展为以下内容的宏__native_flush_tlb_single
:
-
static inline void __native_flush_tlb_single(unsigned long addr)
-
{
-
asm volatile("invlpg (%0)" ::"r" (addr) : "memory");
-
}
或调用__flush_tlb
,它只会更新cr3
我们所看到的寄存器。完成此步骤后,__early_set_fixmap
功能执行完毕,我们可以返回到__early_ioremap
实现。在为给定地址设置了fixmap区域后,我们需要prev_map
使用slot
索引将I / O Re-mapped区域的基本虚拟地址保存在以下位置:
prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]);
并退回。
第二个函数early_iounmap
取消映射I/O
内存区域。此函数采用两个参数:基地址和I/O
区域大小,通常看起来与十分相似early_ioremap
。它还会通过Fixmap插槽,并查找具有给定地址的插槽。之后,它获取fixmap插槽的索引并进行调用__late_clear_fixmap
或__early_set_fixmap
根据其after_paging_init
值进行选择。它的调用方式__early_set_fixmap
有一个不同early_ioremap
:作为物理地址early_iounmap
传递zero
。最后,它将I / O存储区的地址设置为NULL
:
prev_map[slot] = NULL;
这就是fixmaps
和有关的ioremap
。当然,这部分内容并不涵盖的所有功能ioremap
,仅涵盖早期的ioremap,但也包含普通的ioremap。但是在更详细地研究之前,我们需要知道更多的事情。
Links
- apic
- vsyscall
- Intel Trusted Execution Technology
- Xen
- Real Time Clock
- e820
- Memory management unit
- TLB
- Paging
- Linux kernel memory management Part 1.
相关阅读
《ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)》