导航:首页 > 操作系统 > linuxvma

linuxvma

发布时间:2023-04-27 23:34:33

① 写内存算是访问内存吗

当用户访问用户空间的这段地址范围时,实际是访问设备内存。
linux上电时,并不会为外设地址空间建立页表。
但我们知道,linux访问内存使用的都是虚拟地址,因此如果想访问外设的寄存器(一般包括数据寄存器、控制寄存器与状态寄存器),需要在驱动初始化中将外设所处的物理地址映射为虚拟地址,使用ioremap接口可以实现该功能。

ioremap & ioremap_nocache
ioremap和ioremap_nocache实现相同,使用场景为映射device memory类型内存。同时不使用cache(device memory本身就没有cacheable这个属性),即CPU的读写操作直接操作设备内存。

ioremap_cached
ioremap_cached用来映射memory type为normal memory的设备,同时使用cache,这会提高内存的访问速度,提高系统的性能。

ioremap_wc & ioremap_wt
ioremap_wc用来映射memory type为normal memory的设备,同时不使用cache。

I/O内存访问流程
request_mem_region
ioremap
rw
iounmap
release_mem_region
二、设备地址映射到用户空间
一般情况下,用户空间是不能够直接访高肢问设备的。mmap可实现这个功能。

mmap通过将设备内存映射到用户空间的一段内存上,这样,当用户访问用户空间的这段地址范围时,实际是访问设备内存。这样在每次访问时,节省了用户空间和内核空间的复制过程。

无论是普通文件还是设备文件,读写都是基于系统的虚拟文件系统接口,普通文件为了保护磁盘,避免频繁读写,还引入带缓冲页机制,通过read/write/ioctl访问文件时,都需经历“用户到内核”的内存拷贝过程,然后才将文件内容写入磁盘。

通过mmap方法,将文件(包括设备文件)映射到用户进程虚拟内存空间,代替read/write/ioctl的访问方式,此时内存拷贝过程只有“用户空间到虚拟内存空间”,省去了“用户到内核”的拷贝过程,在数据量大的情况下能显着提升读写效率。因此,mmap也称为“零拷贝”(zero )技术。

caddr_t *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

fd为文件描述符,一般由open返回。fd也可指定为-1,并指定flags参数中的MAP_ANON,表示匿名映射。
length指映射的字节数,从offset开始计算;
prot指定访问权限;
start指定文件被映射到用户空间的起始地址,一般设为NULL,由内核指定改地址;
函数返回值为映射到用户空间的地址。
1
2
3
4
5
1
2
3
4
5
mmap过程
1、在虚拟内存中查找一块VMA
2、将这块VMA进行映射
3、如果设备驱动程序或文件系统的file_operation定义了mmap接口,则调用它;
4、将VMA插入进程的VMA链表中
1
2
3
4
1
2
3
4
进程在映射空间的对共享内容的修改不会实时同步写回到磁盘文件中,只有调用munmap()函数释放映射后才会执行同步操作。mmap机制提供msync()函数,用于手动同步修改内容到磁盘源文件。

linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同滚老类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:
在这里插入图片描述
vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。

三、devmem原理
“/dev/mem”设备
“/大念升dev/mem”是linux系统的一个虚拟字符设备,无论是标准linux系统还是嵌入式linux系统,都支持该设备。
“/dev/mem”设备是内核所有物理地址空间的全映像,这些地址包括:

物理内存(RAM)空间
物理存储(ROM)空间
cpu总线地址
cpu寄存器地址
外设寄存器地址,GPIO、定时器、ADC
1
2
3
4
5
1
2
3
4
5
“/dev/mem”设备通常与“mmap”结合使用,可将指定内存映射到用户空间。
类似的还有/dev/kmem设备,kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。

devmem命令原理
应用程序通过mmap函数实现对/dev/mem驱动中mmap方法的使用,映射了设备的内存到用户空间,实现对这些物理地址的读写操作。
类似的有devkmem命令,通过mmap函数实现对/dev/kmem驱动中mmap方法的使用,映射了设备的内核空间到用户空间,实现对这些物理地址的读写操作。

四、malloc原理
malloc的工作原理
可执行文件加载到内存中的时候,就给栈和堆划分了固定大小的空间。使用vm_area_struct结构体指明了一个连续区域的头地址和尾地址。

