九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Linux內(nèi)存管理(二)

注:本文中提到的ICE為一Android工程,對(duì)應(yīng)Linux內(nèi)核版本為2.6.29。

2.6 slab分配器

從前面分析可知,內(nèi)核對(duì)內(nèi)存的管理都是以頁(yè)為最小單位的,也就是說(shuō)想從內(nèi)核申請(qǐng)內(nèi)存,必須是頁(yè)的倍數(shù)。如果只想申請(qǐng)幾十個(gè)字節(jié),獲取到的也至少是一頁(yè),而且這一頁(yè)的剩余部分別人是不能使用的,因此明顯造成浪費(fèi)。Linux使用slab分配器對(duì)從內(nèi)核獲取到的頁(yè)再次分配,以減少浪費(fèi)。

slab分配器有三個(gè)目標(biāo):

I      減少系統(tǒng)分配小塊內(nèi)存時(shí)產(chǎn)生的碎片;

II     把經(jīng)常使用的對(duì)象緩存起來(lái),減少分配、初始化及釋放對(duì)象的時(shí)間開(kāi)銷;

III   調(diào)整對(duì)象以更好的使用硬件高速緩存。

打個(gè)比方:slab就是高速列車上搞零售的列車員。從廠家整批貨物,然后搬到列車上分拆成單件由乘客挑選購(gòu)買。這里的關(guān)鍵是高速列車,對(duì)應(yīng)我們的高速緩存(cache),而批進(jìn)的貨物即是通過(guò)頁(yè)框分配器獲取的大塊內(nèi)存,單件就是被slab拆分初始化后的對(duì)象(小段內(nèi)存),而對(duì)象才是真正供給開(kāi)發(fā)者(乘客)使用的。不同的是它是免費(fèi)的,但注意這里是文明的世界,你不可以搶的。slab為了實(shí)現(xiàn)上面目標(biāo),做了如下操作:

I      獲取一塊內(nèi)存;

II     科學(xué)劃分成大小為2n或某個(gè)結(jié)構(gòu)體長(zhǎng)度的小塊(稱為對(duì)象);

III   將初始化好的slab放入高速緩存;

IV   利用slab著色及對(duì)象對(duì)齊提高訪問(wèn)速度。

slab與高速緩存密不可分,下面先來(lái)看看它們的描述符:

高速緩存描述符結(jié)構(gòu)為kmem_cache

類型

名字

說(shuō)明

struct array_cache * []

array

CPU指針數(shù)組,指向包含空閑對(duì)象的本地高速緩存

unsigned int

batchcount

要移進(jìn)/移出本地高速緩存的大批對(duì)象的數(shù)量

unsigned int

limit

本地高速緩存中空閑對(duì)象的最大數(shù)目。可調(diào)

unsigned int

buffer_size

單個(gè)對(duì)象的大小,即kmem_cache_alloc()每次可獲得的內(nèi)存的大小

unsigned int

reciprocal_buffer_size

buffer_size的倒數(shù),系統(tǒng)中沒(méi)有使用該變量

unsigned int

flags

描述高速緩存永久屬性的一組標(biāo)志

unsigned int

num

封裝在一個(gè)單獨(dú)slab中的對(duì)象個(gè)數(shù)

unsigned int

gfporder

一個(gè)單獨(dú)slab中包含的連續(xù)頁(yè)框數(shù)目的對(duì)數(shù)

gfp_t

gfpflags

分配頁(yè)框時(shí)傳遞給伙伴系統(tǒng)函數(shù)的一組標(biāo)志

size_t

colour

slab使用的顏色個(gè)數(shù)

unsigned int

colour_off

slab中的基本對(duì)齊偏移

struct kmem_cache *

slabp_cache

指向包含slab描述符的普通slab高速緩存,如果使用內(nèi)部slab描述符,該字段為NULL

unsigned int

slab_size

單個(gè)slab的大小

unsigned int

dflags

描述高速緩存動(dòng)態(tài)屬性的一組標(biāo)志

void (*ctor)(void *obj)

ctor

指向與高速緩存相關(guān)的構(gòu)造/析構(gòu)方法

const char *

name

指向高速緩存的名字

struct list_head

next

高速緩存描述符雙向鏈表使用的指針

struct kmem_list3 *[]

nodelists

每個(gè)節(jié)點(diǎn)上屬于該高速緩存的所有slab鏈表,請(qǐng)看下表

2-5:高速緩存描述符

kmem_list3結(jié)構(gòu)。

