Linux内存

UMA和NUMA模型:

均匀存储器存取(UMA):将多个处理器与一个集中的存储器和I/O总线相连,所有处理器只能访问同一个物理存储器,缺点是伸缩性有限。

NUMA:分布式存储器访问方式处理器可以同时访问不同的存储器地址。NUMA模式下,处理器被划分成多个“节点”,每个节点都可以访问全部的系统物理存储器。内存被划分成多个区域(BANK,簇)。当前多数系统会把内存分割成2块区域,一块是专门给CPU去访问,一块是给外围设备板卡的DMA使用

Linux把物理内存划分为三个层次来管理

层次描述
存储节点(inode)CPU被划分成多个节点(node),内存划分成簇,每个CPU对应一个本地物理内存,即一个CPU-node对应一个内存簇,即每个内存簇被认为是一个节点
管理区(Zone)每个物理内存节点node被划分成多个内存管理区域,用于表示不同范围的内存,内核可以使用不同的映射方式映射物理内存
页面(Page)内存被细分为多个页面帧,页面是最基础的页面分配的单位

系统的物理内存被划分成几个节点(node),一个node对应一个内存簇bank,即每个内存簇被认为是一个节点。(可以使用NODE_DATA(node_id)来查找系统中编号为node_id的节点

内存被划分成节点,每个节点关联到系统的一个处理器内核中用pg_data_t来实例系统中每个节点被链接到一个以NULL为结尾的pgdat_list链表中,其中每个节点使用pg_data_tnode_next字段链接到下一个节点。对于UMA结构,只使用contig_page_data的静态pg_data结构,此时NODE_DATA直接指向全局的contig_page_data.

节点被划分成内存管理区。一个内存管理区使用struct zone_struct描述,zone_t,用以表示内存的某个范围低端范围的16MB被描述为ZONE_DMA,然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出内核段的物理地址域ZONE_HIGHMEM(0xF8000000~0xFFFFFFFF),高端内存,是系统中预留的可用内存空间,不能被内核直接映射。(为了兼容热插拔以及内存碎片化的处理,内核引入一些逻辑上的内存区:

1、内核定义一个伪内存区ZONE_MOVEABLE,在防止物理内存碎片的机制mmeory mirgation中需要使用该内存区域以供内存碎片的极致使用

2、ZONE_DEVICE:为支持热插拔设备而分配的Non Volatile Memory,非易失性内存)。

页帧(page frame):代表内存的最小单元堆内存中每个页都会创建一个struct page的实例。传统上,把内存视为连续的字节,即内存为字节数组,内存单元的编号(地址)可作为字节数组的索引。分页管理时,将若干字节试为一页,比如4K byte,此时内存变成连续页,即内存为页数组每一页物理内存为页帧,以页为单位对内存进行编号,该编号可作为页数组的索引,称为页帧号。 (页的数据结构对象都保存在mem_map全局数组中,该数组通常被存放在ZONE_NORMAL的首部,或者就在小内存系统中装入内核映像而预留的区域之后,在载入内核的低地址至内存区域的后面内存区域,也就是ZONE_NORMAL开始的地方的内存的页的数据结构的对象,都保存在这个全局数组中)。

分页单元可以实现把线性地址转换成物理地址线性地址被划分成固定长度大小的组,称为页页内部的线性地址被映射到连续的物理地址。这样内核可以指定一个页的物理地址和其存储权限,而不用指定页的全部线性地址的存储权限。

分页单元把所有RAM分成固定长度的页帧(也叫页框,page frame),每一个页帧包含一个页,也就是说页帧和页的长度是一样的。页框属于内存的一部分,因此也是一个存储区域。 ----mm_types.h struct page结构体中的mapping,不只保存一个指针,还保存一些额外的信息,用于判断页是否属于未关联地址空间的某个匿名内存区。 通过mapping恢复anon_vma的方法:anon_vma=(struct anon_vma *)(mapping-PAGE_MAPPING_ANON)。

page的flags标识主要分为4部分,其中标志位flag向高位增长,其它的向低位增长,中间存在空闲位

字段描述
section主要用于稀疏内存模型SPARSEMEM,可忽略
nodeNUMA节点号, 标识该page属于哪一个节点
zone内存域标志,标识该page属于哪一个zone
flagpage的状态标识

如下图所示:

mem_map是一个struct page的数组,管理着系统中所有的物理内存页面

virt_to_page(addr)产生线性地址对应的页描述符地址;

pfn_to_page(pfn)产生对应页框号的页描述符地址。

结点状态 node_states

enum node_states {
    N_POSSIBLE,         /* The node could become online at some point 
                         结点在某个时候可能变成联机*/
    N_ONLINE,           /* The node is online 
                        节点是联机的*/
    N_NORMAL_MEMORY,    /* The node has regular memory
                            结点是普通内存域 */
#ifdef CONFIG_HIGHMEM
N_HIGH_MEMORY,      /* The node has regular or high memory 
	                     结点是普通或者高端内存域*/
#else
  N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif

#ifdef CONFIG_MOVABLE_NODE
N_MEMORY,           /* The node has memory(regular, high, movable) */
#else
N_MEMORY = N_HIGH_MEMORY,
#endif
   N_CPU,      /* The node has one or more cpus 结点有一个多多个CPU */ 
   NR_NODE_STATES
};

其中N_POSSIBLE, N_ONLINE和N_CPU用于CPU和内存的热插拔

Linux内核为什么把各个物理内存节点划分成不同的管理区域zone? 硬件限制

ISA(工业标准体系结构)总线的直接内存存簇DMA处理器有一个严格的限制:只能对RAM的前16MB进行寻址

在具有大容量RAM的计算机中,CPU不能直接访问所有的物理地址,因为线性地址的空间太小,内核不可能直接映射所有物理内存到线性地址空间

内核访问高端内存

内核想要访问高于896MB内存时(高端内存),从0xF8000000~0xFFFFFFFF地址空间范围内寻找一段相应大小空闲的逻辑地址空间,借用这段逻辑地址空间。建立映射到想要访问的物理内存。把内核空间与用户空间分开是为了方便MMU的映射,两个不同的逻辑地址(0~3G,3~4G)通过MMU映射到同一块物理内存。

内核1GB线性空间划分如下:

直接映射区:线性空间从3G开始最大896M的区间为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址= 3G+物理地址;

动态内存映射区:该区域有内核函数vmalloc来分配,特点是线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存也可能处于高端内存。

永久内存映射区:该区域可访问高端内存,访问方法是使用alloc_page(_GFP_HLGHMEM),分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。

固定映射区:该区域和4G的顶端只有4K的隔离带,其每个地址项都服务与特定的用途,如ACPI_BASE等。空间特点如下:

1、每个CPU占用一块空间

2、在每个CPU占用的那块空间中,又分为多个小空间,每个小空间大小是1个page目的是定义kmap_types.h中的km_type。

当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后在这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖,通过kmap_atomic()可实现临时映射

页框池:

页框池是由ZONE_DMA和ZONE_NORMAL两个区贡献一些页框:

常用的请求页框和释放页框函数:

alloc_pages(gfp_mask, order): 获得连续的页框,返回页描述符地址,是其他类型内存分配的基础。

__get_free_pages(gfp_mask, order): 获得连续的页框,返回页框对应的线性地址。线性地址与物理地址是内核直接映射方式。不能用于大于896M的高端内存。

__free_pages(page,order);

__free_pages(addr,order);

CPU访问页

在保护模式下,无论CPU运行于用户态还是核心态,CPU执行程序锁范围跟的地址都是虚拟地址,MMU必须通过读取控制寄存器CR3中的值作为当前页面目录的指针,进而根据分页内存映射机制将虚拟地址转换成真正的物理地址才能让CPU真正访问。

对于32位的Linux,其每个进程都有4G的寻址空间,但当一个进程访问其虚拟内存空间中的某个地址时又是咋样实现不与其他进程的虚拟空间混淆的呢?

每个进程都有其自身的页面目录PGD,Linux将该目录的指针存放在与进程对应的内存结构task_struct.(struct mm_struct)mm->pgd中。每当一个进程被调度即将进入运行态时,Linux内核都要将该进程的PGD指针设置CR3(switch_mm())

创建新进程时,分配页

创建一个新的进程时,都要为新进程创建一个新的页面目录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字节则为与所有进程以及内核共享的系统空间。

页表:

用来将虚拟地址映射到物理地址的数据结构称为页表(存放在内存中)。

Linux内核通过四级页表虚拟内存空间分为5个部分(4个页表项用于选择页,1个索引用来表示业内的偏移)。各个体系结构不仅地址长度不同,而且地址字查分的方式也不一定相同。因为内核使用了宏用于将地址分解位各个分量。

  • 页全局目录(Page Gobal Directory):包含若干页上级目录的地址
  • 页上级目录(Page Upper Directory):包含若干页中间目录的地址
  • 页中间目录(Page Middle Directory):包含若干页表的地址
  • 每一个页表项指向一个页框

Linux中使用下列宏简化了页表处理,对于每一级页表都是用有以下三个关键描述宏XXX_SHIFTXXX_SIZEXXX_MASK

四级页表中,对应的宏分别为PGDIR、PUD、PMD和PAGE:

PAGE宏-页表(Page Table):

PMD-Page Middle Directory(页中间目录):

 PUD-Page Upper Directory(页上级目录):

PGD-Page Gobal Directory(页全局目录)

内核还提供了许多宏和函数用于读或修改页表表项:

如果相应的表项值为0,那么,宏pte_none、pmd_none、pud_none和 pgd_none产生的值为1,否则产生的值为0。

宏pte_clear、pmd_clear、pud_clear和 pgd_clear清除相应页表的一个表项,由此禁止进程使用由该页表项映射的线性地址。ptep_get_and_clear( )函数清除一个页表项并返回前一个值

set_pte,set_pmd,set_pud和set_pgd向一个页表项中写入指定的值。set_pte_atomic与set_pte作用相同,但是当PAE被激活时它同样能保证64位的值能被原子地写入。

如果a和b两个页表项指向同一页并且指定相同访问优先级,pte_same(a,b)返回1,否则返回0。

如果页中间目录项指向一个大型页(2MB或4MB),pmd_large(e)返回1,否则返回0。

宏pmd_bad由函数使用并通过输入参数传递来检查页中间目录项。如果目录项指向一个不能使用的页表,也就是说,如果至少出现以下条件中的一个,则这个宏产生的值为1:

页不在主存中(Present标志被清除)

页只允许读访问(Read/Write标志被清除)

Acessed或者Dirty位被清除(对于每个现有的页表,Linux总是 强制设置这些标志)。

pud_bad宏和pgd_bad宏总是产生0。没有定义pte_bad宏,因为页表项引用一个不在主存中的页,一个不可写的页或一个根本无法访问的页都是合法的。

如果一个页表项的Present标志或者Page Size标志等于1,则pte_present宏产生的值为1,否则为0。

前面讲过页表项的Page Size标志对微处理器的分页部件来讲没有意义,然而,对于当前在主存中却又没有读、写或执行权限的页,内核将其Present和Page Size分别标记为0和1。

这样,任何试图对此类页的访问都会引起一个缺页异常,因为页的Present标志被清0,而内核可以通过检查Page Size的值来检测到产生异常并不是因为缺页。

如果相应表项的Present标志等于1,也就是说,如果对应的页或页表被装载入主存,pmd_present宏产生的值为1。pud_present宏和pgd_present宏产生的值总是1。

 分段 VS 分页

分段:是指将程序所需要的内存空间大小的虚拟空间,通过映射机制到某个物理地址空间(映射的操作由硬件完成),分段映射机制解决了之前操作系统存在的两个问题:

(1)地址空间没有隔离

(2)程序运行的地址不确定

不过分段存在一个严重的问题内存使用效率低,分段的内存映射单元是整个程序

分页机制解决了分段机制所存在的内存使用效率问题,核心思想是系统为程序执行文件中的第x页分配了第y页同时将y页会添加到进程虚拟空间地址的映射表中(页表),这样程序就可以通过映射访问到内存页y了。

分页模式下的线性地址转换

 线性地址的本质是索引+偏移量的形式,在四级分页模式下,线性地址被分为5部分,如下图:

在线性地址中,每个页表索引即代表线性地址在对应级别的页表中关联的页表项。正是这种索引与页表项的对应关系形成了整个页表映射机制。

Linux中通过4级页表访问物理内存

Linux中每个进程有自己的PGD(Page Global Directory),是一个物理页并包含一个pgd_t数组,进程的pgd_t数据见tast_struct-> mm_struct->pgd_t *pgd;

通过如下几个函数,不断向下索引,就可以从进程的页表中搜索特定地址对应的页面对象:

启动过程中的内存初始化——三个阶段:

1、系统启动--> (引导内存分配器)bootmem或memblock初始化完成,此阶段只能使用memblock_reserve函数分配内存(内核为系统中每一个结点都提供一个struct bootmem_data结构的实例,用于bootmem的内存管理,含有引导内存分配器给结点分配内存时所需的信息,需要在编译时分配给内核,bootmem存在外碎片的问题,bootmem使用一个位图来管理页,以位图代替原来的空闲链表结构来表述原来的存储空间);

2、bootmem或memblock--> buddy完成前引导内存分配器bootmem或memblock接受内存的管理工作

3、buddy初始化完成--> 系统停止使用cache或buddy分配内存

MMU:

管理虚拟存储器、物理虚拟器的控制线路,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持。

虚拟存储器程序运行之前,只把那些要运行的部分先装入内存,其余部分在等到使用的时候从磁盘载入,当内存不足时,再将暂时不用的部分调出到磁盘。使得大程序可以在小的内存空间执行,也使得内存可以同时装入更多的程序并发执行。

TLB: Translation Lookaside Buffer,即转换旁路缓存缓存少量的虚拟地址和物理地址之间的转换关系,是转换表的cache,被称为“快表”。

TTW: Translation Table walk,即转换表漫游,当TLB中没有缓存对应的地址转换关系时,需要通过对内存中转换表(大多数处理器的转换表为多级页表)的访问来获得虚拟地址和物理地址的对应关系。TTW成功后,结果应写入TLB。

ARM cpu地址转换涉及三种地址:虚拟地址(VA,Virtual Address)、变换后的虚拟地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)。