malloc函数分配内存主要是使用brk和mmap系统调用

brk(): 小于128k
在堆段分配malloc的内存,将堆顶的指针brk往上推;
mmap(): 大于128k
是在堆和栈之间(文件映射区域)找分配一块空闲的虚拟内存,
1
2
3
4
1
2
3
4
malloc系统调用后,并没有实际分配物理内存。
这时候读虚拟内存地址,返回值是0;
第一次写的时候,发生缺页中断,才会实际分配物理内存,建立虚拟内存与物理内存的映射关系。

缺页中断
malloc的空间没有实际分配的情况下,在写的时候会报缺页中断。实际上:
进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(即存在位为0),那么停止该指令的执行,并产生一个页不存在的异常,对应的故障处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。

当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:

1、检查要访问的虚拟地址是否合法
2、查找/分配一个物理页
3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
4、建立映射关系(虚拟地址到物理地址)
1
2
3
4
1
2
3
4
重新执行发生缺页中断的那条指令
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。

如何查看进程发生缺页中断的次数:
ps -o majflt,minflt -C program

majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。

malloc的free
前面知道,通过移动brk申请的内存,存放在进程的堆区域中。
free是由运行库实现,它只是在已分配的堆块前面加一个可用标志,并不实际释放内存,不论是物理内存还是进程的堆空间。
在下次的malloc时,这块空间可能被重用。
如果进程的堆空间出现较多的碎片(这是逻辑地址中的碎片),运行库的堆管理例程会移动/合并碎片,此时可能会出现物理内存的释放/重新分配。
而对于brk指针,只有它指向的那片内存被free的时候才会下移。比如先malloc了一个A,然后malloc了一个B。free掉A之后,brk是不会下移的;free掉B的时候brk才会下移。

② linux铁三角之内存(三)

一个用户空间的进程,究竟消耗了多少内存。
首先要名确,一个application消耗的内存,一定指得是用户空间的内存。

3g - 4g 的kernal space是共享的,每个进程都有自己用户空间0 - 3G,只要通过系统调用就可以陷入kernal space, 就会从x86的3 rings升级到0 rings, 即陷入到内核空间。
app 调driver的iocrtrl, dirver 的ioctrl 内部通纯枝过调用kmalloc/vmalloc申请的内存并不计算在内,因为是通过内核的api申请的,属于内核消耗的。

vss、rss、pss、uss

pidof a.out
pmap a.out

vma的来源

在linux铁三角(二)有过叙述,这里不再赘述。这里直接上图把

MMU给CPU发送page fault的时候,在硬件中有2个寄存器

是否RSS就代表一个进程真正的内存消耗呢?

三个进程,其中2个Bash, 1 个 cat.
那么对应三张页表,每当切换进程,存储页表的 基地历裤茄址就会却换,从而切换到不同的地址空间中。
中间肢察的是内存条,通过三张页表瓜分物理内存。
104进程内存消耗:

③ Linux将设备地址映射到用户空间内存映射与VMA

一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap ()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap ()实现了这样的一个映射过程:它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程。
mmap ()必须以PAGE_SIZE为单位进行映射,实际上,内存只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行页对齐,强行以PAGE_SIZE的倍数大小进行映射。
从file_operations文件操作结构体可以看出,驱动中mmap ()函数的原型如下:
int ( *mmap)(struct file *, struct vm_area_struct* ) ;
驱动中的mmap () 函数将在用户进行mmap ()系统调用时最终被调用,mmap ()系统调用的原型与file_operations中mmap ()的原型区别很大,如下所示:
caddr_t mmap (caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
参数fd为文件描述符,一般由open ()返回,fd也可以指定为-1,此时需指定flags参数中的MAP_ANON,表明进行的是匿名映射。
len是映射到调用用户空间的字节数,它从被映射文件开头offset个字节开始算起,offset参数一般设为0,表示从文件头开始映射。
prot参数指定访问权限,可取如下几个值的“或”:PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)。
参数addr指定文件应被映射到用户空间的起始地址,一般被指定为NULL,这样,选择起始地址的任务将由内核完成,而函数的返回值就是映射到用户空间的地址。其类型caddr_t实际上就是void*。
当用户调用mmap ())的时候,内核会进行如下处理。
1)在进程的虚拟空间查找一块VMA。
2)将这块VMA进行映射。
3)如果设备驱动程序或者文件系统的file_operations定义了mmap ()操作,则调用它。
4)将这个VMA插入进程的VMA链表中。
file_operations中mmap ()函数的第一个参数就是步骤1)找到的VMA。
由mmap ()系统调用映射的内存可由munmap ()解除映射,这个函数的原型如下:
int munmap(caddr_t addr, size_t len ) ;
驱动程序中mmap ()的实现机制是建立页表,并填充VMA结构体中vm_operations_struct指针。

