您的位置 首页 ADAS

Linux内核地址映射模型与Linux内核高端内存详解

Linux内核地址映射模型与Linux内核高端内存详解-Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。 Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。 段页式机制如下图。 linux内核地址空间划分 通常32位Linux内核地址空间划分0~3G为用户空

Linux 操作体系和驱动程序运转在内核空间,应用程序运转在用户空间,两者不能简略地运用指针传递数据,由于Linux运用的虚拟内存机制,用户空间的数据或许被换出,当内核空间运用用户空间指针时,对应的数据或许不在内存中。

Linux内核地址映射模型

x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,通过段页式地址映射后,才真实拜访物理内存。

段页式机制如下图。

linux内核地址空间区分

一般32位Linux内核地址空间区分0~3G为用户空间,3~4G为内核空间。留意这儿是32位内核地址空间区分,64位内核地址空间区分是不同的。

Linux内核高端内存的由来

当内核模块代码或线程拜访内存时,代码中的内存地址都为逻辑地址,而对应到真实的物理内存地址,需求地址1对1的映射,如逻辑地址0xc0000003对应的物理地址为0&TImes;3,0xc0000004对应的物理地址为0&TImes;4,… …,逻辑地址与物理地址对应的联系为

物理地址 = 逻辑地址 – 0xC0000000

假定依照上述简略的地址映射联系,那么内核逻辑地址空间拜访为0xc0000000 ~ 0xffffffff,那么对应的物理内存规模就为0&TImes;0 ~ 0&TImes;40000000,即只能拜访1G物理内存。若机器中装置8G物理内存,那么内核就只能拜访前1G物理内存,后边7G物理内存将会无法拜访,由于内核 的地址空间现已悉数映射到物理内存地址规模0×0 ~ 0×40000000。即便装置了8G物理内存,那么物理地址为0×40000001的内存,内核该怎样去拜访呢?代码中有必要要有内存逻辑地址 的,0xc0000000 ~ 0xffffffff的地址空间现已被用完了,所以无法拜访物理地址0×40000000今后的内存。

显 然不能将内核地址空间0xc0000000 ~ 0xfffffff悉数用来简略的地址映射。因而x86架构中将内核地址空间区分三部分:ZONE_DMA、ZONE_NORMAL和 ZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这便是内存高端内存概念的由来。

在x86结构中,三种类型的区域如下:

ZONE_DMA        内存开端的16MB

ZONE_NORMAL       16MB~896MB

ZONE_HIGHMEM       896MB ~ 完毕

Linux内核高端内存的了解

前面咱们解说了高端内存的由来。 Linux将内核地址空间区分为三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高端内存HIGH_MEM地址空间规模为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是怎样凭借128MB高端内存地址空间是怎样完结拜访能够一切物理内存?

当内核想拜访高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间规模内找一段相应巨细闲暇的逻辑地址空间,借用一会。借用这段逻辑地址空间,树立映射到想拜访的那段物理内存(即填充内核PTE页面表),暂时用一会,用完后偿还。这样他人也能够借用这段地址空间拜访其他物理内存,完结了运用有限的地址空间,拜访一切一切物理内存。如下图。

例 如内核想拜访2G开端的一段巨细为1MB的物理内存,即物理地址规模为0×80000000 ~ 0x800FFFFF。拜访之前先找到一段1MB巨细的闲暇地址空间,假定找到的闲暇地址空间为0xF8700000 ~ 0xF87FFFFF,用这1MB的逻辑地址空间映射到物理地址空间0×80000000 ~ 0x800FFFFF的内存。映射联系如下:

当内核拜访完0×80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF内核线性空间开释。这样其他进程或代码也能够运用0xF8700000 ~ 0xF87FFFFF这段地址拜访其他物理内存。

从上面的描绘,咱们能够知道高端内存的最基本思维:借一段地址空间,树立暂时地址映射,用完后开释,到达这段地址空间能够循环运用,拜访一切物理内存。

看到这儿,不由有人会问:万一有内核进程或模块一向占用某段逻辑地址空间不开释,怎样办?若真的呈现的这种状况,则内核的高端内存地址空间越来越严重,若都被占用不开释,则没有树立映射到物理内存都无法拜访了。