没有启动MMU时,CPU核心,cache,MMU,外设等所有部件使用的都是物理地址。

启动MMU后,CPU核心对外发出虚拟地址VAVA被转换为MVA供cache,MMU使用在这里MVA被转换成PA;最后使用PA读取实际设备

虚拟地址到物理地址的转换:

ARM CPU使用页表来转换,页表由一个一个条目组成,每个条目存储一段虚拟地址对应的物理地址及访问权限,或下一级页表的地址。

Linux内核空间:

Linux中1GB的内核地址空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域,如图:

一般情况下,物理内存映射区最大长度为896MB,系统的物理内存被顺带映射在内核空间的这个区域中,当系统物理内存大于896MB 时,超过物理内存映射区的那部分内存称为高端内存(而未超过物理内存映射区的内存通常被称为常规内存),内核在存取高端内存时必须将它们映射到高端页面映射区。

系统物理内存超过4GB时,必须使用CPU的扩展分页(PAE)模式所提供的64位页目录项才能存取到4GB以上的物理内存,这需要CPU的支持。

由此可见,在3~4GB之间的内核空间中,从低地址到高地址依次为:物理内存映射区—隔离带—vmalloc虚拟内存分配区—隔离带—高端内存映射区—专用页面映射区—保留区。

Linux内核动态申请