④ linux操作系统额外占用物理内存是多少

这个看看你需要使用的情况.
如果是使用文本化界面的话,其稿戚实64M就足够了.
但是如果你需要使用到图形化界面(GNOME),那么最好分轿数128M以上,个人觉得256M左右比较合适,当然这个根据你本闭敬首身自带的硬件内存的大小决定.
因为是半虚拟化,所以对系统的资源占用是比较大的,所以最好先确定你的硬件环境.

⑤ 简述缺页中断和一般中断的区别

1、范围不同

一般中断只需要保护现场,然后就直接跳到需及时处理的地方。

缺页中断除了保护现场之外,还要判断内存中是否有足够的空间存储所需的页或段,然后再把所需页调进来再使用。

2、结果不同

一般中断在处理完之后返回时,执行下一条指令。

缺页中断返回时,执行产生中断的那一条指令。

3、次数不同

在扒困码指令执行期间产生和处理缺页中断信号,一条指令在执行期间,可能产生多次缺页中断。

一般中断只产生一尺誉次,发生中断指令后转入相应处理程序进行处理,恢复被中断程序现场。



(5)linuxvma扩展阅读

产生缺页中断的几种情况

1、当内存管理单元(MMU)中确实没有创建虚拟物理页映射关系,并且在该虚拟地址之后再没有当前进程的线性区(vma)的时候,这将杀掉该进程。

2、当MMU中确实没有创建虚拟页物理页映射关系,并且在该虚拟地址之后存在当前进程的线性区vma的时候,这很可能是缺页中断,并且可能是栈溢出导致的缺页中断。

3、当使用malloc/mmap等希望访问物理空间的库函数/系统调用后,由于linux并未真正给新创建的vma映射物理页,此时若先进行写操作,将和2产生缺页中断的情况一样;若先进行读操作虽然也会产生缺页异常,将被映射给默认的零页,等再进行写操作时,仍会产生缺页中断,这次必须分配1物理页了,进入写时复制的流程。

4、当使用fork等系统调用创建子进程时,子进程春哪不论有无自己的vma,它的vma都有对于物理页的映射,但它们共同映射的这些物理页属性为只读,即linux并未给子进程真正分配物理页,当父子进程任何一方要写相应物理页时,导致缺页中断的写时复制。

⑥ Linux 虚拟地址空间如何分布

一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。

1. MM_STRUCT结构

mm_strcut 用来描述一个进程的虚拟地址空间,在/include/linux/sched.h 中描述如下:

struct mm_struct {

struct vm_area_struct * mmap; /* 指向虚拟区间(VMA)链表 */

rb_root_t mm_rb; /*指向red_black树*/

struct vm_area_struct * mmap_cache; /* 指向最近找到的虚拟区间*/

pgd_t * pgd; /*指向进程的页目录*/

atomic_t mm_users; /* 用户空间中的有多少用户*/

atomic_t mm_count; /* 对"struct mm_struct"有多少引用*/

int map_count; /* 虚拟区间的个数*/

struct rw_semaphore mmap_sem;

spinlock_t page_table_lock; /* 保护任务页表和 mm->rss */

struct list_head mmlist; /*所有活动(active)mm的链表 */

unsigned long start_code, end_code, start_data, end_data;

unsigned long start_brk, brk, start_stack;

unsigned long arg_start, arg_end, env_start, env_end;

unsigned long rss, total_vm, locked_vm;

unsigned long def_flags;

unsigned long cpu_vm_mask;

unsigned long swap_address;

unsigned mpable:1;

/* Architecture-specific MM context */

mm_context_t context;

};

对该结构进一步说明如下:

在内核代码中,指向这个数据结构的变量常常是mm。

每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。

