linux内存管理-内核用户空间 【转】

简介:

转自:http://blog.chinaunix.net/uid-25909619-id-4491362.html

1,linux内存管理中几个重要的结构体和数组

page

unsigned long flags

一组标志,也对页框所在的管理区进行编号

atomic_t _count

该页被引用的次数

atomic_t _mapcount

页框中页表项数目,如果没有则为-1

struct list_head lru

管理page忙碌/空闲链表(inactive_list/active_list),protected by zone->lru_lock !

 

zone

struct free_area free_area[MAX_ORDER]

标识出管理区中的空闲页框块(buddy system)

struct pglist_data *zone_pgdat

该zone的一些属性,包括指向各个node_zone指针等

unsigned long zone_start_pfn

zone start page frame number,zone在mem_map数组中的起始页号。/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */

 

 

pglist_data

struct zone node_zones[MAX_NR_ZONES]

节点中管理区描述符数组

struct zonelist  node_zonelists[MAX_ZONELISTS]

页分配器使用的zonelist数据结构的数组

int nr_zones

节点中管理区的个数

struct page *node_mem_map

节点中页描述符的数组

unsigned long node_start_pfn

节点中第一个页框的下标

 

 

mem_map

初始化调用路径:

free_area_init_node()

    alloc_node_mem_map()

        mem_map = NODE_DATA(0)->node_mem_map;

 

.

2,各个结构体之间的关系

3,主要的内存分配函数

vmalloc

1,vmalloc 申请返回的地址在vmalloc_start到vmalloc_end之间。其中,vmalloc_start在虚拟地址:

3G+physic memory length + 8G(gap)

vmalloc_end位置在虚拟地址:

4G -128K(专用页面映射)

2,kmalloc对应于kfree,可以分配连续的物理内存;

3,vmalloc优先使用高端物理内存,但性能上会打些折扣。

vmalloc分配的物理页不会被交换出去; 

vmalloc使用的是vmlist链表,与管理用户进程的vm_area_struct要区别,而后者会swapped。

 

kmalloc

1,kmalloc申请返回内核虚拟地址,这个虚拟地址与实际的物理地址之间存在偏移量0XC000_0000,可用用virt_to_phys() 得到对应的物理地址。

3G~physic

 

2,vmalloc对应于vfree,分配连续的虚拟内存,但是物理上不一定连续。

3,kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的。

4, kmalloc()是内核中最常见的内存分配方式,它最终调用伙伴系统的__get_free_pages()函数分配,根据传递给这个函数的flags参数,决定这个函数的分配适合什么场合,如果标志是GFP_KERNEL则仅仅可以用于进程上下文中,如果标志GFP_ATOMIC则可以用于中断上下文或者持有锁的代码段中。

kmalloc返回的线形地址是直接映射的,而且用连续物理页满足分配请求,且内置了最大请求数(2**5=32页)。

5,kmalloc 能够处理的最小分配是 32 或者 64 字节(依赖系统的体系所使用的页大小),小于128K

kmap

kmap()是主要用在高端存储器页框的内核映射中,一般是这么使用的:

使用alloc_pages()在高端存储器区得到struct page结构,然后调用kmap(struct *page)在内核地址空间PAGE_OFFSET+896M之后的地址空间中(PKMAP_BASE到FIXADDR_STAR)建立永久映射(如果page结构对应的是低端物理内存的页,该函数仅仅返回该页对应的虚拟地址)

kmap()也可能引起睡眠,所以不能用在中断和持有锁的代码中

不过kmap 只能对一个物理页进行分配,所以尽量少用。

使用kmap的原因:

对于高端物理内存(896M之后),并没有和内核地址空间建立一一对应的关系(即虚拟地址=物理地址+PAGE_OFFSET这样的关系),所以不能使用get_free_pages()这样的页分配器进行内存的分配,而必须使用alloc_pages()这样的伙伴系统算法的接口得到struct *page结构,然后将其映射到内核地址空间,注意这个时候映射后的地址并非和物理地址相差PAGE_OFFSET。

 

get_user_pages

用于从用户空间获取缓冲区地址(页对齐),直接进行IO操作。通常用于大数据量的操作,如DMA。

其访问流程如下:

 

4,其他一些杂

gfp_mask

有三个作用:

1,行为修饰  使用指定的方法分配内存。例如GPF_WAIT,可睡眠;GPF_IO,可启动磁盘。

2,区修饰   标识从哪个分区分配内存

3,类型修饰  例如GFP_KERNEL=>__FGP_WAIT | __GFP_IO | __GFP_FS,指定所需行为和区描述符。

 

migrate_type

 

#define MIGRATE_UNMOVABLE     0

#define MIGRATE_RECLAIMABLE   1

#define MIGRATE_MOVABLE       2

#define MIGRATE_PCPTYPES      3 /* the number of types on the pcp lists */

#define MIGRATE_RESERVE       3

#define MIGRATE_ISOLATE       4 /* can't allocate from here */

#define MIGRATE_TYPES         5

 

对伙伴系统的改进,减少系统碎片。

struct free_area {

    struct list_head    free_list[MIGRATE_TYPES];

    unsigned long        nr_free;

};

每一个free_area包含多个链表,其中每一个链表中的内存页面按照其自身是否可以释放或者迁移被归为一类,于是凡是请求“不可迁移”页面的分配请求全部在free_list[MIGRATE_UNMOVABLE]这条链表上分配,和老版本一样,系统中有10个free_area代表大小为2的N次幂个不同页面的集合。这种归类可以最小化内存碎片。

                                linux内存管理(2)-用户空间

1. 编译链接的一些知识

首先,我们来编写一个简单的程序,示例代码如下