在 Linux 内核空间申请内存涉及的函数主要包括 kmalloc()、_ _get_free_pages()和 vmalloc()等。kmalloc()和_ _get_free_pages()(及其类似函数)申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而 vmalloc()在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而 vmalloc()申请的虚拟内存和物理内存之间也没有简单的换算关系。

1、kmalloc()

函数原型:void *kmalloc(size_t size, int flags);

最常用的是标志flag是GFP_KERNEL,其含义是在内核空间的进程中申请内存。 kmalloc()的底层依赖__get_free_pages()实现分配标志的前缀 GFP 正好是这个底层函数的缩写。使用 GFP_ KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNEL 申请内存

中断处理函数、 tasklet 和内核定时器等非进程上下文中不能阻塞,此时驱动应当使用 GFP_ATOMI标志来申请内存。当使用 GFP_ATOMIC 标志申请内存时,若不存在空闲页,则不等待,直接返回。

其他的相对不常用的申请标志还包括 GFP_USER(用来为用户空间页分配内存,可能阻塞)、GFP_HIGHUSER(类似 GFP_USER, 但是从高端内存分配)、 GFP_NOIO不允许任何 I/O 初始化)、 GFP_NOFS(不允许进行任何文件系统调用)、 _ _GFP_DMA(要求分配在能够 DMA 的内存区)、 _ _GFP_HIGHMEM(指示分配的内存可以位于高端内存)、 _ _GFP_COLD(请求一个较长时间不访问的页)、 _ _GFP_NOWARN当一个分配无法满足时,阻止内核发出警告)、 _ _GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)、 _ _GFP_REPEAT(分配失败则尽力重复尝试)、 _ _GFP_NOFAIL(标志只许申请成功,不推荐)和_ _GFP_NORETRY若申请不到,则立即放弃)。