一个进程的虚拟空间中可能有多个虚拟区间(参见下面对vm_area_struct描述),对这些虚拟区间的组织方式有两种,当虚拟区较少时采用单链表,由mmap指针指向这个链表,当虚拟区间多时采用“红黑树(red_black
tree)”结构,由mm_rb指向这颗树。在2.4.10以前的版本中,采用的是AVL树,因为与AVL树相比,对红黑树进行操作的效率更高。

因为程序中用到的地址常常具有局部性,因此,最近一次用到的虚拟区间很可能下一次还要用到,因此,把最近用到的虚拟区间结构应当放入高速缓存,这个虚拟区间就由mmap_cache指向。

指针pgt指向该进程的页目录(每个进程都有自己的页目录,注意同内核页目录的区别),当调度程序调度一个程序运行时,就将这个地址转成物理地址,并写入控制寄存器(CR3)。

由于进程的虚拟空间及其下属的虚拟区间有可能在不同的上下文中受到访问,而这些访问又必须互斥,所以在该结构中设置了用于P、V操作的信号量mmap_sem。此外,page_table_lock也是为类似的目的而设置。

虽然每个进程只有一个虚拟地址空间,但这个地址空间可以被别的进程来共享,如,子进程共享父进程的地址空间(也即共享mm_struct结构)。所以,用mm_user和mm_count进行计数。类型atomic_t实际上就是整数,但对这种整数的操作必须是“原子”的。

另外,还描述了代码段、数据段、堆栈段、参数段以及环境段的起始地址和结束地址。这里的段是对程序的逻辑划分,与我们前面所描述的段机制是不同的。

mm_context_t是与平台相关的一个结构,对i386 几乎用处不大。

在后面对代码的分析中对有些域给予进一步说明。

2. VM_AREA_STRUCT 结构

vm_area_struct描述进程的一个虚拟地址区间,在/include/linux/mm.h中描述如下:

struct vm_area_struct

struct mm_struct * vm_mm; /* 虚拟区间所在的地址空间*/

unsigned long vm_start; /* 在vm_mm中的起始地址*/

unsigned long vm_end; /*在vm_mm中的结束地址 */

/* linked list of VM areas per task, sorted by address */

struct vm_area_struct *vm_next;

pgprot_t vm_page_prot; /* 对这个虚拟区间的存取权限 */

unsigned long vm_flags; /* 虚拟区间的标志. */

rb_node_t vm_rb;

/*

* For areas with an address space and backing store,

* one of the address_space->i_mmap{,shared} lists,

* for shm areas, the list of attaches, otherwise unused.

*/

struct vm_area_struct *vm_next_share;

struct vm_area_struct **vm_pprev_share;

/*对这个区间进行操作的函数 */

struct vm_operations_struct * vm_ops;

/* Information about our backing store: */

unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE

units, *not* PAGE_CACHE_SIZE */

struct file * vm_file; /* File we map to (can be NULL). */

unsigned long vm_raend; /* XXX: put full readahead info here. */

void * vm_private_data; /* was vm_pte (shared mem) */

};

vm_flag是描述对虚拟区间的操作的标志,其定义和描述如下

标志名 描述

VM_DENYWRITE 在这个区间映射一个打开后不能用来写的文件。

VM_EXEC 页可以被执行。

VM_EXECUTABLE 页含有可执行代码。

VM_GROWSDOWN 这个区间可以向低地址扩展。

VM_GROWSUP 这个区间可以向高地址扩展。

VM_IO 这个区间映射一个设备的I/O地址空间。

VM_LOCKED 页被锁住不能被交换出去。

VM_MAYEXEC VM_EXEC 标志可以被设置。

VM_MAYREAD VM_READ 标志可以被设置。

VM_MAYSHARE VM_SHARE 标志可以被设置。

VM_MAYWRITE VM_WRITE 标志可以被设置。

VM_READ 页是可读的。

VM_SHARED 页可以被多个进程共享。

VM_SHM 页用于IPC共享内存。
VM_WRITE 页是可写的。