類型

名字

說(shuō)明

struct list_head

slabs_partial

包含空閑和非空閑對(duì)象的slab描述符雙向循環(huán)鏈表

struct list_head

slabs_full

不包含空閑對(duì)象的slab描述符雙向循環(huán)鏈表

struct list_head

slabs_free

只包含空閑對(duì)象的slab描述符雙向循環(huán)鏈表

unsigned long

free_objects

高速緩存中空閑對(duì)象的個(gè)數(shù)

spinlock_t

list_lock

本結(jié)構(gòu)使用的自旋鎖

struct array_cache *

shared

指向所有CPU共享的一個(gè)本地高速緩存的指針

unsigned long

next_reap

slab分配器的頁(yè)回收算法使用

int

free_touched

slab分配器的頁(yè)回收算法使用

2-6kmem_list3結(jié)構(gòu)

slab描述結(jié)構(gòu)為struct slab。

類型

名字

說(shuō)明

struct list_head

list

slab描述符的三個(gè)雙向循環(huán)鏈表中的一個(gè)(在高速緩存描述符的kmem_list3結(jié)構(gòu)中的slabs_full、slabs_partialslabs_free鏈表)使用的指針

struct list_head

colouroff

slab中第一個(gè)對(duì)象的偏移

void *

s_mem

slab中的第一個(gè)對(duì)象的地址

unsigned int

inuse

當(dāng)前正在使用的slab中的對(duì)象個(gè)數(shù)

kmem_bufctl_t

free

slab中下一個(gè)空閑對(duì)象的下標(biāo)

unsigned short

nodeid

slab所屬節(jié)點(diǎn)的ID號(hào)

2-7slab描述符

下圖(來(lái)自于《Linux虛擬內(nèi)存管理》)展示了高速緩存與slab關(guān)系:

 

2-4:高速緩存、slab及對(duì)象關(guān)系圖

高速緩存分通用和專用,所謂通用就是系統(tǒng)初始化時(shí)實(shí)現(xiàn)準(zhǔn)備了一組常用大小的內(nèi)存對(duì)象組供內(nèi)核開(kāi)發(fā)者使用,比如使用kmalloc()獲取。下圖為ICE平臺(tái)上使用的通用高速緩存。

 

2-5ICE平臺(tái)的通用高速緩存

專用高速緩存是指專為一個(gè)結(jié)構(gòu)體創(chuàng)建的高速緩存。比如文件的inode結(jié)構(gòu),用的很多,但其大小在通用高速緩存中找不到,如果強(qiáng)行從通用高速緩存中申請(qǐng)空間勢(shì)必會(huì)造成內(nèi)存浪費(fèi)。因此,系統(tǒng)專門(mén)為其創(chuàng)建一個(gè)專用高速緩存,使得對(duì)應(yīng)對(duì)象的大小就是inode結(jié)構(gòu)的大?。ㄐ枰?/span>4字節(jié)對(duì)齊)。下圖為ICE平臺(tái)上使用的專用高速緩存。

 

 

2-6ICE平臺(tái)的專用高速緩存

無(wú)論是通用還是專用高速緩存都是通過(guò)kmem_cache_create()函數(shù)創(chuàng)建的。它們的區(qū)別是:通用是在系統(tǒng)初始化時(shí)(start_kernel()->kmem_cache_init())系統(tǒng)創(chuàng)建的,而專用則是由內(nèi)核開(kāi)發(fā)者根據(jù)需要?jiǎng)?chuàng)建的。這個(gè)函數(shù)只是獲取一個(gè)高速緩存描述符并將其插入到高速環(huán)存鏈表中,如圖2-4。

一個(gè)新創(chuàng)建的高速緩存沒(méi)有包含任何slab,因此也沒(méi)有空閑的對(duì)象。只有當(dāng)以下兩個(gè)條件都為真時(shí),才給高速緩存分配slab

I      已發(fā)出一個(gè)分配新對(duì)象的請(qǐng)求,即調(diào)用kmem_cache_alloc()請(qǐng)求對(duì)象;

II     高速緩存不包含任何空閑對(duì)象。

