Linux内核设计与实现 第十二章 内存管理

news/2024/4/29 6:03:21/文章来源:https://blog.csdn.net/weixin_55255438/article/details/126907784

因为内核内存需要节省着用,内核处理内存分配错误比较麻烦等,所以内核中获取内存不用户空间获取内存复杂得多。
本章讨论内核是如何管理内存和内核之中获取内存的办法。

12.1页

a)
可以通过 getconf 命令来查看系统的page的大小:

[wangyubin@localhost ]$ getconf -a | grep -i 'page'
PAGESIZE                           4096
PAGE_SIZE                          4096
_AVPHYS_PAGES                      637406
_PHYS_PAGES                        2012863

4096(Byte) =2^12(Byte) = 4(Kilobyte),以上的 PAGESIZE 就是当前机器页大小,即 4KB

b)
页的结构体头文件是: <linux/mm_types.h> 位置:include/linux/mm_types.h

/** 页中包含的成员非常多,还包含了一些联合体* 其中有些字段我暂时还不清楚含义,以后再补上。。。*/
struct page {unsigned long flags;    /* 存放页的状态,各种状态参见<linux/page-flags.h> */atomic_t _count;        /* 页的引用计数 */union {atomic_t _mapcount; /* 已经映射到mms的pte的个数 */struct {           /* 用于slab层 */u16 inuse;u16 objects;};};union {struct {unsigned long private;         /* 此page作为私有数据时,指向私有数据 */struct address_space *mapping; /* 此page作为页缓存时,指向关联的address_space */};
#if USE_SPLIT_PTLOCKSspinlock_t ptl;
#endifstruct kmem_cache *slab;    /* 指向slab层 */struct page *first_page;    /* 尾部复合页中的第一个页 */};union {pgoff_t index;        /* Our offset within mapping. */void *freelist;        /* SLUB: freelist req. slab lock */};struct list_head lru;      /* 将页关联起来的链表项 */
#if defined(WANT_PAGE_VIRTUAL)void *virtual;             /* 页的虚拟地址 */
#endif                         /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGSunsigned long debug_flags;    /* Use atomic bitops on this */
#endif#ifdef CONFIG_KMEMCHECK/** kmemcheck wants to track the status of each byte in a page; this* is a pointer to such a status block. NULL if not tracked.*/void *shadow;
#endif
};

c)
硬件MMU:管理内存并把虚拟地址转换为物理地址。
大多数32位体系结构支持4KB的页,而大多数64位系统结构支持8KB。
内核用struct page描述硅制物理内存,不描述内存中的数据。通过struct page可以知道一个页是否空闲,即struct page用于管理页的使用。
page->_count存放页的引用计数,page->_count的值为-1表示内核没有引用此页。
struct page占40字节,一个物理内存4GB、物理页大小为8KB系统共有页面524288个。描述所有页面的struct page总共消耗20MB内存,管理代价不高。

12.2区

a)
页是内存管理的最小单元,但是并不是所有的页对于内核都一样。为了方便管理与使用,内核根据特性对页进行分组。
内核将内存按地址的顺序分成了不同的区,有的硬件只能访问有专门的区。
x86-32系统结构相对简单只是分为了三个区:

描述物理内存
ZONE_DMADMA使用的页<16MB
ZONE_NORMAL正常可寻址的页16~896MB
ZONE_HIGHMEM动态映射的页>896MB

b)
Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配,但是分配不能跨区界限。
当然,内核更希望一般用途的内存从常规区分配,这样节省ZONE_DMA中的页,保证正真需要DMA时,ZONE_DMA中剩余页足够分配。

c)
内核中的分区定义在头文件 <linux/mmzone.h> ,头文件位置:include/linux/mmzone.h
内存区的种类参见 enum zone_type 中的定义。
内存区的结构体定义也在 <linux/mmzone.h> 中,具体参考其中 struct zone 的定义。

12.3获得页

内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口:

void *page_address(struct page *page)//把给定物理页转化成它的逻辑地址,返回一个指针,指向它的逻辑地址struct page* alloc_pages(gfp_t gfp_mask, unsigned int order)//分配 2^order 个页,返回指向第一页页结构的指针
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)//分配 2^order 个页,返回指向第一页逻辑地址的指针struct page *alloc_page(gfp_t gfp_mask)//只分配一页,返回指向页结构的指针
unsigned long __get_free_page(gfp_t gfp_mask)//只分配一页,返回指向其逻辑地址的指针

alloc** 方法和 get** 方法的区别在于,一个返回的是内存的物理地址,一个返回内存物理地址映射后的逻辑地址。
如果无须直接操作物理页结构体的话,一般使用 get** 方法。

1)获得填充为零的页

分配页时,页中数据是随机产生的垃圾信息,但是,有可能其中包含有某些敏感数据,所以特别是分配给用户空间页,就需要将页填充为0。保障系统安全。

unsigned long get_zeroed_page(unsigned int gfp_mask)//只分配一页,并将分配好的页都填充为0。返回指向其逻辑地址的指针

2)释放页

相应的释放内存的函数如下:也是在 <linux/gfp.h> 中定义的

extern void __free_pages(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
extern void free_hot_page(struct page *page);

12.4kmalloc()

kmalloc的定义在 <linux/slab_def.h> 中

void *kmalloc(size_t size, gfp_t flags)
//kmalloc 分配的内存物理地址是连续的,虚拟地址也是连续的,返回的是连续地址的首地址。
// 第 2 个参数, 分配标志,内部最终通过调用 __get_free_pages 来进行,即对应 __get_free_pages 的参数gfp_mask

1) gfp_mask 标志

在请求内存时,参数中有个 gfp_mask 标志,这个标志是控制分配内存时必须遵守的一些规则。
gfp_mask 标志有3类:(所有的 GFP 标志都在 <linux/gfp.h> 中定义)
行为标志 :控制分配内存时,分配器的一些行为
区标志 :控制内存分配在那个区(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM 之类)
类型标志 :由上面2种标志组合而成的一些常用的场景

行为标志主要有以下几种:

行为标志描述
__GFP_WAIT分配器可以睡眠
__GFP_HIGH分配器可以访问紧急事件缓冲池
__GFP_IO分配器可以启动磁盘I/O
__GFP_FS分配器可以启动文件系统I/O
__GFP_COLD分配器应该使用高速缓存中快要淘汰出去的页
__GFP_NOWARN分配器将不打印失败警告
__GFP_REPEAT分配器在分配失败时重复进行分配,但是这次分配还存在失败的可能
__GFP_NOFALL分配器将无限的重复进行分配。分配不能失败
__GFP_NORETRY分配器在分配失败时不会重新分配
__GFP_NO_GROW由slab层内部使用
__GFP_COMP添加混合页元数据,在 hugetlb 的代码内部使用

区标志主要以下3种:

区标志描述
__GFP_DMA从 ZONE_DMA 分配
__GFP_DMA32只在 ZONE_DMA32 分配 (注1)
__GFP_HIGHMEM从 ZONE_HIGHMEM 或者 ZONE_NORMAL 分配 (注2)

注1:ZONE_DMA32 和 ZONE_DMA 类似,该区包含的页也可以进行DMA操作。
唯一不同的地方在于,ZONE_DMA32 区的页只能被32位设备访问。
注2:优先从 ZONE_HIGHMEM 分配,如果 ZONE_HIGHMEM 没有多余的页则从 ZONE_NORMAL 分配。

类型标志是编程中最常用的,在使用标志时,应首先看看类型标志中是否有合适的,如果没有,再去自己组合 行为标志和区标志。