较高层次的结构vm_area_structs是由双向链表连接起来的,它们是按虚地址的降顺序来排列的,每个这样的结构都对应描述一个相邻的地址空间范围。之所以这样分割,是因为每个虚拟区间可能来源不同,有的可能来自可执行映象,有的可能来自共享库,而有的则可能是动态分配的内存区,所以对每一个由vm_area_structs结构所描述的区间的处理操作和它前后范围的处理操作不同。因此Linux
把虚拟内存分割管理,并利用了虚拟内存处理例程(vm_ops)来抽象对不同来源虚拟内存的处理方法。不同的虚拟区间其处理操作可能不同,Linux在这里利用了面向对象的思想,即把一个虚拟区间看成一个对象,用vm_area_structs描述了这个对象的属性,其中的vm_operation结构描述了在这个对象上的操作,其定义在/include/linux/mm.h中:

/*

* These are the virtual MM functions - opening of an area, closing and

* unmapping it (needed to keep files on disk up-to-date etc), pointer

* to the functions called when a no-page or a wp-page exception occurs.

*/

struct vm_operations_struct {

void (*open)(struct vm_area_struct * area);

void (*close)(struct vm_area_struct * area);

struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);

};

vm_operations结构中包含的是函数指针;其中,open、close分别用于虚拟区间的打开、关闭,而nopage用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数。

3.红黑树结构

Linux内核从2.4.10开始,对虚拟区的组织不再采用AVL树,而是采用红黑树,这也是出于效率的考虑,虽然AVL树和红黑树很类似,但在插入和删除节点方面,采用红黑树的性能更好一些,下面对红黑树给予简单介绍。
一颗红黑树是具有以下特点的二叉树:
每个节点着有颜色,或者为红,或者为黑
根节点为黑色
如果一个节点为红色,那么它的子节点必须为黑色
从一个节点到叶子节点上的所有路径都包含有相同的黑色节点数

⑦ linux内核模块编写要求从一个虚存区VMA和一个虚地址addr求这个地址所在的物理页面

implicit declaration of function ***
查谨巧查英语也应该晓得了阿....

隐式的函数声明,就是说你使用了kmap_atomic、kmap_atomic但是没有声明,一般是因为没有包含头文件,或者是内核的API发生了些许变化。橘碰

这两个函数包含在linux/highmem.h中,你在程序中添加一句:#include <圆晌谈linux/highmem.h>试试。
PS :这个是在2.6.38的内核中的,你在lxr.linux.no搜下对应版本的kernel,我没有细细查。

⑧ linux一个线程需要有多少vma

$ ps -ef f | grep proftpd
nobody 23117 1 0 Dec23 ? S 0:00 proftpd: (accepting connections)
jack 23121 23117 0 Dec23 ? S 7:57 /_ proftpd: jack - ftpsrv: IDLE
jack 28944 23117 0 Dec23 ? S 4:56 /弯知_ proftpd: jack - ftpsrv: IDLE
这样数蚂就可以看到proftpd这个进程下面挂了两个埋毕消线程。

⑨ linux中使用了什么内存管理方法,为什么

“事实胜于雄辩”,我们用一个小例子(原形取自《User-Level Memory Management》)来展示上面所讲的各种内存区的差别与位置。

进程的地址空间对应的描述结构是“内存描述符结构”,它表示进程的全部地址空间,——包含了和进程地址空间有关的全部信息,其中当然包含进程的内存区域。

进程内存的分配与回收

创建进程fork()、程序载入execve()、映射文件mmap()、动态内存分配malloc()/brk()等进程相关操作都需要分配内存给进程。不过这时进程申请和获得的还不是实际内存,而是虚拟内存,准确的说是“内存区域”。进程对内存区域的分配最终都会归结到do_mmap()函数上来(brk调用被单独以系统调用实现,不用do_mmap()),

内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中--无论是扩展已存在的内存区域还是创建一个新的区域。

同样,释放一个内存区域应使用函数do_ummap(),它会销毁对应的内存区域。

如何由虚变实!

从上面已经看到进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存(物理页面——页的概念请大家参考硬件基础一章),获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。

该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去真正为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上。(当然,如果页被换出到磁盘,也会产生缺页异常,不过这时不用再建立页表了)

这种请求页机制把页面的分配推迟到不能再推迟为止,并不急于把所有的事情都一次做完(这种思想有点像设计模式中的代理模式(proxy))。之所以能这么做是利用了内存访问的“局部性原理”,请求页带来的好处是节约了空闲内存,提高了系统的吞吐率。要想更清楚地了解请求页机制,可以看看《深入理解linux内核》一书。

