IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux-4.19 内存 -> 正文阅读

[系统运维]linux-4.19 内存

目录

内存管理

内存分配&zone 水位设置

slab 分配

kmalloc 分配

malloc 分配

mmap

缺页异常

page

内存管理数据结构图


内存管理

伙伴系统(buddy system),动态管理存储。

内存分成不同的zone 区域:DMA zone ,normal zone ,highmem zone 等;目前kernel 4.19 没有highmem zone 。

kernel-4.19/include/linux/mmzone.h?

struct zone {
/* zone watermarks, access with *_wmark_pages(zone) macros */
	unsigned long watermark[NR_WMARK];

#每个zone 对应的三个水位值,跟杀应用内存回收相关

long lowmem_reserve[MAX_NR_ZONES];

#highmem zone 内存不足会占用一部分normal zone ,normal zone 不足会占用一部分DMA zone #,所以对DMA zone 和 normal zone 都要预留一部分,避免被其它zone 占完。

#/proc/sys/vm # cat min_free_kbytes

....

struct free_area	free_area[MAX_ORDER];

#空闲页管理,这里按照 2^n? (n =0,...,11) 页大小管理空闲的页面

}

struct free_area {
?? ?struct list_head?? ?free_list[MIGRATE_TYPES];
?? ?unsigned long?? ??? ?nr_free;
};

对应每种 2^n 页大小的空闲空间,将对应的空间分为下面的 MIGRATE_TYPES 中类型:

enum migratetype {
45	MIGRATE_UNMOVABLE,
46	MIGRATE_MOVABLE,
47	MIGRATE_RECLAIMABLE,
48#ifdef CONFIG_CMA

}

Linux内存调节之lowmem reserve - 知乎

可以查看?

/proc/zoneinfo

?/proc/pagetypeinfo

?/proc/buddyinfo? (不同类型页面按 2^n? 大小统计的页面数量)

/proc/meminfo?

/proc/vmstat

内存统计信息

vmstat 命令查看系统内存、i/o、cpu 等资源使用情况

内存分配&zone 水位设置

伙伴系统通过alloc_pages 分配物理页?

kernel-4.19/mm/page_alloc.c

kernel-4.19/include/linux/gfp.h
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

gfp_mask 分配掩码,分为两类,一类zone modifiers ,自定从哪个zone 分配,比如_GFP_DMS 、__GFP_MOVABLE 等,由低4位确定;另一类是action modifiers,不限制从哪个zone 分配,但是会改变分配行为;另外一个参数指分配联系页面数 2^n;

如alloc_pages(GFP_KERNEL,order)

->...->_alloc_pages_modemask

这里会调用get_page_from_freelist 尝试分配物理页,如果失败就会调用__alloc_pages_slowpath

水位设置:

7706static void __setup_per_zone_wmarks(void)
7707{
7708?? ?unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
7709?? ?unsigned long pages_low = extra_free_kbytes >> (PAGE_SHIFT - 10);
7710?? ?unsigned long lowmem_pages = 0;
7711?? ?struct zone *zone;
7712?? ?unsigned long flags;
7713
7714?? ?/* Calculate total number of !ZONE_HIGHMEM pages */
7715?? ?for_each_zone(zone) {
7716?? ??? ?if (!is_highmem(zone))
7717?? ??? ??? ?lowmem_pages += zone->managed_pages;
7718?? ?}
7719
7720?? ?for_each_zone(zone) {
7721?? ??? ?u64 min, low;
7722
7723?? ??? ?spin_lock_irqsave(&zone->lock, flags);
7724?? ??? ?min = (u64)pages_min * zone->managed_pages;
7725?? ??? ?do_div(min, lowmem_pages);
7726?? ??? ?low = (u64)pages_low * zone->managed_pages;
7727?? ??? ?do_div(low, vm_total_pages);
7728
7729?? ??? ?if (is_highmem(zone)) {
7730?? ??? ??? ?/*
7731?? ??? ??? ? * __GFP_HIGH and PF_MEMALLOC allocations usually don't
7732?? ??? ??? ? * need highmem pages, so cap pages_min to a small
7733?? ??? ??? ? * value here.
7734?? ??? ??? ? *
7735?? ??? ??? ? * The WMARK_HIGH-WMARK_LOW and (WMARK_LOW-WMARK_MIN)
7736?? ??? ??? ? * deltas control asynch page reclaim, and so should
7737?? ??? ??? ? * not be capped for highmem.
7738?? ??? ??? ? */
7739?? ??? ??? ?unsigned long min_pages;
7740
7741?? ??? ??? ?min_pages = zone->managed_pages / 1024;
7742?? ??? ??? ?min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL);
7743?? ??? ??? ?zone->watermark[WMARK_MIN] = min_pages;
7744?? ??? ?} else {
7745?? ??? ??? ?/*
7746?? ??? ??? ? * If it's a lowmem zone, reserve a number of pages
7747?? ??? ??? ? * proportionate to the zone's size.
7748?? ??? ??? ? */
7749?? ??? ??? ?zone->watermark[WMARK_MIN] = min;
7750?? ??? ?}

slab 分配

?slab 将伙伴系统分配的页可以按字节再次分配

kernel-4.19/mm/slab_common.c
struct kmem_cache *kmem_cache_create(); 创建slab 描述符,用户创建自己的缓冲描述符
kmalloc 用于创建通用slab 缓冲
void kmem_cache_destory(); 销毁slab描述符


kmem_cache_alloc;分配slab 缓存对象,会关闭本地中断
kmem_cache_free ?;释放slab 对象,会关闭本地中断

kernel-4.19/include/linux/slab.h
262#ifdef CONFIG_SLOB
263/*
264 * SLOB passes all requests larger than one page to the page allocator.
265 * No kmalloc array is necessary since objects of different sizes can
266 * be allocated from the same page.
267 */
268#define KMALLOC_SHIFT_HIGH?? ?PAGE_SHIFT
269#define KMALLOC_SHIFT_MAX?? ?(MAX_ORDER + PAGE_SHIFT - 1)
270#ifndef KMALLOC_SHIFT_LOW
271#define KMALLOC_SHIFT_LOW?? ?3
272#endif
273#endif
274
275/* Maximum allocatable size */
276#define KMALLOC_MAX_SIZE?? ?(1UL << KMALLOC_SHIFT_MAX)
277/* Maximum size for which we actually use a slab cache */
278#define KMALLOC_MAX_CfACHE_SIZE?? ?(1UL << KMALLOC_SHIFT_HIGH)
279/* Maximum order allocatable via the slab allocagtor */
280#define KMALLOC_MAX_ORDER?? ?(KMALLOC_SHIFT_MAX - PAGE_SHIFT)
281
282/*
283 * Kmalloc subsystem.
284 */
285#ifndef KMALLOC_MIN_SIZE
286#define KMALLOC_MIN_SIZE (1 << KMALLOC_SHIFT_LOW)
287#endif
28

slab 区域位2^25 * PAGE_SIZE = 32M ;

当分配slab 时,会根据分配的size 从 2^0~2^order( 最大KMALLOC_MAX_ORDER)? 个页面大小尝试,直到找到最合适的order 。? ? ? ??

kernel-4.19/include/linux/slab_def.h

struct kmem_cache {

#cpu 本地缓存

struct array_cache __percpu *cpu_cache;

#cpu缓存&cpu共享缓存计数相关
/* 1) Cache tunables. Protected by slab_mutex */
?? ?unsigned int batchcount;
?? ?unsigned int limit;
?? ?unsigned int shared;

#? ? 根据 num = 2^order 页面/ (size + sizeof(freelist_cache))

#也就是根据分配对象size 大小,确定分配了页面可以细分位多少个对象,这里可以简单认为分配一个size 大小对象,依附分配一个freelist_cache;

? ? unsigned int num;?? ??? ?/* # of objs per slab */??

?? ?struct kmem_cache *freelist_cache;
?? ?unsigned int freelist_size;

#left_over = 2^order -?2^order 页面/ (size + sizeof(freelist_cache))?

#分配了num个对象,还剩余的空间,这个时候?colour_off =?cache_line_size() ,也就是一级缓存

#大小;colour =?left_over/colour_off ,剩余空间可以当多少个一级缓存;

#这里跟本cpu本地缓存,其它cpu共享缓存相关

?? ?size_t colour;?? ??? ??? ?/* cache colouring range */
?? ?unsigned int colour_off;?? ?/* colour offset */

#node 链表管理slab 分配对象使用情况

struct kmem_cache_node *node[MAX_NUMNODES];
}

slob 适用于微小嵌入式系统,slub 使用大型大内存系统,这样性能比slab更好

kmalloc 分配

kmalloc 是使用slab机制,按照2^order 页内存,来创建多个slab 描述符,如16B、32B、64B、... 、32M,系统命名为kmalloc-16等;这些实在系统启动 是create_kmalloc_caches()中完成。

vmalloc 分配函数

vmalloc.c

void *vzalloc(unsigned long size)
{
?? ?return __vmalloc_node_flags(size, NUMA_NO_NODE,
?? ??? ??? ??? ?GFP_KERNEL | __GFP_ZERO);
}

vmallc 优先从高端内存(arm32),从VMALLOC_START~?VMALLOC_END分配,虚拟地址是连续,物理地址不一定连续。 vmalloc 会睡眠,不能在中断上下文使用。

这里会创建struct vm_struct 来描述申请的vmalloc区域,从VMALLOC_START~?VMALLOC_END 查找目前系统中vmap_area_root红黑树查找适合的空闲空间,找到就返回vmap_area 描述符,不存在合适的,就通过alloc_page 来分配物理页面;这里如果分配页面小于一页,调用kmalloc_node ,这就成了slab 分配,大于一页大小调用__vmalloc_node;

?? ??? ??? ? ? ?int node, const void *caller);
1684static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
1685?? ??? ??? ??? ? pgprot_t prot, int node)
1686{
1687?? ?struct page **pages;
1688?? ?unsigned int nr_pages, array_size, i;
1689?? ?const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
1690?? ?const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;
1691?? ?const gfp_t highmem_mask = (gfp_mask & (GFP_DMA | GFP_DMA32)) ?
1692?? ??? ??? ??? ??? ?0 :
1693?? ??? ??? ??? ??? ?__GFP_HIGHMEM;
1694
1695?? ?nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
1696?? ?array_size = (nr_pages * sizeof(struct page *));
1697
1698?? ?/* Please note that the recursion is strictly bounded. */
1699?? ?if (array_size > PAGE_SIZE) {
1700?? ??? ?pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
1701?? ??? ??? ??? ?PAGE_KERNEL, node, area->caller);
1702?? ?} else {
1703?? ??? ?pages = kmalloc_node(array_size, nested_gfp, node);
1704?? ?}
1705
1706?? ?if (!pages) {
1707?? ??? ?remove_vm_area(area->addr);
1708?? ??? ?kfree(area);
1709?? ??? ?return NULL;
1710?? ?}
1711
1712?? ?area->pages = pages;
1713?? ?area->nr_pages = nr_pages;
1714
1715?? ?for (i = 0; i < area->nr_pages; i++) {
1716?? ??? ?struct page *page;
1717
1718?? ??? ?if (node == NUMA_NO_NODE)
1719?? ??? ??? ?page = alloc_page(alloc_mask|highmem_mask);
1720?? ??? ?else
1721?? ??? ??? ?page = alloc_pages_node(node, alloc_mask|highmem_mask, 0);
1722
1723?? ??? ?if (unlikely(!page)) {
1724?? ??? ??? ?/* Successfully allocated i pages, free them in __vunmap() */
1725?? ??? ??? ?area->nr_pages = i;
1726?? ??? ??? ?atomic_long_add(area->nr_pages, &nr_vmalloc_pages);
1727?? ??? ??? ?goto fail;
1728?? ??? ?}
1729?? ??? ?area->pages[i] = page;
1730?? ??? ?if (gfpflags_allow_blocking(gfp_mask|highmem_mask))
1731?? ??? ??? ?cond_resched();
1732?? ?}
1733?? ?atomic_long_add(area->nr_pages, &nr_vmalloc_pages);
1734
1735?? ?if (map_vm_area(area, prot, pages))
1736?? ??? ?goto fail;
1737?? ?return area->addr;
1738
1739fail:
1740?? ?warn_alloc(gfp_mask, NULL,
1741?? ??? ??? ? ?"vmalloc: allocation failure, allocated %ld of %ld bytes",
1742?? ??? ??? ? ?(area->nr_pages*PAGE_SIZE), area->size);
1743?? ?vfree(area->addr);
1744?? ?return NULL;
1745}