类型标志实际标志描述
GFP_ATOMIC__GFP_HIGH这个标志用在中断处理程序,下半部,持有自旋锁以及其他不能睡眠的地方
GFP_NOWAIT0与 GFP_ATOMIC 类似,不同之处在于,调用不会退给紧急内存池。
这就增加了内存分配失败的可能性
GFP_NOIO__GFP_WAIT这种分配可以阻塞,但不会启动磁盘I/O。
这个标志在不能引发更多磁盘I/O时能阻塞I/O代码,可能会导致递归
GFP_NOFS(__GFP_WAIT | __GFP_IO)这种分配在必要时可能阻塞,也可能启动磁盘I/O,但不会启动文件系统操作。
这个标志在你不能再启动另一个文件系统的操作时,用在文件系统部分的代码中
GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS )这是常规的分配方式,可能会阻塞。这个标志在睡眠安全时用在进程上下文代码中。
为了获得调用者所需的内存,内核会尽力而为。这个标志应当为首选标志
GFP_USER(__GFP_WAIT | __GFP_IO | __GFP_FS )这是常规的分配方式,可能会阻塞。用于为用户空间进程分配内存时
GFP_HIGHUSER(__GFP_WAIT | __GFP_IO | __GFP_FS )|__GFP_HIGHMEM)从 ZONE_HIGHMEM 进行分配,可能会阻塞。用于为用户空间进程分配内存
GFP_DMA__GFP_DMA从 ZONE_DMA 进行分配。需要获取能供DMA使用的内存的设备驱动程序使用这个标志。通常与以上的某个标志组合在一起使用。

以上各种类型标志的使用场景总结:

场景相应标志
进程上下文,可以睡眠使用 GFP_KERNEL
进程上下文,不可以睡眠使用 GFP_ATOMIC,在睡眠之前或之后以 GFP_KERNEL 执行内存分配
中断处理程序使用 GFP_ATOMIC
软中断使用 GFP_ATOMIC
tasklet使用 GFP_ATOMIC
需要用于DMA的内存,可以睡眠使用 (GFP_DMA|GFP_KERNEL)
需要用于DMA的内存,不可以睡眠使用 (GFP_DMA|GFP_ATOMIC),或者在睡眠之前执行内存分配

2)Kfree()

kmalloc 和 vmalloc 所对应的释放内存的方法分别为:

void kfree(const void *)
void vfree(const void *)

12.5vmalloc()

vmalloc通过分配非连续的物理内存块,再“修改”页表,把内存映射到逻辑地址空间的连续区域中。
虽然一般必须要物理连续的内存的是硬件,但是使用vmalloc需要专门建立页表项,影响性能,最终我们基本都用kmalloc()来获得内存。

kmalloc的定义在 <linux/slab_def.h> 中

void *vmalloc(unsigned long size)//vmalloc 分配的内存物理地址是不连续的,虚拟地址是连续的

kmalloc 和 vmalloc 所对应的释放内存的方法分别为:

void kfree(const void *)
void vfree(const void *)

12.6slab层

高速缓存:硬件高速缓存的容量大,访问速度接近于CPU的速度(CPU的时钟频率)。CPU可以直接向高速缓存中存取数据,从而减少了时间,提高了系统的运行速度。
空闲链表:空闲链表包含可供使用的、已经分配好的数据结构块。当代码需要一个新的数据结构实例时,就可以从空闲链表中抓取一个,而不需要请求分配内存。使用完放回空闲链表,而不是释放它。
分配和释放数据结构是所有内核普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。
CPU访问高速缓存快于访问内存,从空闲链表提取放回内存快于从内核请求释放内存。

1)slab层的设计

高速缓存:硬件高速缓存的容量大,访问速度接近于CPU的速度(CPU的时钟频率)。CPU可以直接向高速缓存中存取数据,从而减少了时间,提高了系统的运行速度。
空闲链表:空闲链表包含可供使用的、已经分配好的数据结构块。当代码需要一个新的数据结构实例时,就可以从空闲链表中抓取一个,而不需要请求分配内存。使用完放回空闲链表,而不是释放它。
分配和释放数据结构是所有内核普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。
CPU访问高速缓存快于访问内存,从空闲链表提取放回内存快于从内核请求释放内存。

slab分配器扮演着通用数据结构缓存层的角色。
linux中的高速缓存是用所谓 slab 层来实现的,slab层即内核中管理高速缓存的机制。
整个slab层的原理如下:
可以在内存中建立各种对象的高速缓存(比如进程描述相关的结构 task_struct 的高速缓存)
除了针对特定对象的高速缓存以外,也有通用对象的高速缓存
每个高速缓存中包含多个 slab,slab用于管理缓存的对象
slab中包含多个缓存的对象,物理上由一页或多个连续的页组成
在这里插入图片描述