这里我们需要说明在内存区域结构上的nopage操作。当访问的进程虚拟内存并未真正分配页面时,该操作便被调用来分配实际的物理页,并为该页建立页表项。在最后的例子中我们会演示如何使用该方法。

系统物理内存管理

虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,概括地讲,地址转换需要将虚拟地址分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一级别的页表或者指向最终的物理页面。

每个进程都有自己的页表。进程描述符的pgd域指向的就是进程的页全局目录。下面我们借用《linux设备驱动程序》中的一幅图大致看看进程地址空间到物理页之间的转换关系。

上面的过程说起来简单,做起来难呀。因为在虚拟地址映射到页之前必须先分配物理页——也就是说必须先从内核中获取空闲页,并建立页表。下面我们介绍一下内核管理物理内存的机制。

物理内存管理(页管理)

Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存[3],系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存时还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。

鉴于上述需求,内核分配物理页面时为了尽量减少不连续情况,采用了“伙伴”关系来管理空闲页面。伙伴关系分配算法大家应该不陌生——几乎所有操作系统方面的书都会提到,我们不去详细说它了,如果不明白可以参看有关资料。这里只需要大家明白Linux中空闲页面的组织和管理利用了伙伴关系,因此空闲页面分配时也需要遵循伙伴关系,最小单位只能是2的幂倍页面大小。内核中分配空闲页面的基本函数是get_free_page/get_free_pages,它们或是分配单页或是分配指定的页面(2、4、8…512页)。

注意:get_free_page是在内核中分配内存,不同于malloc在用户空间中分配,malloc利用堆动态分配,实际上是调用brk()系统调用,该调用的作用是扩大或缩小进程堆空间(它会修改进程的brk域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数为单位,扩张或收缩对应的内存区域,但brk值并非以页面大小为倍数修改,而是按实际请求修改。因此Malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然会是以页为单位分配的。

另外,需要提及的是,物理页在系统中由页结构structpage描述,系统中所有的页面都存储在数组mem_map[]中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页面则可由上述提到的以伙伴关系组织的空闲页链表(free_area[MAX_ORDER])来索引。

内核内存使用

Slab

所谓尺有所长,寸有所短。以页为最小单位分配内存对于内核管理系统中的物理内存来说的确比较方便,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用来存放描述符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。

为了满足内核对这种小内存块的需要,Linux系统采用了一种被称为slab分配器的技术。Slab分配器的实现相当复杂,但原理不难,其核心思想就是“存储池[4]”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。

Slab技术不但避免了内存内部分片(下文将解释)带来的不便(引入Slab分配器的主要目的是为了减少对伙伴系统分配算法的调用次数——频繁分配和回收必然会导致内存碎片——难以找到大块连续的可用内存),而且可以很好地利用硬件缓存提高访问速度。

Slab并非是脱离伙伴关系而独立存在的一种内存分配方式,slab仍然是建立在页面基础之上,换句话说,Slab将页面(来自于伙伴关系管理的空闲页面链表)撕碎成众多小内存块以供分配,slab中的对象分配和销毁使用kmem_cache_alloc与kmem_cache_free。

Kmalloc

Slab分配器不仅仅只用来存放内核专用的结构体,它还被用来处理内核对小块内存的请求。当然鉴于Slab分配器的特点,一般来说内核程序中对小于一页的小块内存的请求才通过Slab分配器提供的接口Kmalloc来完成(虽然它可分配32到131072字节的内存)。从内核内存分配的角度来讲,kmalloc可被看成是get_free_page(s)的一个有效补充,内存分配粒度更灵活了。

有兴趣的话,可以到/proc/slabinfo中找到内核执行现场使用的各种slab信息统计,其中你会看到系统中所有slab的使用信息。从信息中可以看到系统中除了专用结构体使用的slab外,还存在大量为Kmalloc而准备的Slab(其中有些为dma准备的)。

内核非连续内存分配(Vmalloc)

伙伴关系也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外部分片和内部分片之说,所谓内部分片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费;外部分片是指系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求。无论何种分片都是系统有效利用内存的障碍。slab分配器使得一个页面内包含的众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存。伙伴关系把内存块按大小分组管理,一定程度上减轻了外部分片的危害,因为页框分配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部分片,但并未彻底消除。你自己比划一下多次分配页面后,空闲内存的剩余情况吧。

所以避免外部分片的最终思路还是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的情况很类似于用户空间分配虚拟内存,内存逻辑上连续,其实映射到并不一定连续的物理内存上。Linux内核借用了这个技术,允许内核程序在内核地址空间中分配虚拟地址,同样也利用页表(内核页表)将虚拟地址映射到分散的内存页上。以此完美地解决了内核内存使用中的外部分片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不同于kmalloc,它可以分配较Kmalloc大得多的内存空间(可远大于128K,但必须是页大小的倍数),但相比Kmalloc来说,Vmalloc需要对内核虚拟地址进行重映射,必须更新内核页表,因此分配效率上要低一些(用空间换时间)

与用户进程相似,内核也有一个名为init_mm的mm_strcut结构来描述内核地址空间,其中页表项pdg=swapper_pg_dir包含了系统内核空间(3G-4G)的映射关系。因此vmalloc分配内核虚拟地址必须更新内核页表,而kmalloc或get_free_page由于分配的连续内存,所以不需要更新内核页表。

vmalloc分配的内核虚拟内存与kmalloc/get_free_page分配的内核虚拟内存位于不同的区间,不会重叠。因为内核虚拟空间被分区管理,各司其职。进程空间地址分布从0到3G(其实是到PAGE_OFFSET,在0x86中它等于0xC0000000),从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页面表mem_map等等)比如我使用的系统内存是64M(可以用free看到),那么(3G——3G+64M)这片内存就应该映射到物理内存,而vmalloc_start位置应在3G+64M附近(说"附近"因为是在物理内存映射区与vmalloc_start期间还会存在一个8M大小的gap来防止跃界),vmalloc_end的位置接近4G(说"接近"是因为最后位置系统会保留一片128k大小的区域用于专用页面映射,还有可能会有高端内存映射区,这些都是细节,这里我们不做纠缠)。