在 香港尖沙咀有些写字楼,洗手间很少且有门锁的。客户要去洗手间的话,能够向前台拿钥匙,便利完后,把钥匙偿还到前台。这样尽管只要一个洗 手间,但能够满意一切客户去洗手间的需求。要是某个客户一向占用洗手间、钥匙不偿还,那么其他客户都无法上洗手间了。Linux内核高端内存办理的思维类 似。

Linux内核高端内存的区分

内核将高端内存区分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。

对 于高端内存,能够通过 alloc_page() 或许其它函数取得对应的 page,可是要想拜访实践物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是怎样拜访物理内存的),也便是说,咱们需求为高端内存对应的 page 找一个线性空间,这个进程称为高端内存映射。

对应高端内存的3部分,高端内存映射有三种方法:映射到”内核动态映射空间”(noncontiguous memory allocation)

这种方法很简略,由于通过 vmalloc() ,在”内核动态映射空间”请求内存的时分,就或许从高端内存取得页面(参看 vmalloc 的完结),因而说高端内存有或许映射到”内核动态映射空间”中。

耐久内核映射(permanent kernel mapping)

假如是通过 alloc_page() 取得了高端内存对应的 page,怎样给它找个线性空间?

内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.6内核上,这个地址规模是 4G-8M 到 4G-4M 之间。这个空间起叫”内核永久映射空间”或许”永久内核映射空间”。这个空间和其它空间运用相同的页目录表,关于内核来说,便是 swapper_pg_dir,对一般进程来说,通过 CR3 寄存器指向。一般状况下,这个空间是 4M 巨细,因而只是需求一个页表即可,内核通过来 pkmap_page_table 寻觅这个页表。通过 kmap(),能够把一个 page 映射到这个空间来。由于这个空间是 4M 巨细,最多能一起映射 1024 个 page。因而,关于不运用的的 page,及应该时从这个空间开释掉(也便是免除映射联系),通过 kunmap() ,能够把一个 page 对应的线性地址从这个空间开释出来。

暂时映射(temporary kernel mapping)内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特别需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的暂时映射。

这块空间具有如下特色:(1)每个 CPU 占用一块空间(2)在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间巨细是 1 个 page,每个小空间用于一个意图,这些意图界说在 kmap_types.h 中的 km_type 中。

当要进行一次暂时映射的时分,需求指定映射的意图,依据映射意图,能够找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次暂时映射会导致曾经的映射被掩盖。通过 kmap_atomic() 可完结暂时映射。

常见问题:

1、用户空间(进程)是否有高端内存概念?

用户进程没有高端内存概念。只要在内核空间才存在高端内存。用户进程最多只能够拜访3G物理内存,而内核进程能够拜访一切物理内存。

2、64位内核中有高端内存吗?

现在实际中,64位Linux内核不存在高端内存,由于64位内核能够支撑超越512GB内存。若机器装置的物理内存超越内核地址空间规模,就会存在高端内存。

3、用户进程能拜访多少物理内存?内核代码能拜访多少物理内存?

32位体系用户进程最大能够拜访3GB,内核代码能够拜访一切物理内存。

64位体系用户进程最大能够拜访超越512GB,内核代码能够拜访一切物理内存。

4、高端内存和物理地址、逻辑地址、线性地址的联系?

高端内存只和逻辑地址有联系,和逻辑地址、物理地址没有直接联系。

5、为什么不把一切的地址空间都分配给内核?

若把一切地址空间都给内存,那么用户进程怎样运用内存?怎样确保内核运用内存和用户进程不起抵触?

(1)让咱们疏忽Linux对段式内存映射的支撑。 在维护模式下,咱们知道不管CPU运转于用户态仍是核心态,CPU履行程序所拜访的地址都是虚拟地址,MMU 有必要通过读取操控寄存器CR3中的值作为当时页面目录的指针,从而依据分页内存映射机制(参看相关文档)将该虚拟地址转化为真实的物理地址才干让CPU真 正的拜访到物理地址。