2)slab分配器的接口

slab结构体的定义参见:mm/slab.c

struct slab {struct list_head list;   /* 存放缓存对象,这个链表有 满,部分满,空 3种状态  */unsigned long colouroff; /* slab 着色的偏移量 */void *s_mem;             /* 在 slab 中的第一个对象 */unsigned int inuse;      /* slab 中已分配的对象数 */kmem_bufctl_t free;      /* 第一个空闲对象(如果有的话) */unsigned short nodeid;   /* 应该是在 NUMA 环境下使用 */
};

slab层的使用主要有四个方面:
高速缓存的创建
从高速缓存中分配对象
向高速缓存释放对象
高速缓存的销毁

/*** 创建高速缓存* 参见文件: mm/slab.c* 这个函数的注释很详细,这里就不多说了。*/
struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,unsigned long flags, void (*ctor)(void *))/*** 从高速缓存中分配对象也很简单* 函数参见文件:mm/slab.c* @cachep - 指向高速缓存指针* @flags  - 之前讨论的 gfp_mask 标志,只有在高速缓存中所有slab都没有空闲对象时,*           需要申请新的空间时,这个标志才会起作用。** 分配成功时,返回指向对象的指针*/
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)/*** 向高速缓存释放对象* @cachep - 指向高速缓存指针* @objp   - 要释放的对象的指针*/
void kmem_cache_free(struct kmem_cache *cachep, void *objp)/*** 销毁高速缓存* @cachep - 指向高速缓存指针 */
void kmem_cache_destroy(struct kmem_cache *cachep)

3)尝试使用slab分配器的接口

我做了创建高速缓存的例子,来尝试使用上面的几个函数。
测试代码如下:(其中用到的 kn_common.h 和 kn_common.c 参见之前的博客《Linux内核设计与实现》读书笔记(六)- 内核数据结构)

#include <linux/slab.h>
#include <linux/slab_def.h>
#include "kn_common.h"MODULE_LICENSE("Dual BSD/GPL");#define MYSLAB "testslab"static struct kmem_cache *myslab;/* 申请内存时调用的构造函数 */
static void ctor(void* obj)
{printk(KERN_ALERT "constructor is running....\n");
}struct student
{int id;char* name;
};static void print_student(struct student *);static int testslab_init(void)
{struct student *stu1, *stu2;/* 建立slab高速缓存,名称就是宏 MYSLAB */myslab = kmem_cache_create(MYSLAB,sizeof(struct student),0,0,ctor);//创建高速缓存MYSLAB(#define MYSLAB "testslab")/* 高速缓存中分配2个对象 */printk(KERN_ALERT "alloc one student....\n");stu1 = (struct student*)kmem_cache_alloc(myslab, GFP_KERNEL);//从高速缓存myslab中分配对象stu1->id = 1;stu1->name = "wyb1";print_student(stu1);printk(KERN_ALERT "alloc one student....\n");stu2 = (struct student*)kmem_cache_alloc(myslab, GFP_KERNEL);stu2->id = 2;stu2->name = "wyb2";print_student(stu2);/* 释放高速缓存中的对象 */printk(KERN_ALERT "free one student....\n");kmem_cache_free(myslab, stu1);//向高速缓存myslab释放对象stu1printk(KERN_ALERT "free one student....\n");kmem_cache_free(myslab, stu2);//向高速缓存myslab释放对象stu2/* 执行完后查看 /proc/slabinfo 文件中是否有名称为 “testslab”的缓存。(保存着监视系统中所有活动的 slab 缓存的信息的文件为/proc/slabinfo) */return 0;
}static void testslab_exit(void)
{/* 删除建立的高速缓存 */printk(KERN_ALERT "*************************\n");print_current_time(0);kmem_cache_destroy(myslab);//销毁高速缓存printk(KERN_ALERT "testslab is exited!\n");printk(KERN_ALERT "*************************\n");/* 执行完后查看 /proc/slabinfo 文件中是否有名称为 “testslab”的缓存 */
}static void print_student(struct student *stu)
{if (stu != NULL){printk(KERN_ALERT "**********student info***********\n");printk(KERN_ALERT "student id   is: %d\n", stu->id);printk(KERN_ALERT "student name is: %s\n", stu->name);printk(KERN_ALERT "*********************************\n");}elseprintk(KERN_ALERT "the student info is null!!\n");    
}module_init(testslab_init);//注册模块加载函数,调用函数 module_init 来声明 xxx_init 为驱动入口函数,当加载驱动的时候 xxx_init
函数就会被调用。
module_exit(testslab_exit);//注册模块卸载函数,调用函数module_exit来声明xxx_exit为驱动出口函数,当卸载驱动的时候xxx_exit
函数就会被调用。