當(dāng)上述兩個(gè)條件都滿足時(shí),slab分配器調(diào)用cache_grow()函數(shù)來(lái)增加一個(gè)slab。這個(gè)函數(shù)又調(diào)用kmem_getpages()從頁(yè)框分配器獲得一組頁(yè)框(由高速緩存描述符中的gfporder確定為2 gfporder個(gè)頁(yè)框)。接著cache_grow()調(diào)用cache_init_objs(),將前一組頁(yè)框分成n個(gè)對(duì)象(大小有高速緩存描述符中的obj_size指定),再調(diào)用描述符中的構(gòu)造函數(shù)(由kmem_cache_create()指定)對(duì)各個(gè)對(duì)象進(jìn)行初始化。然后將初始化好的slab插入到空閑slab鏈表中(slabs_free鏈表)。

2.7 kmalloc

如果slab分配器、伙伴系統(tǒng)算法弄明白了,kmalloc()就不用講,它幾乎完全等同于kmem_cache_alloc(),不同的是kmalloc()有自己選擇通用高速緩存的機(jī)制,而后者需要申請(qǐng)者指定高速緩存。看下代碼就全明白了:

 

33__builtin_constant_p(size)gcc的內(nèi)部函數(shù),意思是說(shuō)如果size是常量函數(shù)就返回true,否則返回false。但David Howells他們說(shuō)了,該函數(shù)在kmalloc里恒為真,可參考android/kernel目錄下的aa(內(nèi)核開(kāi)發(fā)者修改提交記錄)文件:

 

2-7:內(nèi)核開(kāi)發(fā)者提交記錄片段

通過(guò)增加打印證明kmalloc()函數(shù)中無(wú)論是變量還是常量__builtin_constant_p()返回的值均為1

39~46行的目的即是到通用高速緩存表中查找符合size的高速緩存,來(lái)看一下kmalloc_sizes.h,全部?jī)?nèi)容如下:

 

如果找到則調(diào)用kmem_cache_alloc()函數(shù)到找到的那個(gè)高速緩存中的slab鏈表上找一個(gè)未用的對(duì)象(內(nèi)存塊的首地址)給申請(qǐng)者,否則換回NULL。

從上面的分析及驗(yàn)證來(lái)看,第55行應(yīng)該不會(huì)被調(diào)用到。但我們?nèi)匀粊?lái)分析一下,函數(shù)體如下:

void *__kmalloc(size_t size, gfp_t flags)

{

       return __do_kmalloc(size, flags, NULL);

}

再看__do_kmalloc()函數(shù):

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,

                                     void *caller)

{

       struct kmem_cache *cachep;

 

       /* If you want to save a few bytes .text space: replace

        * __ with kmem_.

        * Then kmalloc uses the uninlined functions instead of the inline

        * functions.

        */

       cachep = __find_general_cachep(size, flags);

       if (unlikely(ZERO_OR_NULL_PTR(cachep)))

              return cachep;

       return __cache_alloc(cachep, flags, caller);

}

可以看出仍然是調(diào)用__find_general_cachep()從通用高速緩存總找到相應(yīng)大小的高速緩存,然后再調(diào)用__cache_alloc()(事實(shí)上kmem_cache_alloc()中只有一行,就是調(diào)用__cache_alloc())從slab中找一個(gè)空閑對(duì)象(內(nèi)存塊的首地址)返回給申請(qǐng)者。

2.8 vmalloc

伙伴系統(tǒng)算法一節(jié)中講到減少內(nèi)存碎片的兩種辦法:一種是用伙伴系統(tǒng)有效管理內(nèi)存;二是將不連續(xù)的內(nèi)存映射到連續(xù)的線性地址,vmalloc()就是這種方法的實(shí)現(xiàn)。

vmalloc()映射到的連續(xù)線性地址不是隨意的,內(nèi)核專門(mén)為其劃了一塊空間,位于物理內(nèi)存映射末端偏移8M的地方。請(qǐng)見(jiàn)圖1-1,范圍為0x9D800000~0xE0000000,請(qǐng)參考內(nèi)核中VMALLOC_STARTVMALLOC_END定義。也就是說(shuō)映射在內(nèi)核空間,用戶態(tài)的程序是不能訪問(wèn)的,要訪問(wèn)得進(jìn)入內(nèi)核態(tài)。

來(lái)看一下該過(guò)程中的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)vm_struct,下表為其參數(shù)描述:

類型

名字

說(shuō)明

struct vm_struct *

next

指向下一個(gè)vm_struct結(jié)構(gòu)的指針

void *

addr

內(nèi)存區(qū)內(nèi)第一個(gè)內(nèi)存單元的線性地址

unsigned long

size

內(nèi)存區(qū)的大小加上4096(一頁(yè)的安全區(qū),為了防止越界訪問(wèn))

unsigned long

