FreeBSD VM核心記憶體管理(轉)

gugu99發表於2007-08-11
FreeBSD VM核心記憶體管理(轉)[@more@]

  本文涉及到的原始碼是FreeBSD5.0Release,參考4.4BSD設計與實現相關章節,Matt Dillon的文章。

  VM系統涉及的主要資料結構描述

  1. vmspace

  該結構用於描述一個程式的虛擬地址空間,其包含了平臺無關性的vm_map結構和平臺相關性的pmap結構,以及該程式記憶體使用的一些統計計量。

  2. vm_map

  該結構是描述與平臺無關性的虛擬地址空間的最高層資料結構,其包含了一系列虛擬地址有效地址對映實體和這些對映的屬性。

  3. vm_map_entry

  該結構描述了一段虛擬地址空間(start – end),以及該段地址空間代表的是一種VM物件、另一個地址對映還是一個地址子對映,及其相應的共享保護和繼承等屬性。

  4. vm_object

  該結構描述了一段虛擬地址空間的資料來源,它可以描述一個檔案、一段為零的記憶體和一個裝置等等。

  5. vm_page

  該結構描述了一頁實體記憶體,是VM用於表述實體記憶體的低層資料結構。頁尺寸是在系統啟動時,由平臺決定的。

  6. pagerops (vm_pager)

  該結構描述了VM物件的後臺儲存如何訪問,在FreeBSD中,是透過pagerops結構描述函式指標,實現不同型別的物件的具體操作,在vm_object結構中,透過handle成員指定具體型別物件對應的資料結構,比如裝置型別對應dev_t (cdev結構指標)。在一般OS描述中,採用vm_pager描述該目的的資料結構。

  本文集中討論FreeBSD核心虛擬地址空間的管理,涉及到核心地址空間分配和核心地址空間動態分配。FreeBSD的核心空間總是被對映到每一個程式的地址空間的最高部分。和任何其它程式一樣,核心也是透過包含一系列的vm_map_entry結構實體的vm_map結構來管理一段地址空間的使用。子對映是核心對映特有的,用於隔離、限制一段地址空間以提供給核心子系統使用,比如mbuf操作。本文主要討論與平臺無關性的內容,涉及到平臺相關性時,以i386為例簡要說明。

  1. SI_SUB_VM初始化

  在系統啟動時,mi_startup()函式會呼叫SI_SUB_VM初始化與平臺無關的VM系統,其定義是:“SYSINIT(vm_mem, SI_SUB_VM, SI_ORDER_FIRST, vm_mem_init, NULL)”。在vm_mem_init函式初始化之後,我們就只使用虛擬記憶體了,現在分析該函式的實現:

  vm_set_page_size();

  該函式設定頁面尺寸,i386是PAGE_SIZE(4K),記錄在系統統計vmmeter結構型別的全域性變數cnt的v_page_size成員中。

  virtual_avail = vm_page_startup(avail_start, avail_end, virtual_avail);

  該語句初始化常駐記憶體模組。分析函式vm_page_startup的引數和返回值:avail_start的值是從系統啟動時,組合語言呼叫init386的入參first,指明有效記憶體的起始地址;avail_end是在getmemsize()函式結束時,透過avail_end = phys_avail[pa_indx];語句獲得,該函式是一個與平臺相關的函式,這裡不作詳細討論,只須明白getmemsize()函式是獲得具體實體記憶體的尺寸;virtual_avail是指向第一個可用頁面的虛擬地址,在呼叫vm_page_startup函式前,是在pmap_bootstrap函式中獲得初始值,並在vm_page_startup函式中調整獲得真實的值。函式vm_page_startup將實體記憶體整理、分配為頁面單元,並初始化頁面管理模組所需資訊,每一個頁面單元被放置在自由連結串列中,該函式實現的詳細討論在頁排程中討論,作為核心管理涉及到的區域分配器初始化的一部分是在該函式中透過呼叫uma_startup函式實現的,該函式的實現在隨後討論。

  vm_object_init();

  初始化VM的物件模組,FreeBSD是透過統一的vm_object結構使用虛擬記憶體,該函式完成虛擬記憶體物件模組所需資訊的初始化。

  vm_map_startup();

  初始化VM地址對映模組。

  kmem_init(virtual_avail, virtual_end);

  該函式建立核心虛擬地址對映關係,將核心文字、資料、BSS和所有系統啟動時已經分配了的空間做一個對映,插入VM_MIN_KERNEL_ADDRESS和virtual_avail之間,餘下的virtual_avail和virtual_end之間的地址空間是可用的自由空間。

  pmap_init(avail_start, avail_end);

  該函式初始化實體記憶體地址空間的對映關係。

  vm_pager_init();

  該函式實現系統所支援的所有頁面介面型別的初始化,頁面介面為資料在其支援的儲存空間和實體記憶體之間的移動提供了一種機制,比如磁碟裝置與記憶體之間,檔案系統與記憶體之間。

  至此,vm_mem_init函式執行完成,VM系統初始化完成。

  2. 核心地址空間分配

  VM系統核心使用的虛擬地址空間段提供了一套用於分配和釋放的函式,這些空間段可以從核心地址對映和子對映中分配獲得。

  根據申請的頁是否可以被pageout守護程式排程,核心記憶體分配有兩種路徑。在VM子系統初始化時,呼叫了kmem_init函式建立了核心對映。我們分析該函式的具體實現:

  函式void kmem_init(vm_offset_t start, vm_offset_t end)

  m = vm_map_create(kernel_pmap, VM_MIN_KERNEL_ADDRESS, end);

  函式vm_map_create根據給定了kernel_map實體地址,建立一個新的地址對映m,而VM_MIN_KERNEL_ADDRESS和end給出了該對映範圍的下水位(lower address bound)和上水位(upper address bound)。

  kernel_map = m;

  kernel_map->system_map = 1;

  (void) vm_map_insert(m, NULL, (vm_offset_t) 0,

  VM_MIN_KERNEL_ADDRESS, start, VM_PROT_ALL, VM_PROT_ALL, 0);

  vm_map_unlock(m);

  由於函式kmem_init僅用於系統初始化,建立核心地址對映,因此,將獲得的地址對映賦給全域性變數kernel_map儲存,透過vm_map_insert函式建立一個vm_map_entry實體記錄相關值,VM_PROT_ALL和VM_PROT_ALL標識這段虛擬地址的訪問許可權,參見/sys/vm/vm.h定義。

  2.1 Wired (nonpageable,不可被pageout排程的頁)分配函式

  固定頁(wired page)是從來不會產生頁錯誤(page fault)。其分配是由kmem_alloc函式和kmem_malloc函式實現的。

  函式vm_offset_t kmem_alloc(vm_map_t map, vm_size_t size)

  該函式用於在核心地址對映或子對映中,分配記憶體。

  size = round_page(size);

  調整申請記憶體的尺寸,使之為PAGE_SIZE的整數倍。

  vm_map_lock(map);

  if (vm_map_findspace(map, vm_map_min(map), size, &addr)) {

  vm_map_unlock(map);

  return (0);

  }

  offset = addr - VM_MIN_KERNEL_ADDRESS;

  vm_object_reference(kernel_object);

  vm_map_insert(map, kernel_object, offset, addr, addr + size,

  VM_PROT_ALL, VM_PROT_ALL, 0);

  vm_map_unlock(map);

  在vm_map結構的lock鎖機制保護下。透過呼叫vm_map_findspace函式查詢map地址對映是否有足夠的空間滿足申請的記憶體尺寸,如果成功,則可用空間的起始地址存於addr中;如果失敗則返回1,則kmem_alloc函式呼叫失敗。獲得該記憶體分配空間起始地址與VM_MIN_KERNEL_ADDRESS的偏移。透過vm_object_reference函式對核心物件kernel_obj計數器ref_count加1,kernel_obj的初始化是在VM初始化時,呼叫vm_object_init實現的,其型別是OBJT_DEFAULT。呼叫vm_map_insert函式將剛找到的虛擬地址空間插入地址對映map的vm_map_entry連結串列中。

  for (i = 0; i < size; i += PAGE_SIZE) {

  vm_page_t mem;

  mem = vm_page_grab(kernel_object, OFF_TO_IDX(offset + i),

  VM_ALLOC_ZERO | VM_ALLOC_RETRY);

  if ((mem->flags & PG_ZERO) == 0)

  pmap_zero_page(mem);

  mem->valid = VM_PAGE_BITS_ALL;

  vm_page_flag_clear(mem, PG_ZERO);

  vm_page_wakeup(mem);

  }

  接下來的這段程式碼非常有意思,對於申請的記憶體空間每一頁,透過呼叫vm_page_grab函式,檢視該頁是否已經被kernel_object持有,如果是,則根據vm_page成員flags標識,如果是PG_BUSY,則等待該標識清PG_BUSY,將該頁重新設定為PG_BUSY,返回該地址對映(mem),如果該頁沒有被kernel_object持有,則分配一個新頁(mem)。透過判斷PG_ZERO標識,保證該頁已經清零。最後透過vm_page_wakeup函式,給正在等待該頁分配的執行緒一個喚醒的機會。這段程式碼在查詢kernel_object持有頁時,採用了自頂向下的展開演算法(Sleator and Tarjan's top-down splay algorithm)。

  (void) vm_map_wire(map, addr, addr + size, FALSE);

  設定該段記憶體是wired。

  函式kmem_alloc是非常低層的,一般和平臺相關性的函式在申請記憶體會呼叫該函式,比如sysarch()。通常kmem_alloc是使用kernel_map地址對映和kernel_object VM物件。

  函式vm_offset_t kmem_malloc(vm_map_t map, vm_size_t size, int flags)

  該函式同樣是用於在核心地址對映或子對映中,分配記憶體。區別是:

  a) kmem_alloc函式在不能獲得記憶體時,可以阻塞等待,而在中斷層,分配記憶體是不能阻塞的,因此需要kmem_malloc以M_NOWAIT標識呼叫。

  b) kmem_malloc函式為malloc呼叫(malloc(9))提供一種實現機制,即:在核心需要動態分配記憶體(malloc)時,當申請尺寸大於其閥值,最終透過kmem_malloc實現空間分配。

  c) kmem_alloc路徑是使用地址對映kernel_map和物件kernel_object;而kmem_malloc路徑是使用kernel_map的子對映kmem_map和物件kmem_object,後者的具體討論在後一節說明。

  size = round_page(size);

  addr = vm_map_min(map);

  首先,根據入參調整、設定尺寸和地址。

  vm_map_lock(map);

  if (vm_map_findspace(map, vm_map_min(map), size, &addr)) {

  vm_map_unlock(map);

  if (map != kmem_map) {

  static int last_report; /* when we did it (in ticks) */

  if (ticks < last_report || (ticks - last_report) >= hz) {

  last_report = ticks;

  }

  goto bad;

  }

  if ((flags & M_NOWAIT) == 0)

  panic("kmem_malloc(%ld): kmem_map too small: %ld total allocated",

  (long)size, (long)map->size);

  goto bad;

  }

  offset = addr - VM_MIN_KERNEL_ADDRESS;

  vm_object_reference(kmem_object);

  vm_map_insert(map, kmem_object, offset, addr, addr + size,

  VM_PROT_ALL, VM_PROT_ALL, 0);

  這段程式碼的思路和kmem_alloc函式相應的程式碼是一致的,找到足夠的記憶體空間,返回該空間的起始地址addr,並獲得該地址與核心地址對映的偏移(offset),不同之處在於區別c。

  retry:

  m = vm_page_alloc(kmem_object, OFF_TO_IDX(offset + i), pflags);

  if (m == NULL) {

  if ((flags & M_NOWAIT) == 0) {

  vm_map_unlock(map);

  VM_WAIT;

  vm_map_lock(map);

  goto retry;

  }

  while (i != 0) {

  i -= PAGE_SIZE;

  m = vm_page_lookup(kmem_object, OFF_TO_IDX(offset + i));

  vm_page_lock_queues();

  vm_page_free(m);

  vm_page_unlock_queues();

  }

  vm_map_delete(map, addr, addr + size);

  vm_map_unlock(map);

  goto bad;

  }

  if (flags & M_ZERO && (m->flags & PG_ZERO) == 0)

  pmap_zero_page(m);

  vm_page_flag_clear(m, PG_ZERO);

  m->valid = VM_PAGE_BITS_ALL;

  針對申請的記憶體空間的每一頁(for (i = 0; i < size; i += PAGE_SIZE)):首先透過vm_page_alloc分配一頁,如果分配成功(m != NULL),則根據入參是否含有M_ZERO決定是否需要對分配的頁清零,最後設定該頁的標識。

  如果分配失敗(m == NULL):如果入參標識不含有M_NOWAIT,則可以阻塞等待(VM_WAIT),再次嘗試;如果含有M_NOWAIT,則說明該次申請是不能阻塞等待,則釋放所有已經分配的頁面,申請失敗(goto bad)。

  if (!vm_map_lookup_entry(map, addr, &entry) ||

  entry->start != addr || entry->end != addr + size || entry->wired_count != 0)

  panic("kmem_malloc: entry not found or misaligned");

  entry->wired_count = 1;

  vm_map_simplify_entry(map, entry);

  對於該次申請分配空間對應的vm_map_entry實體成員設值,透過呼叫函式vm_map_simplify_entry建立該entry實體與map其它實體建立關聯。

  for (i = 0; i < size; i += PAGE_SIZE) {

  m = vm_page_lookup(kmem_object, OFF_TO_IDX(offset + i));

  vm_page_lock_queues();

  vm_page_wire(m);

  vm_page_wakeup(m);

  vm_page_unlock_queues();

  pmap_enter(kernel_pmap, addr + i, m, VM_PROT_ALL, 1);

  vm_page_flag_set(m, PG_WRITEABLE | PG_REFERENCED);

  }

  vm_map_unlock(map);

  return (addr);

  這段程式碼是對該次申請的每一頁,透過pmap_enter函式將每一頁的虛擬地址對映關係加入在指定的kernel_pmap中。最後返回該次申請的記憶體起始地址。

  bad:

  return (0);

  如果該次申請出錯,則返回空值。

  2.2 Pageable (可以由pageout排程的頁)分配函式

  函式vm_offset_t kmem_alloc_pageable(vm_map_t map, vm_size_t size)

  該函式用於分配可以被pageout排程的記憶體空間,其中map只能是kernel_map或其子對映。

  size = round_page(size);

  addr = vm_map_min(map);

  result = vm_map_find(map, NULL, 0,

  &addr, size, TRUE, VM_PROT_ALL, VM_PROT_ALL, 0);

  if (result != KERN_SUCCESS) {

  return (0);

  }

  return (addr);

  這段程式碼十分清晰,透過vm_map_find函式,如果能找到足夠的記憶體空間,則分配給addr,並在該map中插入一個vm_map_entry實體表示該段記憶體對映。如果不成功,則返回空值。

  函式vm_offset_t kmem_alloc_nofault (vm_map_t map, vm_size_t size)

  該函式的實現和kmem_alloc_pageable函式基本一致,只是在呼叫vm_map_find函式時,其最後一位引數是MAP_NOFAULT,具體用意,我不太確定。

  函式 vm_offset_t kmem_alloc_wait(vm_map_t map, vm_size_t size)

  該函式實現核心一個子對映的記憶體分配功能,在呼叫該函式時,可能會阻塞。

  vm_map_lock(map);

  if (vm_map_findspace(map, vm_map_min(map), size, &addr) == 0)

  break;

  if (vm_map_max(map) - vm_map_min(map) < size) {

  vm_map_unlock(map);

  return (0);

  }

  map->needs_wakeup = TRUE;

  vm_map_unlock_and_wait(map, FALSE);

  無限迴圈執行這段程式碼,除非break或是return。如果找到有足夠空間分配這段記憶體vm_map_findspace返回為0,則跳出迴圈。如果沒有,則判斷是否該子對映是否有足夠空間給申請者,如果沒有則返回空值。如果該子對映有足夠空間,只是暫時沒有,則透過vm_map_unlock_and_wait睡眠,等待有空間可用。

  vm_map_insert(map, NULL, 0, addr, addr + size, VM_PROT_ALL, VM_PROT_ALL, 0);

  vm_map_unlock(map);

  當分配了足夠的空間後,將對應的vm_map_entry實體插入map中。

  2.3 核心記憶體空間釋放函式

  函式void kmem_free(vm_map_t map, vm_offset_t addr, vm_size_t size)

  該函式用於釋放核心記憶體分配的記憶體空間。

  (void) vm_map_remove(map, trunc_page(addr), round_page(addr + size));

  函式kmem_free透過調整需要釋放的空間的起始地址add和size,作為vm_map_remove的入參釋放這段空間。

  函式void kmem_free_wakeup (vm_map_t map, vm_offset_t addr, vm_size_t size)

  vm_map_lock(map);

  (void) vm_map_delete(map, trunc_page(addr), round_page(addr + size));

  if (map->needs_wakeup) {

  map->needs_wakeup = FALSE;

  vm_map_wakeup(map);

  }

  vm_map_unlock(map);

  該函式和kmem_free函式的功能一樣,只不過在釋放空間後,會檢查needs_wakeup成員,如果有其它執行緒阻塞於該map,則透過vm_map_wakeup函式試圖喚醒。

  2.4 核心子對映分配

  FreeBSD5.0透過kernel_map提供了核心地址對映,基於kernel_map,FreeBSD5.0提供了kmem_map、clean_map和exec_map子對映。其中clean_map由分出buffer_map和pager_map兩個子對映。

  Ø kmem_map:為malloc機制和mbuf提供核心記憶體空間對映。

  Ø exec_map:為exec程式執行等系統呼叫提供核心記憶體空間對映。

  Ø buffer_map:為檔案系統提供核心記憶體對映。

  Ø pager_map:為頁機制提供核心記憶體對映。

  本小節討論核心記憶體地址子對映分配的實現。

  函式vm_map_t kmem_suballoc(vm_map_t parent, vm_offset_t* min,

  vm_offset_t* max, vm_size_t size)

  該函式實現了核心記憶體地址的子對映分配。

  GIANT_REQUIRED;

  size = round_page(size);

  *min = (vm_offset_t) vm_map_min(parent);

  ret = vm_map_find(parent, NULL, (vm_offset_t) 0,

  min, size, TRUE, VM_PROT_ALL, VM_PROT_ALL, 0);

  if (ret != KERN_SUCCESS) {

  printf("kmem_suballoc: bad status return of %d. ", ret);

  panic("kmem_suballoc");

  }

  在父對映parent中,查詢是否有足夠的空間給申請分配的子對映,如果失敗,則出現異常錯誤。

  *max = *min + size;

  result = vm_map_create(vm_map_pmap(parent), *min, *max);

  if (result == NULL)

  panic("kmem_suballoc: cannot create submap");

  透過vm_map_create函式建立新的地址對映vm_map,賦給result,如果不能建立,也是出現異常錯誤。

  if (vm_map_submap(parent, *min, *max, result) != KERN_SUCCESS)

  panic("kmem_suballoc: unable to change range to submap");

  return (result);

  透過vm_map_submap函式,建立parent和result之間的聯絡,如果不能建立也是異常錯誤。最後返回新建立的子對映結構的指標。

  3. 區域分配器機制(UMA子系統)

  FreeBSD5.0透過區域分配器(zone allocator)提供了對動態大小的記憶體分配的有效管理。核心中經常使用的malloc函式也是區域分配器的一個封裝。區域分配器第一次出現在FreeBSD3.0中,但是在FreeBSD5.0中已經完全重寫了。

  3.1 資料結構

  區域分配器涉及到的主要資料結構是:uma_zone、uma_slab、uma_bucket、uma_cache和uma_hash,其中uma_hash是為了提高效率。

  一個uma_zone可以有多個uma_slab,每個uma_slab是透過單向連結串列串聯(根據slab的狀態,可以在不同的連結串列中);每個slab是由多個item組成,每個item是真正供資料儲存所用的空間。而uma_bucket和uma_cache是為每個CPU提供cache和並行處理機制。

  3.2 區域分配器初始化

  區域分配器的初始化分為三個部分,分別在核心執行SI_SUB_VM、SI_SUB_KMEM和SI_SUB_VM_CONF模組中呼叫。

  函式void uma_startup(void *bootmem)

  該函式實現了區域分配器初始化的一部分,其入參bootmem是由系統啟動時,在vm_page_startup函式給出的,指向了系統分配給區域管理所需記憶體的首地址。該段記憶體的尺寸應該是UMA_SLAB_SIZE * UMA_BOOT_PAGES,即30頁。

  mtx_init(&uma_mtx, "UMA lock", NULL, MTX_DEF);

  初始化全域性互斥體uma_mtx,該uma_mtx用於保護對全域性區域管理器連結串列uma_zones的操作。

  args.name = "UMA Zones";

  args.size = sizeof(struct uma_zone) + (sizeof(struct uma_cache) * (maxcpu - 1));

  args.ctor = zone_ctor;

  args.dtor = zone_dtor;

  args.uminit = zero_init;

  args.fini = NULL;

  args.align = 32 - 1;

  args.flags = UMA_ZONE_INTERNAL;

  zone_ctor(zones, sizeof(struct uma_zone), &args);

  手工建立zones,透過zone_ctor函式對zones初始化,並插入全域性連結串列uma_zones中。關於zones的定義參考:

  static struct uma_zone masterzone;

  static uma_zone_t zones = &

  所有的區域分配管理器都是從masterzone派生的產物。

  for (i = 0; i < UMA_BOOT_PAGES; i++) {

  slab = (uma_slab_t)((u_int8_t *)bootmem + (i * UMA_SLAB_SIZE));

  slab->us_data = (u_int8_t *)slab;

  slab->us_flags = UMA_SLAB_BOOT;

  LIST_INSERT_HEAD(&uma_boot_pages, slab, us_link);

  uma_boot_free++;

  }

  根據bootmem提供的記憶體地址,分配30個頁面,串聯在uma_boot_pages連結串列中。

  slabsize = UMA_SLAB_SIZE - sizeof(struct uma_slab); /*4k – 24*/

  slabsize /= UMA_MAX_WASTE; /*(4k-24)/(4k/10) = 9*/

  slabsize++; /* 10*/

  slabsize += sizeof(struct uma_slab); /*10 + 24 = 34*/

  計算slabsize尺寸,在i386體系中,是34。

  slabzone = uma_zcreate("UMA Slabs", slabsize, NULL, NULL, NULL, NULL,

  UMA_ALIGN_PTR, UMA_ZONE_INTERNAL);

  hashzone = uma_zcreate("UMA Hash", sizeof(struct slabhead *) * UMA_HASH_SIZE_INIT, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_INTERNAL);

  bucketzone = uma_zcreate("UMA Buckets", sizeof(struct uma_bucket),

  NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_INTERNAL);

  呼叫uma_zcreate函式建立三個新的區域,分別賦給slabzone、hashzone和bucketzone。系統中所有的uma_slab結構都是從slabzone區域分配;hashzone負責雜湊表的初始化分配;bucketzone負責所有uma_bucket分配。

  在區域分配器執行完uma_startup之後,如圖所示其結構:

  masterzone

  zones->uz_part_slab

  item => slabzone

  item => hashzone

  item => bucketzone

  函式void uma_startup2(void)

  該函式十分簡單,就是幾個全域性變數的設定。透過bucket_enable函式設定bucketdisable的值,該值決定了bucketzone區域是否可用。

  booted = 1;

  bucket_enable();

  函式static void uma_startup3(void)

  callout_init(&uma_callout, 0);

  callout_reset(&uma_callout, UMA_WORKING_TIME * hz, uma_timeout, NULL);

  該函式用於設定定時執行器,每隔UMA_WORKING_TIME * hz的時間,執行一個uma_timeout函式。主要是用於統計系統的區域使用資訊,以及相應的每個CPU的cache使用情況。

  3.3 區域介面函式

  關於區域分配器的介面函式描述可以參考zone(9)。本小節基於zone(9)的論述,詳細討論區域分配器的介面函式的實現。

  函式uma_zone_t uma_zcreate(char *name, size_t size, uma_ctor ctor, uma_dtor dtor,

  uma_init uminit, uma_fini fini, int align, u_int16_t flags)

  該函式用於建立新的區域管理器(uma_zone結構型別)。入參:name是用於除錯所用的文字字串;size表示需要建立的區域分配管理器的每一個item的尺寸;ctor和dtor都是函式指標,分別用於uma_zalloc和uma_zfree系列函式回撥使用;uminit和fini同樣是函式指標,用於區域分配物件的最佳化;align是位掩碼;flags說明了需要建立的區域的特性。

  flags定義如下:

  #define UMA_ZFLAG_OFFPAGE 0x0001 /* Struct slab/freelist off page */

  #define UMA_ZFLAG_PRIVALLOC 0x0002 /* Zone has supplied it's own alloc */

  #define UMA_ZFLAG_INTERNAL 0x0004 /* Internal zone, no offpage no PCPU */

  #define UMA_ZFLAG_MALLOC 0x0008 /* Zone created by malloc */

  #define UMA_ZFLAG_NOFREE 0x0010 /* Don't free data from this zone */

  #define UMA_ZFLAG_FULL 0x0020 /* This zone reached uz_maxpages */

  #define UMA_ZFLAG_BUCKETCACHE 0x0040 /* Only allocate buckets from cache */

  #define UMA_ZFLAG_HASH 0x0080 /* Look up slab via hash */

  函式uma_zcreate的實現主要是透過呼叫uma_zalloc_internal函式,我們先看看函式uma_zcreate的程式碼:

  args.name = name;

  args.size = size;

  args.ctor = ctor;

  args.dtor = dtor;

  args.uminit = uminit;

  args.fini = fini;

  args.align = align;

  args.flags = flags;

  return (uma_zalloc_internal(zones, &args, M_WAITOK));

  正如我們前面討論啟動時,提及的所有的區域建立都是從zones中派生的。現在分析uma_zalloc_internal函式,可以分為4個主體部分討論:

  if (bucketdisable && zone == bucketzone)

  return (NULL);

  在系統啟動時,指定的UMA_BOOT_PAGES空間耗盡(bucketdisable = 1),並且指定的父區域管理器是bucketzone,則返回NULL值。

  ZONE_LOCK(zone);

  slab = uma_zone_slab(zone, flags);

  if (slab == NULL) {

  ZONE_UNLOCK(zone);

  return (NULL);

  }

  透過呼叫uma_zone_slab函式,從zone中分配一個uma_slab型別結構,其指標賦給slab。

  item = uma_slab_alloc(zone, slab);

  ZONE_UNLOCK(zone);

  透過uma_zone_slab函式,從zone中的一個slab中分配一個item,該item用於儲存新的uma_zone型別的管理結構。

  if (zone->uz_ctor != NULL)

  zone->uz_ctor(item, zone->uz_size, udata);

  if (flags & M_ZERO)

  bzero(item, zone->uz_size);

  return (item);

  如果制定了uz_ctor,則呼叫回撥函式對item裡儲存的新的uma_zone進行構造初始化。最後返回新的uma_zone地址。

  函式void * uma_zalloc(uma_zone_t zone, int flags)

  該函式實現了從zone中分配一個item的功能,該函式只是uma_zalloc_arg函式一個簡單封裝。

  return uma_zalloc_arg(zone, NULL, flags);

  因此,我們討論uma_zalloc_arg函式實現的主體部分。

  zalloc_restart:

  cpu = PCPU_GET(cpuid);

  CPU_LOCK(zone, cpu);

  cache = &zone->uz_cpu[cpu];

  根據當前呼叫者的CPU號,獲得相應的uma_cache結構。

  zalloc_start:

  bucket = cache->uc_allocbucket;

  成員uc_allocbucket記錄了該item應該分配的來源。

  if (bucket) {

  如果bucket有值,則說明可以檢查是否能從該bucket中分配item。

  if (bucket->ub_ptr > -1) {

  如果ub_ptr > -1,說明該bucket目前存在可以分配的item。

  item = bucket->ub_bucket[bucket->ub_ptr];

  bucket->ub_ptr--;

  cache->uc_allocs++;

  CPU_UNLOCK(zone, cpu);

  if (zone->uz_ctor)

  zone->uz_ctor(item, zone->uz_size, udata);

  if (flags & M_ZERO)

  bzero(item, zone->uz_size);

  return (item);

  分配該item,並做相應的初始化,返回該item。

  } else if (cache->uc_freebucket) {

  如果uc_allocbucket沒有可用空間,檢查是否存在free bucket,並且可用。

  if (cache->uc_freebucket->ub_ptr > -1) {

  uma_bucket_t swap;

  swap = cache->uc_freebucket;

  cache->uc_freebucket = cache->uc_allocbucket;

  cache->uc_allocbucket = swap;

  goto zalloc_start;

  將該可用的uc_freebucket賦給uc_allocbucket,重新對bucket賦值,再次試圖分配item。

  }

  }

  }

  以上是對如果存在bucket的情況處理。分析一下,什麼情況會繼續執行下面的程式碼。可能的情況:a. 不存在uc_allocbucket;b. uc_allocbucket已經耗盡。

  ZONE_LOCK(zone);

  zone->uz_allocs += cache->uc_allocs;

  cache->uc_allocs = 0;

  更新uz_allocs值,該值說明了該zone區域被分配的次數。比如當uc_allocbucket已經耗盡,需要將該cache的uc_allocs加入uz_allocs中,因為後面會更新uc_allocbucket。

  if (cache->uc_allocbucket) {

  LIST_INSERT_HEAD(&zone->uz_free_bucket, cache->uc_allocbucket, ub_link);

  cache->uc_allocbucket = NULL;

  }

  現在uc_allocbucket是一個可以釋放item的bucket,將其加入到uz_free_bucket中。

  if ((bucket = LIST_FIRST(&zone->uz_full_bucket)) != NULL) {

  LIST_REMOVE(bucket, ub_link);

  cache->uc_allocbucket = bucket;

  ZONE_UNLOCK(zone);

  goto zalloc_start;

  }

  更新cache結構的uc_allocbucket指標,試圖重新分配item。

  CPU_UNLOCK(zone, cpu);

  if (zone->uz_count < UMA_BUCKET_SIZE - 1)

  zone->uz_count++;

  if (uma_zalloc_bucket(zone, flags)) {

  ZONE_UNLOCK(zone);

  goto zalloc_restart;

  }

  ZONE_UNLOCK(zone);

  如果zone確實沒有可用bucket使用,因此,需要透過uma_zalloc_bucket分配新的bucket,然後完全重新開始試圖分配item。

  return (uma_zalloc_internal(zone, udata, flags));

  什麼時候會執行到這一步?比如,對於系統的某個模組的連結串列,需要malloc分配每個節點空間,而且該連結串列很長,以至於在uma_zcreate建立的slab裡的item都使用完了。因此,需要再分配slab來滿足需要。

  函式void uma_zfree(uma_zone_t zone, void *item)

  該函式實現了將item釋放回zone中,同uma_zalloc類似,該函式是uma_zfree_arg的一個封裝。

  uma_zfree_arg(zone, item, NULL);

  現在,我們分析uma_zfree_arg的實現。

  if (zone->uz_flags & UMA_ZFLAG_FULL)

  goto zfree_internal;

  標識UMA_ZFLAG_FULL說明該區域zone已經達到峰值,直接放在後面處理。後面再討論。

  if (zone->uz_dtor)

  zone->uz_dtor(item, zone->uz_size, udata);

  如果該zone的解析函式指標不為空,則首先呼叫其解析函式處理。

  zfree_restart:

  cpu = PCPU_GET(cpuid);

  CPU_LOCK(zone, cpu);

  cache = &zone->uz_cpu[cpu];

  同uma_zalloc_arg函式類似,根據當前呼叫者的CPU號,獲得相應的uma_cache結構。

  zfree_start:

  bucket = cache->uc_freebucket;

  if (bucket) {

  if (bucket->ub_ptr < zone->uz_count) {

  bucket->ub_ptr++;

  bucket->ub_bucket[bucket->ub_ptr] = item;

  CPU_UNLOCK(zone, cpu);

  return;

  } else if (cache->uc_allocbucket) {

  if (cache->uc_allocbucket->ub_ptr < cache->uc_freebucket->ub_ptr) {

  uma_bucket_t swap;

  swap = cache->uc_freebucket;

  cache->uc_freebucket = cache->uc_allocbucket;

  cache->uc_allocbucket = swap;

  goto zfree_start;

  }

  }

  }

  比較uma_zalloc_arg函式相應的程式碼,只不過bucket代表需要釋放的uc_freebucket。如果該uc_freebucket還有剩餘item空間,就不真正的釋放該item,只是將計數器加1。如果uc_freebucket滿了,並且uc_allocbucket有值,而且uc_allocbucket還有item空間,則交換這兩個值,重新對bucket賦值,並再次試圖對bucket計數器加1。

  如果該zone原來的uc_freebucket為空,或者uc_freebucket和uc_allocbucket都滿了,則需要繼續執行。

  ZONE_LOCK(zone);

  bucket = cache->uc_freebucket;

  cache->uc_freebucket = NULL;

  if (bucket != NULL) {

  LIST_INSERT_HEAD(&zone->uz_full_bucket, bucket, ub_link);

  }

  如果是uc_freebucket的item已經滿了,則將uc_freebucket插入uz_full_bucket連結串列中。

  if ((bucket = LIST_FIRST(&zone->uz_free_bucket)) != NULL) {

  LIST_REMOVE(bucket, ub_link);

  ZONE_UNLOCK(zone);

  cache->uc_freebucket = bucket;

  goto zfree_start;

  }

  從該zone需要釋放的bucket連結串列uz_free_bucket(如果其不為空)中,移出第一個節點,將其賦給uc_freebucket,重新執行zfree_start。

  CPU_UNLOCK(zone, cpu);

  ZONE_UNLOCK(zone);

  bflags = M_NOWAIT;

  if (zone->uz_flags & UMA_ZFLAG_BUCKETCACHE)

  bflags |= M_NOVM;

  bucket = uma_zalloc_internal(bucketzone, NULL, bflags);

  if (bucket) {

  bucket->ub_ptr = -1;

  ZONE_LOCK(zone);

  LIST_INSERT_HEAD(&zone->uz_free_bucket, bucket, ub_link);

  ZONE_UNLOCK(zone);

  goto zfree_restart;

  }

  當執行到這段程式碼,說明以前分配給該zone的bucket都已經使用完了,因此,透過uma_zalloc_internal函式試圖從bucketzone中再為zone分配新的bucket。如果成功,則插入uz_free_bucket連結串列中,再次試圖調整該bucket的ub_ptr值。

  zfree_internal:

  uma_zfree_internal(zone, item, udata, 0);

  return;

  當執行到這段程式碼,說明該zone的bucket已經完全滿了,需要真正釋放該item返回給zone。函式uma_zfree_internal的實現在uma_zdestroy函式中討論。

  函式void uma_zdestroy(uma_zone_t zone)

  該函式用於銷燬一個空的區域。

  uma_zfree_internal(zones, zone, NULL, 0);

  函式uma_zdestroy是uma_zfree_internal的一個封裝,因此,我們討論uma_zfree_internal的實現。static void uma_zfree_internal(uma_zone_t zone, void *item, void *udata, int skip)

  if (!skip && zone->uz_dtor)

  zone->uz_dtor(item, zone->uz_size, udata);

  如果沒有指定跳過該zone的解析函式,並且該zone的解析函式指標不為空,則執行該解析函式。

  ZONE_LOCK(zone);

  if (!(zone->uz_flags & UMA_ZFLAG_MALLOC)) {

  mem = (u_int8_t *)((unsigned long)item & (~UMA_SLAB_MASK));

  if (zone->uz_flags & UMA_ZFLAG_HASH)

  slab = hash_sfind(&zone->uz_hash, mem);

  else {

  mem += zone->uz_pgoff;

  slab = (uma_slab_t)mem;

  }

  } else {

  slab = (uma_slab_t)udata;

  }

  根據標識UMA_ZFLAG_MALLOC,如果uz_flags含有該標識,說明該zone是用於malloc機制,則透過udata入參獲得uma_slab_t(slab)的值;如果不含有該標識,則透過計算獲得該slab的值。

  if (slab->us_freecount+1 == zone->uz_ipers) {

  LIST_REMOVE(slab, us_link);

  LIST_INSERT_HEAD(&zone->uz_free_slab, slab, us_link);

  } else if (slab->us_freecount == 0) {

  LIST_REMOVE(slab, us_link);

  LIST_INSERT_HEAD(&zone->uz_part_slab, slab, us_link);

  }

  根據slab成員us_freecount不同的值,將slab移入不同佇列中。

  freei = ((unsigned long)item - (unsigned long)slab->us_data) / zone->uz_rsize;

  slab->us_freelist[freei] = slab->us_firstfree;

  slab->us_firstfree = freei;

  slab->us_freecount++;

  調整該slab的item指標,以及相應的統計資訊。

  zone->uz_free++;

  調整zone的可用空間(item)計數器。

  if (zone->uz_flags & UMA_ZFLAG_FULL) {

  if (zone->uz_pages < zone->uz_maxpages)

  zone->uz_flags &= ~UMA_ZFLAG_FULL;

  wakeup_one(zone);

  }

  ZONE_UNLOCK(zone);

  如果uz_flags含有UMA_ZFLAG_FULL,說明該zone在釋放該item支前,已經達到飽和狀態,因此,釋放了該item,說明該zone又有了可用的item。所以,去掉標識UMA_ZFLAG_FULL,並透過wakeup_one函式,試圖喚醒等待於該zone的第一個執行緒。

  3.4 關於區域分配器的一點後話

  FreeBSD5.0提供的區域分配器,為核心動態分配記憶體提供了全面的跟蹤機制,同時透過指標的移動極大的提高了記憶體分配和釋放的速度。區域分配器含有很多連結串列,分配和釋放空間,主要是透過節點在連結串列間的轉移實現。

  4. 核心動態記憶體管理-malloc機制

  核心和使用者呼叫malloc函式的形式不一樣,在這裡,我們討論核心部分的實現。首先,看看malloc的定義:“void * malloc(unsigned long size, struct malloc_type *type, int flags);”,核心在使用malloc前,需要透過宏MALLOC_DEFINE定義malloc_type的type實體,將該宏展開:

  MALLOC_DEFINE(type, shortdesc, longdesc) à

  struct malloc_type type[1] = {

  { NULL, 0, 0, 0, 0, 0, M_MAGIC, shortdesc, {} }

  };

  SYSINIT(type##_init, SI_SUB_KMEM, SI_ORDER_SECOND, malloc_init, type);

  SYSUNINIT(type##_uninit, SI_SUB_KMEM, SI_ORDER_ANY, malloc_uninit, type)

  因此,核心使用malloc函式的變數type都會在開機的時候透過malloc_init初始化,在reboot的時候透過malloc_uninit釋放。

  檢視malloc_type結構:

  struct malloc_type {

  struct malloc_type *ks_next; /* next in list */

  u_long ks_memuse; /* total memory held in bytes */

  u_long ks_size; /* sizes of this thing that are allocated */

  u_long ks_inuse; /* # of packets of this type currently in use */

  uint64_t ks_calls; /* total packets of this type ever allocated */

  u_long ks_maxused; /* maximum number ever used */

  u_long ks_magic; /* if it's not magic, don't touch it */

  const char *ks_shortdesc; /* short description */

  struct mtx ks_mtx; /* lock for stats */

  };

  該結構用於對同一種申請動態分配的型別使用情況的控制。成員ks_next用於將核心所有需要動態分配空間的型別透過kmemstatistics連結。別的成員參考英文註釋。

  4.1 malloc子系統初始化

  透過mi_startup()函式對SI_SUB_KMEM子系統初始化。

  A:SYSINIT(kmem, SI_SUB_KMEM, SI_ORDER_FIRST, kmeminit, NULL)

  分析kmeminit函式的主體部分:

  mtx_init(&malloc_mtx, "malloc", NULL, MTX_DEF);

  初始化全域性互斥體malloc_mtx,該互斥體用於保護全域性malloc_type連結串列kmemstatistics,該連結串列用於維護系統透過MALLOC_DEFINE宏定義的type。

  vm_kmem_size = VM_KMEM_SIZE;

  mem_size = cnt.v_page_count * PAGE_SIZE;

  if ((mem_size / VM_KMEM_SIZE_SCALE) > vm_kmem_size)

  vm_kmem_size = mem_size / VM_KMEM_SIZE_SCALE;

  if (vm_kmem_size >= VM_KMEM_SIZE_MAX)

  vm_kmem_size = VM_KMEM_SIZE_MAX;

  TUNABLE_INT_FETCH("kern.vm.kmem.size", &vm_kmem_size);

  if ((vm_kmem_size / 2) > (cnt.v_page_count * PAGE_SIZE))

  vm_kmem_size = 2 * cnt.v_page_count * PAGE_SIZE;

  設定、調整核心記憶體尺寸vm_kmem_size,其中TUNABLE_INT_FETCH宏用於從/boot/defaults/loader.conf或/boot/loader.conf檔案中獲得kern.vm.kmem.size值,如果設定了該值,則賦給vm_kmem_size。最後將vm_kmem_size的值限制在實體記憶體的兩倍以內。

  npg = (nmbufs * MSIZE + nmbclusters * MCLBYTES + nmbcnt *

  sizeof(u_int) + vm_kmem_size) / PAGE_SIZE;

  kmem_map = kmem_suballoc(kernel_map, (vm_offset_t *)&kmembase,

  (vm_offset_t *)&kmemlimit, (vm_size_t)(npg * PAGE_SIZE));

  kmem_map->system_map = 1;

  計算用於網路通訊所需緩衝的尺寸,並透過kmem_suballoc函式分配vm_map型別的結構用於管理該段記憶體(kmem_map)。

  uma_startup2();

  for (i = 0, indx = 0; kmemzones[indx].kz_size != 0; indx++) {

  int size = kmemzones[indx].kz_size;

  char *name = kmemzones[indx].kz_name;

  kmemzones[indx].kz_zone = uma_zcreate(name, size, NULL, NULL, NULL, NULL,

  UMA_ALIGN_PTR, UMA_ZONE_MALLOC);

  for (;i <= size; i+= KMEM_ZBASE)

  kmemsize[i >> KMEM_ZSHIFT] = indx;

  }

  這段程式碼是區域分配器初始化的一個部分。函式uma_startup2完成區域分配器所需的一些全域性變數的設定。全域性變數kmemzones的定義如下:

  struct {

  int kz_size;

  char *kz_name;

  uma_zone_t kz_zone;

  } kmemzones[] = {

  {16, "16", NULL},

  {32, "32", NULL},

  {64, "64", NULL},

  {128, "128", NULL},

  {256, "256", NULL},

  {512, "512", NULL},

  {1024, "1024", NULL},

  {2048, "2048", NULL},

  {4096, "4096", NULL},

  {8192, "8192", NULL},

  {16384, "16384", NULL},

  {32768, "32768", NULL},

  {65536, "65536", NULL},

  {0, NULL},

  };

  當核心需要分配一個較小記憶體空間時(<65536),函式malloc透過該變數實現記憶體區域分配,區域分配器是透過uma_zone結構管理的,因此,在這段程式碼中透過uma_zcreate建立相應的區域,並將該區域指標賦給kz_zone成員,UMA_ZONE_MALLOC標識指明瞭該區域用於malloc呼叫。涉及到uma的函式系列,我們在後面的區域分配器中討論。

  B:SYSINIT(type##_init, SI_SUB_KMEM, SI_ORDER_SECOND, malloc_init, type)

  和SYSUNINIT(type##_uninit, SI_SUB_KMEM, SI_ORDER_ANY, malloc_uninit, type)

  該宏是MALLOC_DEFINE宏的一條語句。所有核心透過malloc動態分配的空間都需透過MALLOC_DEFINE定義,即在系統初始化時,需要malloc_init對該變數初始化,下面分析該函式主體:

  mtx_lock(&malloc_mtx);

  if (type->ks_magic != M_MAGIC)

  panic("malloc type lacks magic");

  if (cnt.v_page_count == 0)

  panic("malloc_init not allowed before vm init");

  if (type->ks_next != NULL)

  return;

  type->ks_next = kmemstatistics;

  kmemstatistics = type;

  mtx_init(&type->ks_mtx, type->ks_shortdesc, "Malloc Stats", MTX_DEF);

  mtx_unlock(&malloc_mtx);

  這段程式碼非常簡單,就是在malloc_mtx互斥體的保護下,將一個新的malloc_type 型別的type變數插入kmemstatistics連結串列首部,並初始化malloc_type結構的ks_mtx互斥體。

  同樣,malloc_uninit用於在系統關閉時,將type變數從kmemstatistics抽出,並銷燬其ks_mtx互斥體。

  4.2 malloc介面函式的實現

  函式void * malloc(unsigned long size, struct malloc_type *type, int flags)

  該函式實現了核心動態分配記憶體的功能,FreeBSD根據其分配記憶體尺寸與閥值KMEM_ZMAX(64K)的關係決定如何實現該記憶體區域分配,其實現主體如下。

  if (size <= KMEM_ZMAX) {

  if (size & KMEM_ZMASK)

  size = (size & ~KMEM_ZMASK) + KMEM_ZBASE;

  indx = kmemsize[size >> KMEM_ZSHIFT];

  zone = kmemzones[indx].kz_zone;

  va = uma_zalloc(zone, flags);

  mtx_lock(&ksp->ks_mtx);

  if (va == NULL)

  goto out;

  ksp->ks_size |= 1 << indx;

  size = zone->uz_size;

  } else {

  size = roundup(size, PAGE_SIZE);

  zone = NULL;

  va = uma_large_malloc(size, flags);

  mtx_lock(&ksp->ks_mtx);

  if (va == NULL)

  goto out;

  }

  如果申請的記憶體空間尺寸小於等於閥值KMEM_ZMAX,採用2的冪的關係來分配空間,即如果申請500位元組的空間,滿足該尺寸的最小kmemzones是kz_size為512的項;如果申請513位元組的空間,則滿足該尺寸的最小kmemzones是kz_size為1024的項,因此,實際透過uma_zalloc函式分配的記憶體尺寸應該是512和1024位元組。

  如果申請的記憶體空間尺寸大於閥值KMEM_ZMAX,採用滿足該尺寸的最小整數倍PAGE_SIZE(4K)的空間尺寸。即如果申請65537位元組的記憶體,則需要分配的尺寸是17個PAGE_SIZE(68K),更新尺寸後,透過uma_large_malloc函式實現具體區域空間分配,其本質是透過kmem_malloc函式實現。

  ksp->ks_memuse += size;

  ksp->ks_inuse++;

  out:

  ksp->ks_calls++;

  if (ksp->ks_memuse > ksp->ks_maxused)

  ksp->ks_maxused = ksp->ks_memuse;

  mtx_unlock(&ksp->ks_mtx);

  return ((void *) va);

  這段程式碼是malloc函式實現的剩餘部分,其中略過了與流程關係不大的程式碼。主要用於更新申請分配記憶體的型別控制結構的成員資訊。

  函式void free(void *addr, struct malloc_type *type)

  該函式釋放動態分配的記憶體空間。

  slab = vtoslab((vm_offset_t)addr & (~UMA_SLAB_MASK));

  if (!(slab->us_flags & UMA_SLAB_MALLOC)) {

  size = slab->us_zone->uz_size;

  uma_zfree_arg(slab->us_zone, addr, slab);

  } else {

  size = slab->us_size;

  uma_large_free(slab);

  }

  在malloc函式中,透過uma_large_malloc函式分配的空間起slab值的us_flags標識含有UMA_SLAB_MALLOC。正如malloc函式根據申請的空間尺寸有兩種獲得記憶體的方法,釋放記憶體空間時,也是這兩種獲得空間方法對應的釋放空間方法。當呼叫uma_large_free函式時,其實質是呼叫kmem_free函式實現。

  mtx_lock(&ksp->ks_mtx);

  ksp->ks_memuse -= size;

  ksp->ks_inuse--;

  mtx_unlock(&ksp->ks_mtx);

  由於釋放了該記憶體空間,因此,管理該型別的malloc_type的成員也需要更新。

  函式void * realloc(void *addr, unsigned long size, struct malloc_type *type, int flags)

  該函式用於改變記憶體空間尺寸,將先前分配的記憶體空間addr的尺寸改變為size。

  if (addr == NULL)

  return (malloc(size, type, flags));

  如果沒有指明記憶體地址addr,則就是完全分配一個size尺寸的記憶體空間,呼叫malloc函式即可。

  slab = vtoslab((vm_offset_t)addr & ~(UMA_SLAB_MASK));

  if (slab->us_zone)

  alloc = slab->us_zone->uz_size;

  else

  alloc = slab->us_size;

  如果原來分配的空間存在於uma_zone中(us_zone不為空),則透過uz_size獲得原來空間尺寸,否則透過us_size獲得原來尺寸。

  if (size <= alloc

  && (size > (alloc >> REALLOC_FRACTION) || alloc == MINALLOCSIZE))

  return (addr);

  如果可能,儘量用原來的記憶體地址。

  if ((newaddr = malloc(size, type, flags)) == NULL)

  return (NULL);

  bcopy(addr, newaddr, min(size, alloc));

  free(addr, type);

  return (newaddr);

  重新分配一個size尺寸的記憶體空間,將原有空間的資料複製到新的記憶體空間,並釋放原有的記憶體空間。

  函式void * reallocf(void *addr, unsigned long size, struct malloc_type *type, int flags)

  該函式是realloc函式的封裝,如果呼叫realloc成功,則返回;不成功則釋放原有地址addr的記憶體空間。

  4.3 malloc其它說明

  核心透過sysctl_kern_malloc函式,提供了查詢當前核心透過malloc動態記憶體分配的方法,我們可以透過命令“sysctl kern.malloc”查詢。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-944550/,如需轉載,請註明出處,否則將追究法律責任。

相關文章