Makefile文件如下:

# must complile on customize kernel
obj-m += myslab.o
myslab-objs := testslab.o kn_common.o#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complie object
all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesrm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
#clean
clean:rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned

执行测试代码:(我是在 centos6.3 x64 上实验的)

[root@vbox chap12]# make
[root@vbox chap12]# insmod myslab.ko                 #加载驱动文件myslab.ko,Linux 下模块扩展名为.ko。
[root@vbox chap12]# dmesg | tail -220                #平时通过insmod加载驱动文件的时候,不输出log,所以没办法看ko中输出的log,但是通过dmesg | tail 可以看到日志反馈.#保存着监视系统中所有活动的 slab 缓存的信息的文件为/proc/slabinfo[root@vbox chap12]# cat /proc/slabinfo | grep test   #查看我们建立的缓存名在不在系统中。(#define MYSLAB "testslab")
testslab               0      0     16  202    1 : tunables  120   60    0 : slabdata      0      0      0
[root@vbox chap12]# rmmod myslab.ko #卸载内核模块
[root@vbox chap12]# cat /proc/slabinfo | grep test   #我们的缓存名已经不在系统中了。#cat连接文件并打印到标准输出设备上。grep 命令用于查找文件里符合条件的字符串。

12.7在栈上的静态分配

内核栈小而固定
当给每个进程分配一个固定大小的栈后,不但可以减少内存的消耗,而且内核也无须负担太重的栈管理任务。

1)单页内核栈

内核栈可以在编译时配置选项,设置成4~16KB,即一页为4KB时,可以设置成1、2、3、4页。一页为8KB时,可以设置成1、2页。
历史上中断处理程序和被中断的进程共享一个栈,后可以激活1页栈选项,使中断处理程序获得自己的一页栈。

2)在栈上光明正大地工作

在这里插入图片描述
即静态分配内存不要超过几百字节,需要大块内存,使用动态分配。

12.8高端内存的映射

zone n.区;气候带
high adj.高的
Memory n.内存
高端内存就是之前提到的 ZONE_HIGHMEM 区的内存。
在x86体系结构中,这个区的内存不能映射到内核地址空间上,也就是没有逻辑地址,
为了使用 ZONE_HIGHMEM 区的内存,内核提供了永久映射和临时映射2种手段:

1)永久映射

永久映射的函数是可以睡眠的,所以只能用在进程上下文中。

/* 将 ZONE_HIGHMEM 区的一个page永久的映射到内核地址空间* 返回值即为这个page对应的逻辑地址*/
static inline void *kmap(struct page *page)/* 允许永久映射的数量是有限的,所以不需要高端内存时,应该及时的解除映射 */
static inline void kunmap(struct page *page)

2)临时映射

临时映射不会阻塞,也禁止了内核抢占,所以可以用在中断上下文和其他不能重新调度的地方。

/*** 将 ZONE_HIGHMEM 区的一个page临时映射到内核地址空间* 其中的 km_type 表示映射的目的,* enum kn_type 的定义参见:<asm/kmap_types.h>,它描述了临时映射的目的*/
static inline void *kmap_atomic(struct page *page, enum km_type idx)/* 相应的解除映射是个宏 */
#define kunmap_atomic(addr, idx)    do { pagefault_enable(); } while (0)