(2)关于32位的Linux,其每一个进程都有4G的寻址空间,但当一个进程拜访其虚拟内存空间中的某个地址时又是怎样完结不与其它进程的虚拟空间混杂 的呢?每个进程都有其本身的页面目录PGD,Linux将该目录的指针寄存在与进程对应的内存结构task_struct.(struct mm_struct)mm->pgd中。每逢一个进程被调度(schedule())行将进入运转态时,Linux内核都要用该进程的PGD指针设 置CR3(switch_mm())。

(3)当创立一个新的进程时,都要为新进程创立一个新的页面目录PGD,并从内核的页面目录swapper_pg_dir中仿制内核区间页面目录项至新建进程页面目录PGD的相应方位,详细进程如下:do_fork() –> copy_mm() –> mm_init() –> pgd_alloc() –> set_pgd_fast() –> get_pgd_slow() –> memcpy(&PGD + USER_PTRS_PER_PGD, swapper_pg_dir + USER_PTRS_PER_PGD, (PTRS_PER_PGD – USER_PTRS_PER_PGD) * sizeof(pgd_t))这样一来,每个进程的页面目录就分成了两部分,第一部分为“用户空间”,用来映射其整个进程空间(0x0000 0000-0xBFFF FFFF)即3G字节的虚拟地址;第二部分为“体系空间”,用来映射(0xC000 0000-0xFFFF FFFF)1G字节的虚拟地址。能够看出Linux体系中每个进程的页面目录的第二部分是相同的,所以从进程的视点来看,每个进程有4G字节的虚拟空间, 较低的3G字节是自己的用户空间,最高的1G字节则为与一切进程以及内核同享的体系空间。

(4)现在假定咱们有如下一个情形:在进程A中通过体系调用sethostname(const char *name,seze_t len)设置计算机在网络中的“主机名”.在该情形中咱们必然涉及到从用户空间向内核空间传递数据的问题,name是用户空间中的地址,它要通过体系调用设置到内核中的某个地址中。让咱们看看这个 进程中的一些细节问题:体系调用的详细完结是将体系调用的参数顺次存入寄存器ebx,ecx,edx,esi,edi(最多5个参数,该情形有两个 name和len),接着将体系调用号存入寄存器eax,然后通过中止指令“int 80”使进程A进入体系空间。由于进程的CPU运转等级小于等于为体系调用设置的圈套门的准入等级3,所以能够四通八达的进入体系空间去履行为int 80设置的函数指针system_call()。由于system_call()归于内核空间,其运转等级DPL为0,CPU要将仓库切换到内核仓库,即 进程A的体系空间仓库。咱们知道内核为新建进程创立task_struct结构时,共分配了两个接连的页面,即8K的巨细,并将底部约1k的巨细用于 task_struct(如#define alloc_task_struct() ((struct task_struct *) __get_free_pages(GFP_KERNEL,1))),而其余部分内存用于体系空间的仓库空间,即当从用户空间转入体系空间时,仓库指针 esp变成了(alloc_task_struct()+8192),这也是为什么体系空间一般用宏界说current(参看其完结)获取当时进程的 task_struct地址的原因。每次在进程从用户空间进入体系空间之初,体系仓库就现已被顺次压入用户仓库SS、用户仓库指针ESP、EFLAGS、 用户空间CS、EIP,接着system_call()将eax压入,再接着调用SAVE_ALL顺次压入ES、DS、EAX、EBP、EDI、ESI、 EDX、ECX、EBX,然后调用sys_call_table+4*%EAX,本情形为sys_sethostname()。

(5)在sys_sethostname()中,通过一些维护考虑后,调用copy_from_user(to,from,n),其间to指向内核空间 system_utsname.nodename,比如0xE625A000,from指向用户空间比如0x8010FE00。现在进程A进入了内核,在 体系空间中运转,MMU依据其PGD将虚拟地址完结到物理地址的映射,终究完结从用户空间到体系空间数据的仿制。预备仿制之前内核先要确认用户空间地址和 长度的合法性,至于从该用户空间地址开端的某个长度的整个区间是否现已映射并不去查看,假如区间内某个地址未映射或读写权限等问题呈现时,则视为坏地址, 就发生一个页面反常,让页面反常服务程序处理。进程如 下:copy_from_user()->generic_copy_from_user()->access_ok()+__copy_user_zeroing().

(6)小结:*进程寻址空间0~4G  *进程在用户态只能拜访0~3G,只要进入内核态才干拜访3G~4G  *进程通过体系调用进入内核态*每个进程虚拟空间的3G~4G部分是相同的  *进程从用户态进入内核态不会引起CR3的改动但会引起仓库的改动Linux 简化了分段机制,使得虚拟地址与线性地址总是共同,因而,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的 1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核运用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程运用,称为“用户空间)。由于每个进程能够通过体系调用进入内核,因而,Linux内核由体系 内的一切进程同享。所以,从详细进程的视点来看,每个进程能够具有4G字节的虚拟空间。    Linux运用两级维护机制:0级供内核运用,3级供用户程序运用。从图中能够看出(这儿无法表明图),每个进程有各自的私有用户空间(0~3G),这个空间对体系中的其他进程是不行见的。最高的1GB字节虚拟内核空间则为一切进程以及内核所同享。