上图是内存分布的模糊轮廓

由get_free_page或Kmalloc函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),你可以很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理映射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核页表是有序对应的,系统中的每个物理页面都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。

而vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k大小的空闲区间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页面。

这里给出一个小程序帮助大家认清上面几种分配函数所对应的区域。

#include<linux/mole.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsignedchar*pagemem;

unsignedchar*kmallocmem;

unsignedchar*vmallocmem;

intinit_mole(void)

{

pagemem = get_free_page(0);

printk("<1>pagemem=%s",pagemem);

kmallocmem = kmalloc(100,0);

printk("<1>kmallocmem=%s",kmallocmem);

vmallocmem = vmalloc(1000000);

printk("<1>vmallocmem=%s",vmallocmem);

}

voidcleanup_mole(void)

{

free_page(pagemem);

kfree(kmallocmem);

vfree(vmallocmem);

}

实例

内存映射(mmap)是Linux操作系统的一个很大特色,它可以将系统内存映射到一个文件(设备)上,以便可以通过访问文件内容来达到访问内存的目的。这样做的最大好处是提高了内存访问速度,并且可以利用文件系统的接口编程(设备在Linux中作为特殊文件处理)访问内存,降低了开发难度。许多设备驱动程序便是利用内存映射功能将用户空间的一段地址关联到设备内存上,无论何时,只要内存在分配的地址范围内进行读写,实际上就是对设备内存的访问。同时对设备文件的访问也等同于对内存区域的访问,也就是说,通过文件操作接口可以访问内存。Linux中的X服务器就是一个利用内存映射达到直接高速访问视频卡内存的例子。

熟悉文件操作的朋友一定会知道file_operations结构中有mmap方法,在用户执行mmap系统调用时,便会调用该方法来通过文件访问内存——不过在调用文件系统mmap方法前,内核还需要处理分配内存区域(vma_struct)、建立页表等工作。对于具体映射细节不作介绍了,需要强调的是,建立页表可以采用remap_page_range方法一次建立起所有映射区的页表,或利用vma_struct的nopage方法在缺页时现场一页一页的建立页表。第一种方法相比第二种方法简单方便、速度快,但是灵活性不高。一次调用所有页表便定型了,不适用于那些需要现场建立页表的场合——比如映射区需要扩展或下面我们例子中的情况。