以上的函数都在 <linux/highmem.h> 中定义的。

12.9每个CPU的分配

用一个数组,一项是一个CPU的数据,几个CPU就有几项。
在这里插入图片描述
按CPU来分配数据主要有2个优点:
最直接的效果就是减少了对数据的锁,提高了系统的性能
由于每个CPU有自己的数据,所以处理器切换时可以大大减少缓存失效的几率 (*注1)

注1:如果一个处理器操作某个数据,而这个数据在另一个处理器的缓存中时,那么存放这个数据的那个
处理器必须清理或刷新自己的缓存。持续的缓存失效成为缓存抖动,对系统性能影响很大。

12.10新的每个CPU接口

1)编译时的每个CPU数据

a)
每个CPU数据:“专用数组”的数据(用一个数组,一项是一个CPU的数据,几个CPU就有几项)。
可以在编译时就定义分配给每个CPU的变量,其分配的接口参见:<linux/percpu-defs.h>
编译时定义就是静态实现,“专用数组”个人猜测可以用宏创建。此“专用数组”正真怎么实现看源码

注意下面两个宏,一个是声明,一个是定义。
其实也就是 DECLARE_PER_CPU 中多了个 extern 的关键字

/* 给每个CPU声明一个类型为 type,名称为 name 的变量 */
DECLARE_PER_CPU(type, name)
/* 给每个CPU定义一个类型为 type,名称为 name 的变量 */
DEFINE_PER_CPU(type, name)

b)
分配好变量后,就可以在代码中使用这个变量 name 了。

DEFINE_PER_CPU(int, name);      /* 为每个CPU定义一个 int 类型的name变量 */get_cpu_var(name)++;            /* 当前处理器上的name变量 +1 */
put_cpu_var(name);              /* 完成对name的操作后,激活当前处理器的内核抢占 */

c)
通过 get_cpu_var 和 put_cpu_var 的代码,我们可以发现其中有禁止和激活内核抢占的函数。
相关代码在 <linux/percpu.h> 中

#define get_cpu_var(var) (*({                \extern int simple_identifier_##var(void);    \preempt_disable();/* 这句就是禁止当前处理器上的内核抢占 */    \&__get_cpu_var(var); }))
#define put_cpu_var(var) preempt_enable()  /* 这句就是激活当前处理器上的内核抢占 */

2)运行时的每个CPU数据

除了像上面那样静态的给每个CPU分配数据,还可以以指针的方式在运行时给每个CPU分配数据。
动态分配参见:<linux/percpu.h>

/* 给每个处理器分配一个 size 字节大小的对象,对象的偏移量是 align */
extern void *__alloc_percpu(size_t size, size_t align);
/* 释放所有处理器上已分配的变量 __pdata */
extern void free_percpu(void *__pdata);/* 还有一个宏,是按对象类型 type 来给每个CPU分配数据的,* 其实本质上还是调用了 __alloc_percpu 函数 */
#define alloc_percpu(type)    (type *)__alloc_percpu(sizeof(type), \__alignof__(type))

动态分配的一个使用例子如下:

void *percpu_ptr;
unsigned long *foo;percpu_ptr = alloc_percpu(unsigned long);
if (!percpu_ptr)/* 内存分配错误 */foo = get_cpu_var(percpu_ptr);
/* 操作foo ... */
put_cpu_var(percpu_ptr);

12.11使用每个CPU数据的原因

按CPU来分配数据主要有2个优点:
最直接的效果就是减少了对数据的锁,提高了系统的性能
由于每个CPU有自己的数据,所以处理器切换时可以大大减少缓存失效的几率 (*注1)

注1:如果一个处理器操作某个数据,而这个数据在另一个处理器的缓存中时,那么存放这个数据的那个
处理器必须清理或刷新自己的缓存。持续的缓存失效成为缓存抖动,对系统性能影响很大。

12.12分配函数的选择