1.虚拟内核空间到物理空间的映射

内核空间中寄存的是内核代码和数据,而进程的用户空间中寄存的是用户程序的代码和数据。不管是内核空间仍是用户空间,它们都处于虚拟空间中。读者会问,系 统启动时,内核的代码和数据不是被装入到物理内存吗?它们为什么也处于虚拟内存中呢?这和编译程序有关,后边咱们通过详细评论就会理解这一点。

虽 然内核空间占有了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开端。对内核空间来说,其地址映射是很简略 的线性映射,0xC0000000便是物理地址与线性地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET。

咱们来看一下在include/asm/i386/page.h中对内核空间中地址映射的阐明及界说:/** This handles the memory map.. We could make this a config* option, but too many people screw it up, and too few need* it.** A __PAGE_OFFSET of 0xC0000000 means that the kernel has* a virtual address space of one gigabyte, which limits the* amount of physical memory you can use to about 950MB. ** If you want more physical memory than this then see the CONFIG_HIGHMEM4G* and CONFIG_HIGHMEM64G options in the kernel configuration.*/#define __PAGE_OFFSET           (0xC0000000)……#define PAGE_OFFSET             ((unsigned long)__PAGE_OFFSET)#define __pa(x)                 ((unsigned long)(x)-PAGE_OFFSET)#define __va(x)                 ((void *)((unsigned long)(x)+PAGE_OFFSET))源 代码的注释中阐明,假如你的物理内存大于950MB,那么在编译内核时就需求加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G选 项,这种状况咱们暂不考虑。假如物理内存小于950MB,则关于内核空间而言,给定一个虚地址x,其物理地址为“x- PAGE_OFFSET”,给定一个物理地址x,其虚地址为“x+ PAGE_OFFSET”。

这儿再次阐明,宏__pa()只是把一个内核空间的虚地址映射到物理地址,而决不适用于用户空间,用户空间的地址映射要杂乱得多。

2.内核映像

鄙人面的描绘中,咱们把内核的代码和数据就叫内核映像(kernel image)。当体系启动时,Linux内核映像被装置在物理地址0x00100000开端的当地,即1MB开端的区间(第1M留作它用)。但是,在正常 运转时, 整个内核映像应该在虚拟内核空间中,因而,衔接程序在衔接内核映像时,在一切的符号地址上加一个偏移量PAGE_OFFSET,这样,内核映像在内核空间 的开始地址就为0xC0100000。

例如,进程的页目录PGD(归于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器CR3设置成指 向新进程的页目录PGD,而该目录的开始地址在内核空间中是虚地址,但CR3所需求的是物理地址,这时分就要用__pa()进行地址转化。在 mm_context.h中就有这么一行句子:asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));

这是一行嵌入式汇编代码,其意义是将下一个进程的页目录开始地址next_pgd,通过__pa()转化成物理地址,寄存在某个寄存器中,然后用mov指令将其写入CR3寄存器中。通过这行句子的处理,CR3就指向新进程next的页目录表PGD了

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qiche/adas/102585.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部