我们这里的实例希望利用内存映射,将系统内核中的一部分虚拟内存映射到用户空间,以供应用程序读取——你可利用它进行内核空间到用户空间的大规模信息传输。因此我们将试图写一个虚拟字符设备驱动程序,通过它将系统内核空间映射到用户空间——将内核虚拟内存映射到用户虚拟地址。从上一节已经看到Linux内核空间中包含两种虚拟地址:一种是物理和逻辑都连续的物理内存映射虚拟地址;另一种是逻辑连续但非物理连续的vmalloc分配的内存虚拟地址。我们的例子程序将演示把vmalloc分配的内核虚拟地址映射到用户地址空间的全过程。

程序里主要应解决两个问题:

第一是如何将vmalloc分配的内核虚拟内存正确地转化成物理地址?

因为内存映射先要获得被映射的物理地址,然后才能将其映射到要求的用户虚拟地址上。我们已经看到内核物理内存映射区域中的地址可以被内核函数virt_to_phys转换成实际的物理内存地址,但对于vmalloc分配的内核虚拟地址无法直接转化成物理地址,所以我们必须对这部分虚拟内存格外“照顾”——先将其转化成内核物理内存映射区域中的地址,然后在用virt_to_phys变为物理地址。

转化工作需要进行如下步骤:

  • 找到vmalloc虚拟内存对应的页表,并寻找到对应的页表项。

  • 获取页表项对应的页面指针

  • 通过页面得到对应的内核物理内存映射区域地址。

  • 如下图所示:

    第二是当访问vmalloc分配区时,如果发现虚拟内存尚未被映射到物理页,则需要处理“缺页异常”。因此需要我们实现内存区域中的nopaga操作,以能返回被映射的物理页面指针,在我们的实例中就是返回上面过程中的内核物理内存映射区域中的地址。由于vmalloc分配的虚拟地址与物理地址的对应关系并非分配时就可确定,必须在缺页现场建立页表,因此这里不能使用remap_page_range方法,只能用vma的nopage方法一页一页的建立。

    程序组成

    map_driver.c,它是以模块形式加载的虚拟字符驱动程序。该驱动负责将一定长的内核虚拟地址(vmalloc分配的)映射到设备文件上。其中主要的函数有——vaddress_to_kaddress()负责对vmalloc分配的地址进行页表解析,以找到对应的内核物理映射地址(kmalloc分配的地址);map_nopage()负责在进程访问一个当前并不存在的VMA页时,寻找该地址对应的物理页,并返回该页的指针。

    test.c它利用上述驱动模块对应的设备文件在用户空间读取读取内核内存。结果可以看到内核虚拟地址的内容(ok!),被显示在了屏幕上。

    执行步骤

    编译map_driver.c为map_driver.o模块,具体参数见Makefile

    加载模块:insmodmap_driver.o

    生成对应的设备文件

    1在/proc/devices下找到map_driver对应的设备命和设备号:grepmapdrv/proc/devices

    2建立设备文件mknodmapfilec 254 0(在我的系统里设备号为254)

    利用maptest读取mapfile文件,将取自内核的信息打印到屏幕上。

    ⑩ linux怎么能跟踪进程每一个vma区域建立的位置

    Tracepath。
    Tracepath指令可以追踪数据到达目标主机的路由早液兆信息,同时还能够发现MTU值。它跟踪路径到目的地,沿着这条路径发现MTU。它使用UDP端口或一些随机端口。
    它类似陆租于Traceroute,只是不需要超级用户特权,并且埋扒没有花哨的选项。

    阅读全文

    与linuxvma相关的资料

    热点内容
    想创app软件怎么操作 浏览:199
    为啥电脑总是显示没有文件夹 浏览:674
    iphone显示无法验证app怎么回事 浏览:968
    推荐解压好游戏 浏览:275
    cpu服务器过载怎么办 浏览:618
    zip内存压缩 浏览:311
    阿里云99元服务器不可以退款 浏览:337
    linux下安装vnc 浏览:656
    微信图片加密编辑可见 浏览:704
    江恩操盘术pdf 浏览:929
    本机构所属的服务器是什么意思 浏览:784
    c编译器有哪些段 浏览:863
    qq原创表情在哪个文件夹 浏览:625
    点点赚app现在怎么不能用了 浏览:363
    网红阿里程序员事件 浏览:203
    算法设计心得体会 浏览:118
    java修改ip 浏览:151
    php不需要编译吗 浏览:136
    特斯拉新车如何用app控制 浏览:187
    文档拖到文件夹就不见了 浏览:816