在众多的内存分配函数中,如何选择合适的内存分配函数很重要,下面总结了一些选择的原则:

应用场景分配函数选择
如果需要物理上连续的页选择低级页分配器或者 kmalloc 函数
如果kmalloc分配是可以睡眠指定 GFP_KERNEL 标志
如果kmalloc分配是不能睡眠指定 GFP_ATOMIC 标志
如果不需要物理上连续的页vmalloc 函数 (vmalloc 的性能不如 kmalloc)
如果需要高端内存alloc_pages 函数获取 page 的地址,在用 kmap 之类的函数进行映射
如果频繁撤销/创建教导的数据结构建立slab高速缓存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_10110.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux开发_CentOS7.4服务器安装NFS、NGINX服务器,ffmpeg、Qt环境

1. 环境介绍 环境介绍&#xff1a;采用的是华为云的ECS弹性云服务器–镜像安装的CentOS7.4 64位 -----是服务器版&#xff0c;非桌面版哦。 在CentOS7.4服务器版本的环境下搭建NFS服务器、安装ffmpeg、安装nginx服务器、部署Qt编译环境。 &#xff08;1&#xff09;配置NGIN…

河北稳控科技几种振弦采集仪的主要区别是什么?

河北稳控科技几种振弦采集仪的主要区别是什么?VH系列属于手持系列,多用于振弦传感器现场单次测量使用;VH501TC采集读数仪,设备是专用的多类型传感器手持式读数仪,主测传感类型为单弦式振弦传感器,辅测传感类型为电压、电流传感。采用 32 位 ARM 处理器和大尺寸全彩屏、阵…

无人机群编队分析的定位问题 分析与思考-1(数学建模竞赛2022年B题)

2022年高教社杯全国大学生数学建模竞赛结束了&#xff0c;在此我们对 2022年 B题 进行一些分析与思考。 1. 初步印象 2022年 B题 &#xff08;无人机遂行编队飞行中的纯方位无源定位&#xff09;是一个有趣的题目。 随着无人机技术的快速发展&#xff0c;早已从高科技变做寻常…

【Java】运算符

我不去想是否能够成功 既然选择了远方 便只顾风雨兼程 —— 汪国真 目录 1. 认识运算符 1.1 认识运算符 1.2 运算符的分类 2. 算术运算符 2.1 四则运算符 2.2 复合赋值运算符 2.3 自增 / 自减 运算符 3.关系运算符 4.逻辑运算符 4.1 逻辑与 && 4.2 逻…

分库分表实践

分库分表实践 分库分表概念以及使用场景 分库分表用来解决单表数据量太大&#xff0c;引起的性能问题。使用分库分表后能够根据特定路由键值将数据分布在不同库以及不同表中&#xff0c;解决了单表数据量的性能、运维等问题。一般来讲&#xff0c;单一数据库实例的数据的阈值…

【网络】HTTP协议详解

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

svn 代码迁入gitlab

window中安装好git客户端,右键空白处,点选git bash here进入git界面,输入命令 将svn38163之后的所有记录都备份那:git svn clone -r 38163:HEAD svn地址 --no-metadata trunk(本地电脑目录名) --username *** 备份所有提交记录:git svn clone svn地址 --no-metadata …

Linux安装Python 以及过程中的命令详细介绍

下载源码包 打开 Python 官网 找到需要的安装包 获取了资源的链接后&#xff0c;进入Linux下载&#xff0c;wget意思是webget&#xff0c; 即下载 wget https://www.python.org/ftp/python/3.10.7/Python-3.10.7.tgz目录下会新增 这样源码包就下载好了。 如果下载太慢&…

二叉树与递归问题

目录 一&#xff1a;求二叉树的深度 二&#xff1a;二叉树反转 三&#xff1a;二叉树镜像判断 四&#xff1a;递归的终止条件 用递归解决的问题必须注意的&#xff1a; 递归的终止条件&#xff0c;也就是递归的出口&#xff08;否则&#xff1a;栈溢出&#xff09;递归的过…

决策树简介