2、__get_free_pages ()

get_zeroed_page(unsigned int flags);

该函数返回一个指向新页的指针并且将该页清零

_ _get_free_page(unsigned int flags);

该宏返回一个指向新页的指针但是该页不清零,它实际上为:

#define _ _get_free_page(gfp_mask) \

_ _get_free_pages((gfp_mask),0)

就是调用了下面的_ _get_free_pages()申请 1 页。

_ _get_free_pages(unsigned int flags, unsigned int order);

该函数可分配多个页并返回分配内存的首地址,分配的页数为 2^order,分配的页也不清零。 order 允许的最大值是 10(即 1024 页)或者 11(即 2048 页),依赖于具体的硬件平台。

_ _get_free_pages ()和 get_zeroed_page ()的实现中调用了 alloc_pages()函数, alloc_pages()既可以在内核空间分配,也可以在用户空间分配,其原型为:

struct page * alloc_pages(int gfp_mask, unsigned long order);

返回为:分配的第一个页的描述符。

3、slab与内存池

slab算法:可以使对象前后两次被使用时分配在同一块内存或同一类内存空间且保留基本的数据结构,从而提高使用效率

创建 slab 缓存。(使用kmem_cache_create创建后备缓存)

struct kmem_cache *kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,void (*ctor)(void*, struct kmem_cache *, unsigned long),void (*dtor)(void*, struct kmem_cache *, unsigned long));