[cpp]  view plain copy print ?
 
  1. #include "stdio.h"  
  2. #include "string.h"  
  3. #include "stdlib.h"  
  4. int i=3;  
  5. int j=4;  
  6. int main()  
  7. {  
  8.    printf("value i is %d\n,address of i is 0x%lx\naddress of j is 0x%lx\n",i,(unsigned int)&i,(unsigned int )&j);  
  9. }  

程序片段1

程序片段1定义了两个变量,a和b,并分别赋值为3,4。然后在主函数main中,打印了i和j的值及其对其对应的虚拟地址。程序的运行结果如下:

[cpp]  view plain copy print ?
 
  1. value i is 3  
  2. address of i is 0x80495e0,  
  3. address of j is 0x80495e4  

片段1

程序片段2中所示的0x80495e0,0x80495e4分别是i和j的虚拟地址,那么,这个地址是怎么确定的?由谁来确定?

通过查看编译后的可执行程序的符号列表,i和j的地址实际上是在链接的过程中确定的,由编译器来确定。

[cpp]  view plain copy print ?
 
  1. objdump -t a.out  
  2.   
  3. a.out:     file format elf32-i386  
  4.   
  5. SYMBOL TABLE:  
  6. ......  
  7. 00000000  w      *UND*  00000000              _Jv_RegisterClasses  
  8. 08048494 g     O .rodata        00000004              _fp_hw  
  9. 08048478 g     F .fini  00000000              _fini  
  10. 00000000       F *UND*  0000019f              __libc_start_main@@GLIBC_2.0  
  11. 08048498 g     O .rodata        00000004              _IO_stdin_used  
  12. 080495dc g       .data  00000000              __data_start  
  13. 080495e0 g     O .data  00000004              i  
  14. 0804849c g     O .rodata        00000000              .hidden __dso_handle  
  15. 080494f0 g     O .dtors 00000000              .hidden __DTOR_END__  
  16. 080483e0 g     F .text  00000069              __libc_csu_init  
  17. 00000000       F *UND*  00000039              printf@@GLIBC_2.0  
  18. 080495e8 g       *ABS*  00000000              __bss_start  
  19. 080495e4 g     O .data  00000004              j  
  20. 080495f0 g       *ABS*  00000000              _end  
  21. 080495e8 g       *ABS*  00000000              _edata  
  22. 08048449 g     F .text  00000000              .hidden __i686.get_pc_thunk.bx  
  23. 08048384 g     F .text  00000044              main  
  24. 08048250 g     F .init  00000000              _init  

 

2. 管理用户空间进程内存的结构体

当一个程序被载入内存,内核就会为其建立一个名为task_struct的结构体来管理这个进程。

[cpp]  view plain copy print ?
 
  1. struct task_struct {  
  2.     struct list_head tasks;  
  3.     struct mm_struct *mm, *active_mm;//字段mm为该进程的内存管理。  
  4.     pid_t pid;  
  5.     struct fs_struct *fs;  
  6.     struct files_struct *files;  
  7. ......  
  8. };  

task_struct

[cpp]  view plain copy print ?
 
  1. struct mm_struct {  
  2.     struct vm_area_struct * mmap; /* list of VMAs ,一个进程有一个mm_struct,多个 
  3.                                 vm_area_struct*/  
  4.     pgd_t * pgd;  
  5.     int map_count; /* number of VMAs */  
  6. ......  
  7. };  

mm_struct

[cpp]  view plain copy print ?
 
  1. struct vm_area_struct {  
  2.     struct mm_struct * vm_mm; /* The address space we belong to. */  
  3.     unsigned long vm_start; /* Our start address within vm_mm. */  
  4.     unsigned long vm_end; /* The first byte after our end address 
  5.     within vm_mm. */  
  6. }  

vm_area_struct

 

 

小结:整个内存管理的体系可以这么理解,在32位系统上,所有的地址(0-4G),页表、页框等都统一由内核管理。而内核中的服务(这里的服务包括内运行在内核中的进程,内核变量等)只是占用了3-4G这个地址空间段。对于用户空间而言,进程的地址是由编译器决定的,编译器在链接各个库文件时,用了0-3G的地址。

对于硬件MMU模块而言,它并不关心Linux给它的地址属于哪个空间,MMU只是根据所设定的规则(内存映射表)找到对应的物理地址。

各结构体之间关系图

 

3. 用户空间malloc的实现

由前面的小结知道,内存管理都在内核空间进行。对于malloc申请的一块内存也是一样,先来看一下malloc调用的图示:

 

malloc->brk()->SYSCALL_DEFINE1(brk, unsigned long, brk)->__get_free_pages() 

 

SYSCALL_DEFINE1这个系统调用,将执行的状态从用户空间转向内核空间。











本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/4846797.html,如需转载请自行联系原作者


相关文章
|
15天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
4天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
8天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
13天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
17天前
|
存储 缓存 监控
Linux内存和硬盘空间管理技巧
了解Linux内存和硬盘管理技巧,提升系统性能和稳定性。使用`free`, `top`, `vmstat`监控内存,通过`sync`, `echo 1 > /proc/sys/vm/drop_caches`清理缓存。利用Swap分区释放内存。借助`df`, `du`检查硬盘空间,清理无用文件,使用`clean-old`, `gzip`, `tar`压缩归档。查找大文件用`find`和`du`,确保
33 0
|
18天前
|
Prometheus 监控 Cloud Native
【Linux】查看系统内存命令(详细讲解)
【Linux】查看系统内存命令(详细讲解)
|
20天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
20天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6
|
1月前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
14 1
|
1月前
|
存储 C语言
C语言--------数据在内存中的存储
C语言--------数据在内存中的存储
26 0