决策树简介 决策树实际上是一个布尔函数,它的输出可以是“0 或 1”或“-1 或 +1”或“-1、0 或 +1”。决策树的大小等于其中存在的节点数,其深度等于从顶部到根的最长路径的长度。 错误率:训练集始终是给模型的标记示例,模型训练得越多,其错误率就越低。 训练样本 = { set…

程序里对象很深很大,可以用这个设计模式缓解一下

如果一个类的有非常多的属性&#xff0c;层级还很深。这个妥妥的是我的对象很大&#xff0c;你创建的时候忍一下......那你每次要创建的时候都忍一下&#xff1f;有没有一种好的方式让我们创建太的时候使用体验更好一点呢? 今天的文章里就给大家介绍一种设计模式&#xff0c;来…

C++多线程的线程返回值问题

对于多线程可执行对象的返回值是何时返回&#xff0c;以及得到的呢&#xff1f; 对于需要用到线程返回值的线程要使用future类对象来实现 文章目录future对象async()launch::deferred参数launch::async参数packaged_taskpromisefuture对象 是一个类模板 提供访问异步对象的操作…

优化 | Management Science 7-8月文章精选: 信息系统中的运筹学

作者&#xff1a;Evelyn Yao 清华大学本科在读 在“Management Science近期论文精选”中&#xff0c;我们有主题、有针对性地选择了Management Science中一些有趣的文章&#xff0c;不仅对文章的内容进行了概括与点评&#xff0c;而且也对文章的结构进行了梳理&#xff0c;旨在…

非零基础自学Java (老师:韩顺平) 第13章 常用类 13.5 StringBuffer类

非零基础自学Java (老师&#xff1a;韩顺平) ✈【【零基础 快速学Java】韩顺平 零基础30天学会Java】 第13章 常用类 文章目录非零基础自学Java (老师&#xff1a;韩顺平)第13章 常用类13.5 StringBuffer类13.5.1 基本介绍13.5.2 String VS StringBuffer13.5.3 String 和 Str…

HashMap

1.HashMap集合 1.1HashMap集合概述和特点【理解】 HashMap底层是哈希表结构的依赖hashCode方法和equals方法保证键的唯一如果键要存储的是自定义对象&#xff0c;需要重写hashCode和equals方法 1.2 特点 HashMap是线程不安全的实现&#xff1b; HashMap可以使用null作为key…

【Pytorch深度学习实战】(9)神经语言模型(RNN-LM)

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

第一视角体验搭载全志T507-H的开发板MYD-YT507H开发板

如今车规级芯片市场潜力巨大&#xff0c;需求旺盛&#xff0c;芯片都在逐渐走向国产化。本文要介绍的主角是MYD-YT507H开发板&#xff0c;该开发板是米尔科技结合全志国产工业级平台CPU——全志T507-H芯片研制的CPU模组&#xff0c;全志T507-H可广泛用于电力物联网、汽车电子、…

目标检测开源框架YOLOv6全面升级,更快更准的2.0版本来啦

9月5日&#xff0c;美团视觉智能部发布了YOLOv6 2.0版本&#xff0c;本次更新对轻量级网络进行了全面升级&#xff0c;量化版模型 YOLOv6-S 达到了 869 FPS&#xff0c;同时&#xff0c;还推出了综合性能优异的中大型网络&#xff08;YOLOv6-M/L&#xff09;&#xff0c;丰富了…

一个div靠左另一个靠右

1.使用flex布局<style>#back{border: red solid 1px;width: 800px;height: 500px;display: flex;align-items: center;}#left{border: blue 1px solid;width: 100px;height: 100px;justify-content: flex-start;}#right{border: blue 1px solid;width: 100px;height: 100…

【前端进阶】-TypeScript类型声明文件详解及使用说明

前言 博主主页&#x1f449;&#x1f3fb;蜡笔雏田学代码 专栏链接&#x1f449;&#x1f3fb;【TypeScript专栏】 前三篇文章讲解了TypeScript的一些高级类型 详细内容请阅读如下&#xff1a;&#x1f53d; 【前端进阶】-TypeScript高级类型 | 泛型约束、泛型接口、泛型工具类…