kmem_cache_create()用于创建一个 slab 缓存,它是一个可以驻留任意数目全部同样大小的后备缓存。

分配 slab 缓存

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

上述函数在 kmem_cache_create()创建的 slab 后备缓冲中分配一块并返回首地址指针。

释放 slab 缓存

void kmem_cache_free(struct kmem_cache *cachep, void *objp);

上述函数释放由 kmem_cache_alloc()分配的缓存。

收回 slab 缓存

int kmem_cache_destroy(struct kmem_cache *cachep);

slab分配器——将内核中经常使用的对象放到高度缓存中,并且由系统保持为初始的利用状态

slab分配器为每种对象分配一个高速缓存每个高速缓存所占的内存区又被划分为多个slab每个slab是由一个或多个连续的页框组成每个页框中包含若干个对象,既有已经分配的对象,也有空闲的对象,其结构图如下:

 高速缓存又分为普通高速缓存和专用高速缓存。

内存池:用于分配大量小对象的后备缓存技术

创建内存池

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,mempool_free_t *free_fn, void *pool_data);

mempool_create()函数用于创建一个内存池, min_nr 参数是需要预分配对象的数目, alloc_fn 和 free_fn是指向内存池机制提供的标准对象分配和回收函数的指针,pool_data 是分配和回收函数用到的指针。

分配和回收对象。

在内存池中分配和回收对象需由以下函数来完成:

void *mempool_alloc(mempool_t *pool, int gfp_mask);

void mempool_free(void *element, mempool_t *pool);

mempool_alloc()用来分配对象,如果内存池分配器无法提供内存,那么就可以用预分配的池。

回收内存池。

void mempool_destroy(mempool_t *pool);

mempool_create()函数创建的内存池需由 mempool_destroy()来回收。

DMA和Cache

Cache:

DMA在内存中操作时,可能会更改Cache映射内存中的数据,导致Cache和内存中的数据不一致,即Cache数据的不一致性。

解决由于DMA导致的Cache一致性问题的最简单方法:直接禁止DMA目标地址范围内内存的Cache功能

DMA:

基于DMA的硬件使用总线地址而不是物理地址,总线地址是从设备角度上看到的内存地址物理地址则是从CPU角度上看到的未经转换的内存地址(经过转换的为虚拟地址)