flags

映射的內(nèi)存的類型,如:VM_ALLOC表示使用vmalloc()得到的頁(yè),VM_MAP表示使用vmap()映射的已經(jīng)被分配的頁(yè),而VM_IOREMAP表示使用ioremap()映射的硬件設(shè)備的板上內(nèi)存

struct page **

pages

該指針數(shù)組每個(gè)元素指向一個(gè)可獲得的頁(yè)描述符

unsigned int

nr_pages

內(nèi)存區(qū)填充的頁(yè)的個(gè)數(shù)

unsigned long

phys_addr

該字段一般不用,除非內(nèi)存被創(chuàng)建來(lái)映射一個(gè)硬件設(shè)備的I/O共享內(nèi)存

void *

caller

指向該內(nèi)存區(qū)的使用者

2-8:不連續(xù)內(nèi)存描述符

下面是vmalloc()源碼:

void *vmalloc(unsigned long size)

{

       return __vmalloc_node(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL,

                                   -1, __builtin_return_address(0));

}

再來(lái)看__vmalloc_node(),

static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,

                                          int node, void *caller)

{

       struct vm_struct *area;

 

       size = PAGE_ALIGN(size);

       if (!size || (size >> PAGE_SHIFT) > num_physpages)

              return NULL;

 

       area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,

                                          node, gfp_mask, caller);

 

       if (!area)

              return NULL;

 

       return __vmalloc_area_node(area, gfp_mask, prot, node, caller);

}

該函數(shù)表明不可以傳入一個(gè)比物理內(nèi)存還大的size。接著調(diào)用__get_vm_area_node()獲取一段連續(xù)的線性地址,并申請(qǐng)一個(gè)不連續(xù)內(nèi)存區(qū)描述符,即vm_struct數(shù)據(jù)塊,并初始化。__vmalloc_area_node()實(shí)現(xiàn)的是申請(qǐng)不連續(xù)內(nèi)存,并將其映射到前面獲取到的連續(xù)線性地址上。請(qǐng)看源碼:

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,

                             pgprot_t prot, int node, void *caller)

{

       struct page **pages;

       unsigned int nr_pages, array_size, i;

 

       nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;

       array_size = (nr_pages * sizeof(struct page *));

 

       area->nr_pages = nr_pages;

       /* Please note that the recursion is strictly bounded. */

       if (array_size > PAGE_SIZE) {

              pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,

                            PAGE_KERNEL, node, caller);

              area->flags |= VM_VPAGES;

       } else {

              pages = kmalloc_node(array_size,

                            (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,

                            node);

       }

       area->pages = pages;

       area->caller = caller;

       if (!area->pages) {

              remove_vm_area(area->addr);

              kfree(area);

              return NULL;

       }

 

       for (i = 0; i < area->nr_pages; i++) {

              struct page *page;

 

              if (node < 0)

                     page = alloc_page(gfp_mask);

              else

                     page = alloc_pages_node(node, gfp_mask, 0);

 

              if (unlikely(!page)) {

                     /* Successfully allocated i pages, free them in __vunmap() */

                     area->nr_pages = i;

                     goto fail;

              }

              area->pages[i] = page;

       }

 

       if (map_vm_area(area, prot, &pages))

              goto fail;

       return area->addr;

 

fail:

       vfree(area->addr);

       return NULL;

}

代碼有點(diǎn)長(zhǎng),但確實(shí)很簡(jiǎn)單。先是申請(qǐng)一塊空間用來(lái)保存不連續(xù)內(nèi)存區(qū)描述中的pages數(shù)組;接著一頁(yè)一頁(yè)(這是至關(guān)重要的)的申請(qǐng)內(nèi)存,并將申請(qǐng)到的內(nèi)存的頁(yè)描述符存放到pages數(shù)組中;最后調(diào)用map_vm_area()pages中的各頁(yè)映射到連續(xù)線性地址上。最后一步是難點(diǎn),從代碼中可知,其原理就是申請(qǐng)新的目錄項(xiàng)和頁(yè)表項(xiàng),并通過(guò)線性地址段、頁(yè)描述符里的信息重新設(shè)置它們。由于還不太了解arm硬件的這一塊,此處代碼略過(guò)。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
linux內(nèi)存管理
工程師深度:學(xué)通Linux內(nèi)核(含詳細(xì)代碼)
muddogxp
Linux內(nèi)存管理之SLAB分配器
Linux slab
slab分配器簡(jiǎn)明分析
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服