再通过map_vm_area 建立页表页面映射。

vma

kernel-4.19/include/linux/mm_types.h

struct mm_struct {
?? ?struct {
?? ??? ?struct vm_area_struct *mmap;?? ??? ?/* list of VMAs */
?? ??? ?struct rb_root mm_rb;
#ifdef CONFIG_SPECULATIVE_PAGE_FAULT
?? ??? ?rwlock_t mm_rb_lock;
#endif
?? ??? ?u64 vmacache_seqnum; ? ? ? ? ? ? ? ? ? /* per-thread vmacache */

mmap 是记录vma 的链表,进程中所以vma 按照地址递增链接的在mmap ;当vma 数量小,查找效率高

mm_rb 进程中按照数链接;当vma 数量很大,查找效率高

find_vma(address ) 通过虚拟地址查找合适的vma;task_struct 中vmacache[] 缓存这最近访问过的vma 信息;进程地址空间在内核中使用VMA来抽象描述,VMA离散分布在3G 用户空间(arm32)。

malloc 分配

malloc 为用户空间进程分配空间,通过brk 系统调用,向内核申请内存分配;内核分配一个VMA 区域,建立页面映射,返回虚拟地址。

在arm32 位系统,3G 用户空间,在每个进程可执行文件,在加载时,从低地址到高地址,为保留区,代码段区,数据段区,接着是brk 分配堆区,start_brk = end_data ,再往上是mmap 区,栈;

mmap

?mmap/munmap 接口时用户空间常用的一个系统调用接口,跟brk 机制类似,有很大vma相关操作。

kernel-4.19/mm/mmap.c

bionic/libc/bionic/mmap.cpp

void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) {
? return mmap64(addr, size, prot, flags, fd, static_cast<off64_t>(offset));
}

addr : 指定映射到进程空间的地址,通常NULL,由内核选择合适地址

size:映射到进程空间的大小

prot:设置内存映射读写属性

flags:设置内存映射属性,如共享映射、私有映射

fd:文件映射对应文件句柄

offset:文件映射时表示文件偏移

flags: MAP_SHARED? 共享区域,多个进程可以通过共享映射一个文件,将文件映射到进程空间,可以看到其它进程修改的内容,修改后内容会同步到磁盘文件。??

MAP_PRIVATE? 创建私有写时复制映射,多个进程通过私有映射同一个文件到进程空间,其它进程看不到修改的内容,修改后内容不会同步磁盘。

MAP_ANONYMOUS 没有关联文件的映射,映射区域会初始化为0

匿名私有映射:通常用于分配内存

文件私有映射:通常用于加载动态库

匿名共享映射:通常用户进程间共享内存;通过shmem 模块打开 /dev/zero? 设备文件来创建

文件共享映射:通常用于内存映射I/O ,用于进程间通信;多个进程将同一个文件映射到进程地址空间,这样修改文件内容,其它进程可以看到

mmap? 文件映射只是建立进程地址空间vma 映射,没有分配page cache 及建立映射和磁盘将文件内容读入page cache。当需要读写文件,会产生缺页中断,将对应的内容从磁盘读入page cache。madvise() 可以让系统建立vma 时预读到page cache.

缺页异常

malloc 和 mmap 都是用户空间api 在内核实现,通过系统调用;两个函数都只创建进程空间地址vma 的映射。当访问时,会发生data abort 缺页异常中断,fault.c 里面调用 do_dataabort(addr ,fsr ,regs)

arm mmu 中有两个跟存储访问失效存储器, 失效状态寄存器 fsr 和 失效地址寄存器 far ,这两个寄存器信息会传入do_dataabort,根据fsr 确定失效原因,及解析来处理函数

1// SPDX-License-Identifier: GPL-2.0
2static struct fsr_info fsr_info[] = {
3?? ?{ do_bad,?? ??? ?SIGBUS, ?0,?? ??? ?"unknown 0"?? ??? ??? ?},
4?? ?{ do_bad,?? ??? ?SIGBUS, ?0,?? ??? ?"unknown 1"?? ??? ??? ?},
5?? ?{ do_bad,?? ??? ?SIGBUS, ?0,?? ??? ?"unknown 2"?? ??? ??? ?},
6?? ?{ do_bad,?? ??? ?SIGBUS, ?0,?? ??? ?"unknown 3"?? ??? ??? ?},
7?? ?{ do_bad,?? ??? ?SIGBUS, ?0,?? ??? ?"reserved translation fault"?? ?},
8?? ?{ do_translation_fault,?? ?SIGSEGV, SEGV_MAPERR,?? ?"level 1 translation fault"?? ?},
9?? ?{ do_translation_fault,?? ?SIGSEGV, SEGV_MAPERR,?? ?"level 2 translation fault"?? ?},
10?? ?{ do_page_fault,?? ?SIGSEGV, SEGV_MAPERR,?? ?"level 3 translation fault"

如果这个时候没有找到vma,发生在用户空间(task_struct->mm_struct 为空),则调用__do_user_fault() 发生异常信号-》force_sig_info 发送信号到异常进程;发生在内核空间,调用__do_kernel_fault();找到了vma ,调用handle_mm_fault建立vma 到物理页面映射。

page

kernel-4.19/include/linux/mm_types.h
struct page {
?? ?unsigned long flags; ? #页面标志位及section、zone 、node等信息


? ? ? ? ? ? ? ? struct address_space *mapping;

?#内核有两种地址,一种时文件映射,这个跟文件存储的介质关联;
#另外一种是匿名映射或ksm,使用低2位判断;最低一位不为0,指向指向struct anon_vma?
#低第二位不为0 对应ksm.

? ? ? ? ? ? ? ? atomic_t _refcount; #内核中计数跟着页面,伙伴系统分配好页面,会设置位1;get_page()会自加,put_page()会
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?#自减;当为 0 时,说明页面空闲或即将释放;大于0 说明已经分配并内核正在使用
? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ?atomic_t _mapcount; ?#被进程PTE 映射的进程数;-1 表示没有进程页映射,0 表示只有父进程pte 映射

}

页面锁PG_locked?

unsigned long flags;定义了一个标志位 PG_locked,内核理页pg_locked获取一个页面锁;lock_page() 用于申请页面锁,被占用就会睡眠等待。

kernel-4.19/include/linux/pagemap.h
kernel-4.19/mm/filemap.c

476/*
477 * lock_page may only be called if we have the page's inode pinned.
478 */
479static inline __sched void lock_page(struct page *page)
480{
481?? ?might_sleep();
482?? ?if (!trylock_page(page))
483?? ??? ?__lock_page(page);
484}
485

/**
1307 * __lock_page - get a lock on the page, assuming we need to sleep to get it
1308 * @__page: the page to lock
1309 */
1310void __sched __lock_page(struct page *__page)
1311{
1312?? ?struct page *page = compound_head(__page);
1313?? ?wait_queue_head_t *q = page_waitqueue(page);
1314?? ?wait_on_page_bit_common(q, page, PG_locked, TASK_UNINTERRUPTIBLE, true);
1315}
1316EXPORT_SYMBOL(__lock_page);

RMVA 反向映射虚拟内存

多个用户进程PTE 映射到一个物理页面;RMVA 就是确定某个物理页面被哪些进程PTE 映射了。

malloc 调用会创建匿名页;cow 写时复制也会创建匿名页面;

anon_vma? 简称av ;anon_vma_chain 简称avc?

fork 创建子进程,会将父进程vma 、PTE 复制到子进程,这样多个vma 页面映射到同一个物理页;面;页可以使用ksm 将不相干vma 页面映射到同一个物理页面。

kswap 回收匿名页面;页面迁移都需要断开跟物理页面映射的PTE 页表项;unmap 用于端口,ksm页面,匿名页面,文件映射页面。

内存管理数据结构图

?

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-29 19:27:13  更:2022-06-29 19:28:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 11:59:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码