DMA映射包括两个方面的工作:分配一片DMA缓冲区为一片缓冲区产生设备可访问的地址。DMA需要内核中提供分配一个DMA一致性的内存区域:

void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

上述函数的返回值为申请到的 DMA 缓冲区的虚拟地址,此外,该函数还通过参数 handle 返回 DMA缓冲区的总线地址。 handle 的类型为 dma_addr_t,代表的是总线地址。

dma_alloc_coherent()申请一片 DMA 缓冲区,进行地址映射并保证该缓冲区的 Cache 一致性。

与dma_alloc_coherent()对应的释放函数为:

void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

以下函数用于分配一个写合并(writecombining)的 DMA 缓冲区

void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

与 dma_alloc_writecombine()对应的释放函数 dma_free_writecombine()实际上就是 dma_free_ coherent(),

因为它定义为:

#define dma_free_writecombine(dev,size,cpu_addr,handle) \

dma_free_coherent(dev,size,cpu_addr,handle)

此外, Linux 内核还提供了 PCI 设备申请 DMA 缓冲区的函数 pci_alloc_consistent(),其原型为:

void * pci_alloc_consistent(struct pci_dev *pdev, size_t size, dma_addr_t *dma_addrp);

对应的释放函数为 pci_free_consistent(),其原型为:

void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr,dma_addr_t dma_addr);

申请和释放DMA通道:

申请DMA通道:

int request_dma(unsigned int dmanr, const char * device_id);

同样的,设备结构体指针可作为传入 device_id 的最佳参数。

释放DMA通道:

void free_dma(unsigned int dmanr);

CPU与内存之间的地址映射(由MMU来完成):

CPU与内存之间用地址来查找数据,但是两者的地址并不一样。CPU产生的是逻辑地址,内存产生的是物理地址

地址捆绑的三种形式:

编译时:编译时生成绝对地址;

加载时:编译时,编译器生成可重定位代码,在加载时捆绑

执行时:执行时才能绑定

对于编译时和加载时的地址,物理地址和CPU产生的逻辑地址是一样的,但是运行时的地址是不一样的。

有一个重定位寄存器(基址寄存器),CPU生成的是逻辑地址(偏移量),当要把数据放到内存里,总是要将偏移量加上基址才是真正的物理地址。

对内存空间的合理分配:

(1)连续分配内存:将内存划分成固定大小的很多分区,规定每个分区存放在一个进程的数据。变长分区,而不是使用固定分区来存放一个进程。进程分配的空间总是连续的。一个进程总是被固定在一定范围内,有利于进程保护,当进程出错时,只影响自己所在的那一块内存。连续分配内存会产生内存碎片(外部碎片),也就是两个进程之间总有些空间是没有使用也就是没有使用的。

(2)分页:允许进程分配的地址是非连续的。物理内存中还是分为固定大小的片段,叫做帧,对应的逻辑内存(也就是CPU产生的逻辑地址的空间)也分为同样大的片段 ,叫做页。同样的,辅存(硬盘)的叫做块。页表:记录着CPU逻辑上的地址对应着内存中的哪个物理地址。分页不会产生外部碎片,在最后一层时,可能会产生内部碎片。另外,页表也增加额外的开销。

(3)分段:分段的技术允许编译器定义自己的段。分段仍然需要逻辑地址和物理地址的映射。只不过需要的是段表,段表存放的是不同的数据位于什么“段”,也是用地址标记的,但好像给内存的段取个名字一样。分段是动态分配的,分段也会产生内存碎片碎片。每段从0开始编址,有名称和长度。

碎片:固定分配大小的(分页)会产生内存碎片,而不定长的方法(连续分配内存和分段)会产生外部碎片。

共享和保护:分段和分页总是分为多个模块,如果他们保护的是特定的功能,这样模块可以是共享的,也就是设置为只读(保护)。

每个集成都拥有独立的4G的虚拟内存系统,每次访问内存空间的某个地址,都需要把地址翻译为实际物理地址,各个进程的内存空间具有类似的结构。一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据、代码等从磁盘拷贝到自己的进程空间,哪些数据。在哪里都由进程控制表中的task_struct记录,task_struct记录着一条链表,记录着内存的分配情况。所有进程共享同一物理地址,每个进程只把自己目前需要的物理内存空间映射并存储到物理内存上。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值