圖解slub

来源:https://www.cnblogs.com/linhaostudy/archive/2019/02/24/10426599.html
-Advertisement-
Play Games

1.前言 在Linux中,伙伴系統(buddy system)是以頁為單位管理和分配記憶體。但是現實的需求卻以位元組為單位,假如我們需要申請20Bytes,總不能分配一頁吧!那豈不是嚴重浪費記憶體。那麼該如何分配呢?slab分配器就應運而生了,專為小記憶體分配而生。slab分配器分配記憶體以Byte為單位。但 ...


1.前言

在Linux中,伙伴系統(buddy system)是以頁為單位管理和分配記憶體。但是現實的需求卻以位元組為單位,假如我們需要申請20Bytes,總不能分配一頁吧!那豈不是嚴重浪費記憶體。那麼該如何分配呢?slab分配器就應運而生了,專為小記憶體分配而生。slab分配器分配記憶體以Byte為單位。但是slab分配器並沒有脫離伙伴系統,而是基於伙伴系統分配的大記憶體進一步細分成小記憶體分配。

前段時間學習了下slab分配器工作原理。因為自己本身是做手機的,發現現在好像都在使用slub分配器,想想還是再研究一下slub的工作原理。之前看了代碼,感覺挺多數據結構和成員的。成員的意思是什麼?數據結構之間的關係是什麼?不知道你是否感覺雲里霧裡。既然代碼閱讀起來晦澀難懂,如果有精美的配圖,不知是否有助於閣下理解slub的來龍去脈呢?我想表達的意思就是文章圖多,圖多,圖多。我們只說原理,儘量不看代碼。因為所有代碼中包含的內容我都會用圖來說明。你感興趣絕對有助於你看代碼。

說明:slub是slab中的一種,slab也是slab中的一種。有時候用slab來統稱slab, slub和slob。slab, slub和slob僅僅是分配記憶體策略不同。本篇文章中說的是slub分配器工作的原理。但是針對分配器管理的記憶體,下文統稱為slab緩存池。所以文章中slub和slab會混用,表示同一個意思。

註:文章代碼分析基於linux-4.15.0-rc3。 圖片有點走形,請單獨點開圖片查看。

2. slub數據結構

slub的數據結構相對於slab來說要簡單很多。並且對外介面和slab相容。所以說,從slab的系統更換到slub,可以說是易如反掌。

2.1. kmem_cache

現在假如從伙伴系統分配一頁記憶體供slub分配器管理。對於slub分配器來說,就是將這段連續記憶體平均分成若幹大小相等的object(對象)進行管理。可是我們總得知道每一個object的size吧!管理的記憶體頁數也是需要知道的吧!不然怎麼知道如何分配呢!因此需要一個數據結構管理。那就是struct kmem_cache。kmem_cache數據結構描述如下:

struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab;
/* Used for retriving partial slabs etc */
slab_flags_t flags;
unsignedlong min_partial;
int size;/* The size of an object including meta data */
int object_size;/* The size of an object without meta data */
int offset;/* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
int cpu_partial;/* Number of per cpu partial objects to keep around */
#endif
struct kmem_cache_order_objects oo;
/* Allocation and freeing of slabs */
struct kmem_cache_order_objects max;
struct kmem_cache_order_objects min;
gfp_t allocflags;/* gfp flags to use on each alloc */
int refcount;/* Refcount for slab cache destroy */
void(*ctor)(void*);
int inuse;/* Offset to metadata */
int align;/* Alignment */
int reserved;/* Reserved bytes at the end of slabs */
constchar*name;/* Name (only for display!) */
structlist_head list;/* List of slab caches */
struct kmem_cache_node *node[MAX_NUMNODES];
};
  1. cpu_slab:一個per cpu變數,對於每個cpu來說,相當於一個本地記憶體緩存池。當分配記憶體的時候優先從本地cpu分配記憶體以保證cache的命中率。
  2. flags:object分配掩碼,例如經常使用的SLAB_HWCACHE_ALIGN標誌位,代表創建的kmem_cache管理的object按照硬體cache 對齊,一切都是為了速度。
  3. min_partial:限制struct kmem_cache_node中的partial鏈表slab的數量。雖說是mini_partial,但是代碼的本意告訴我這個變數是kmem_cache_node中partial鏈表最大slab數量,如果大於這個mini_partial的值,那麼多餘的slab就會被釋放。
  4. size:分配的object size
  5. object_size:實際的object size,就是創建kmem_cache時候傳遞進來的參數。和size的關係就是,size是各種地址對齊之後的大小。因此,size要大於等於object_size。
  6. offset:slub分配在管理object的時候採用的方法是:既然每個object在沒有分配之前不在乎每個object中存儲的內容,那麼完全可以在每個object中存儲下一個object記憶體首地址,就形成了一個單鏈表。很巧妙的設計。那麼這個地址數據存儲在object什麼位置呢?offset就是存儲下個object地址數據相對於這個object首地址的偏移。
  7. cpu_partial:per cpu partial中所有slab的free object的數量的最大值,超過這個值就會將所有的slab轉移到kmem_cache_node的partial鏈表。
  8. oo:低16位代表一個slab中所有object的數量(oo & ((1 << 16) - 1)),高16位代表一個slab管理的page數量((2^(oo 16)) pages)。
  9. max:看了代碼好像就是等於oo。
  10. min:當按照oo大小分配記憶體的時候出現記憶體不足就會考慮min大小方式分配。min只需要可以容納一個object即可。
  11. allocflags:從伙伴系統分配記憶體掩碼。
  12. inuse:object_size按照word對齊之後的大小。
  13. align:位元組對齊大小。
  14. name:sysfs文件系統顯示使用。
  15. list:系統有一個slab_caches鏈表,所有的slab都會掛入此鏈表。
  16. node:slab節點。在NUMA系統中,每個node都有一個struct kmem_cache_node數據結構。

2.2. kmem_cache_cpu

struct kmem_cache_cpu是對本地記憶體緩存池的描述,每一個cpu對應一個結構體。其數據結構如下:

struct kmem_cache_cpu {
void**freelist;/* Pointer to next available object */
unsignedlong tid;/* Globally unique transaction id */
struct page *page;/* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial;/* Partially allocated frozen slabs */
#endif
};
  1. freelist:指向下一個可用的object。
  2. tid:一個神奇的數字,主要用來同步作用的。
  3. page:slab記憶體的page指針。
  4. partial:本地slab partial鏈表。主要是一些部分使用object的slab

2.4. slub介面

瞭解了基本的數據結構,再來看看slub提供的API。如果你瞭解slub,我想這幾個介面你是再熟悉不過了。

struct kmem_cache *kmem_cache_create(constchar*name,
size_t size,
size_t align,
unsignedlong flags,
void(*ctor)(void*));
void kmem_cache_destroy(struct kmem_cache *);
void*kmem_cache_alloc(struct kmem_cache *cachep,int flags);
void kmem_cache_free(struct kmem_cache *cachep,void*objp);
  1. kmem_cache_create是創建kmem_cache數據結構,參數描述如下:
  • name:kmem_cache的名稱
  • size :slab管理對象的大小
  • align:slab分配器分配記憶體的對齊位元組數(以align位元組對齊)
  • flags:分配記憶體掩碼
  • ctor :分配對象的構造回調函數
  1. kmem_cache_destroy作用和kmem_cache_create相反,就是銷毀創建的kmem_cache。
  2. kmem_cache_alloc是從cachep參數指定的kmem_cache管理的記憶體緩存池中分配一個對象,其中flags是分配掩碼,GFP_KERNEL是不是很熟悉的掩碼?
  3. kmem_cache_free是kmem_cache_alloc的反操作

slab分配器提供的介面該如何使用呢?其實很簡單,總結分成以下幾個步驟:

  1. kmem_cache_create創建一個kmem_cache數據結構。
  2. 使用kmem_cache_alloc介面分配記憶體,kmem_cache_free介面釋放記憶體。
  3. release第一步創建的kmem_cache數據結構。

再來一段demo示例代碼就更好了。

/*
 * This is a demo for how to use kmem_cache_create
 */
void slab_demo(void)
{
struct kmem_cache *kmem_cache_16 = kmem_cache_create("kmem_cache_16",16,
8, ARCH_KMALLOC_FLAGS,
            NULL);

/* now you can alloc memory, the buf points to 16 bytes of memory*/
char*buf = kmeme_cache_alloc(kmem_cache_16, GFP_KERNEL);

/*
     * do something what you what, don't forget to release the memory after use
*/
    kmem_cache_free(kmem_cache_16, buf);

    kmem_cache_destroy(kmem_cache_16);
}
  1. 首先使用kmem_cache_create創建名稱為kmem_cache_16的kmem_cache,該kmem_cache主要是描述如何管理一堆對象,其實就是slab的佈局。每個對象都是16位元組,並且分配的對象地址按照8位元組對齊,也就是說從kmem_cache_16中分配的對象大小全是16位元組。不管你要申請多少,反正就是16Bytes。當然,kmem_cache_create僅僅是創建了一個描述slab緩存池佈局的數據結構,並沒有從伙伴系統申請記憶體,具體的申請記憶體操作是在kmeme_cache_alloc中完成的。
  2. kmeme_cache_alloc從kmem_cache_16分配一個對象。
  3. 記憶體使用結束記得kmem_cache_free釋放。
  4. 如果不需要這個kmem_cache的話,就可以調用kmem_cache_destroy進行銷毀吧。在釋放kmem_cache之前要保證從該kmem_cache中分配的對象全部釋放了,否則無法釋放kmem_cache。

3. slub數據結構之間關係

什麼是slab緩存池呢?我的解釋是使用struct kmem_cache結構描述的一段記憶體就稱作一個slab緩存池。一個slab緩存池就像是一箱牛奶,一箱牛奶中有很多瓶牛奶,每瓶牛奶就是一個object。分配記憶體的時候,就相當於從牛奶箱中拿一瓶。總有拿完的一天。當箱子空的時候,你就需要去超市再買一箱回來。超市就相當於partial鏈表,超市存儲著很多箱牛奶。如果超市也賣完了,自然就要從廠家進貨,然後出售給你。廠家就相當於伙伴系統。

說了這麼多終於要拋出辛辛苦苦畫的美圖了。
image

好了,後面說的大部分內容請看這張圖。足以表明數據結構之間的關係了。看懂了這張圖,就可以理清數據結構之間的關係了。

3.1. slub管理object方法

在圖片的左上角就是一個slub緩存池中object的分佈以及數據結構和kmem_cache之間的關係。首先一個slab緩存池包含的頁數是由oo決定的。oo拆分為兩部分,低16位代表一個slab緩存池中object的數量,高16位代表包含的頁數。使用kmem_cache_create()介面創建kmem_cache的時候需要指出obj的size和對齊align。也就是傳入的參數。kmem_cache_create()主要是就是填充kmem_cache結構體成員。既然從伙伴系統得到(2^(oo >> 16)) pages大小記憶體,按照size大小進行平分。一般來說都不會整除,因此剩下的就是圖中灰色所示。由於每一個object的大小至少8位元組,當然可以用來存儲下一個object的首地址。就像圖中所示的,形成單鏈表。圖中所示下個obj地址存放的位置位於每個obj首地址處,在內核中稱作指針內置式。同時,下個obj地址存放的位置和obj首地址之間的偏移存儲在kmem_cache的offset成員。兩外一種方式是指針外置式,即下個obj的首地址存儲的位置位於obj尾部,也就是在obj尾部再分配sizeof(void *)位元組大小的記憶體。對於外置式則offset就等於kmem_cache的inuse成員。

3.2. per cpu freelist

針對每一個cpu都會分配一個struct kmem_cacche_cpu的結構體。可以稱作是本地緩存池。當記憶體申請的時候,優先從本地cpu緩存池申請。在分配初期,本地緩存池為空,自然要從伙伴系統分配一定頁數的記憶體。內核會為每一個物理頁幀創建一個struct page的結構體。kmem_cacche_cpu中page就會指向正在使用的slab的頁幀。freelist成員指向第一個可用記憶體obj首地址。處於正在使用的slab的struct page結構體中的freelist會置成NULL,因為沒有其他地方使用。struct page結構體中inuse代表已經使用的obj數量。這地方有個很有意思的地方,在剛從伙伴系統分配的slab的 inuse在分配初期就置成obj的總數,在分配obj的時候並不會改變。你是不是覺得很奇怪,既然表示已經使用obj的數量,為什麼一直是obj的總數呢?你想想,slab中的對象總有分配完的時候,那個時候就直接脫離kmem_cache_cpu了。此時的inuse不就名副其實了嘛!對於full slab就像圖的右下角,就像無人看管的孩子,沒有任何鏈表來管理。

3.3. per cpu partial

當圖中右下角full slab釋放obj的時候,首先就會將slab掛入per cpu partial鏈表管理。通過struct page中next成員形成單鏈表。per cpu partial鏈表指向的第一個page中會存放一些特殊的數據。例如:pobjects存儲著per cpu partial鏈表中所有slab可供分配obj的總數,如圖所示。當然還有一個圖中沒有體現的pages成員存儲per cpu partial鏈表中所有slab緩存池的個數。pobjects到底有什麼用呢?我們從full slab中釋放一個obj就添加到per cpu partial鏈表,總不能無限制的添加吧!因此,每次添加的時候都會判斷當前的pobjects是否大於kmem_cache的cpu_partial成員,如果大於,那麼就會將此時per cpu partial鏈表中所有的slab移送到kmem_cache_node的partial鏈表,然後再將剛剛釋放obj的slab插入到per cpu partial鏈表。如果不大於,則更新pobjects和pages成員,並將slab插入到per cpu partial鏈表。

3.4. per node partial

per node partia鏈表類似per cpu partial,區別是node中的slab是所有cpu共用的,而per cpu是每個cpu獨占的。假如現在的slab佈局如上圖所示。假如現在如紅色箭頭指向的obj將會釋放,那麼就是一個empty slab,此時判斷kmem_cache_node的nr_partial是否大於kmem_cache的min_partial,如果大於則會釋放該slab的記憶體。

4. slub分配記憶體原理

當調用kmem_cache_alloc()分配記憶體的時候,我們可以從正在使用slab分配,也可以從per cpu partial分配,同樣還可以從per node partial分配,那麼分配的順序是什麼呢?我們可以用下圖表示。

image

首先從cpu 本地緩存池分配,如果freelist不存在,就會轉向per cpu partial分配,如果per cpu partial也沒有可用對象,繼續查看per node partial,如果很不幸也不沒有可用對象的話,就只能從伙伴系統分配一個slab了,並掛入per cpu freelist。我們詳細看一下這幾種情況。

  1. kmem_cache剛剛建立,還沒有任何對象可供分配,此時只能從伙伴系統分配一個slab,如下圖所示。

image

  1. 如果正在使用的slab有free obj,那麼就直接分配即可,這種是最簡單快捷的。如下圖所示。

image

  1. 隨著正在使用的slab中obj的一個個分配出去,最終會無obj可分配,此時per cpu partial鏈表中有可用slab用於分配,那麼就會從per cpu partial鏈表中取下一個slab用於分配obj。如下圖所示。

image

  1. 隨著正在使用的slab中obj的一個個分配出去,最終會無obj可分配,此時per cpu partial鏈表也為空,此時發現per node partial鏈表中有可用slab用於分配,那麼就會從per node partial鏈表中取下一個slab用於分配obj。如下圖所示。

image

5. slub釋放記憶體原理

我們可以通過kmem_cache_free()介面釋放申請的obj對象。釋放對象的流程如下圖所示。

image

如果釋放的obj就是屬於正在使用cpu上的slab,那麼直接釋放即可,非常簡單;如果不是的話,首先判斷所屬slub是不是full狀態,因為full slab是沒媽的孩子,釋放之後就變成partial empty,急需要找個鏈表領養啊!這個媽就是per cpu partial鏈表。如果per cpu partial鏈表管理的所有slab的free object數量超過kmem_cache的cpu_partial成員的話,就需要將per cpu partial鏈表管理的所有slab移動到per node partial鏈表管理;如果不是full slab的話,繼續判斷釋放當前obj後的slab是否是empty slab,如果是empty slab,那麼在滿足kmem_cache_node的nr_partial大於kmem_cache的min_partial的情況下,則會釋放該slab的記憶體。其他情況就直接釋放即可。

  1. 假設下圖左邊的情況下釋放obj,如果滿足kmem_cache_node的nr_partial大於kmem_cache的min_partial的話,釋放情況如下圖所示。

image

  1. 假設下圖左邊的情況下釋放obj,如果不滿足kmem_cache_node的nr_partial大於kmem_cache的min_partial的話,釋放情況如下圖所示。

image

  1. 假設下圖從full slab釋放obj的話,如果滿足per cpu partial管理的所有slab的free object數量大於kmem_cache的cpu_partial成員的話的話,將per cpu partial鏈表管理的所有slab移動到per node partial鏈表管理,釋放情況如下圖所示。

image

  1. 假設下圖從full slab釋放obj的話,如果不滿足per cpu partial管理的所有slab的free object數量大於kmem_cache的cpu_partial成員的話的話,釋放情況如下圖所示。

image

6. kmalloc

了,說了這麼多,估計你會感覺slab好像跟我們沒什麼關係。如果作為一個驅動開發者,是不是感覺自己寫的driver從來沒有使用過這些介面呢?其實我們經常使用,只不過隱藏在kmalloc的面具之下。

kmalloc的記憶體分配就是基於slab分配器,在系統啟動初期調用create_kmalloc_caches ()創建多個管理不同大小對象的kmem_cache,例如:8B、16B、32B、64B、…、64MB等大小。當然預設配置情況下,系統系統啟動之後創建的最大size的kmem_cache是kmalloc-8192。因此,通過slab介面分配的最大記憶體是8192 bytes。那麼通過kmalloc介面申請的記憶體大於8192 bytes該怎麼辦呢?其實kmalloc會判斷申請的記憶體是否大於8192 bytes,如果大於的話就會通過alloc_pages介面申請記憶體。kmem_cache的名稱以及大小使用struct kmalloc_info_struct管理。所有管理不同大小對象的kmem_cache的名稱如下:

conststruct kmalloc_info_struct kmalloc_info[] __initconst ={

{NULL,0},{"kmalloc-96",96},

{"kmalloc-192",192},{"kmalloc-8",8},

{"kmalloc-16",16},{"kmalloc-32",32},

{"kmalloc-64",64},{"kmalloc-128",128},

{"kmalloc-256",256},{"kmalloc-512",512},

{"kmalloc-1024",1024},{"kmalloc-2048",2048},

{"kmalloc-4096",4096},{"kmalloc-8192",8192},

{"kmalloc-16384",16384},{"kmalloc-32768",32768},

{"kmalloc-65536",65536},{"kmalloc-131072",131072},

{"kmalloc-262144",262144},{"kmalloc-524288",524288},

{"kmalloc-1048576",1048576},{"kmalloc-2097152",2097152},

{"kmalloc-4194304",4194304},{"kmalloc-8388608",8388608},

{"kmalloc-16777216",16777216},{"kmalloc-33554432",33554432},

{"kmalloc-67108864",67108864}

};

經過create_kmalloc_caches ()函數之後,系統通過create_kmalloc_cache()創建以上不同size的kmem_cache,並將這些kmem_cache存儲在kmalloc_caches全局變數中以備後續kmalloc分配記憶體。現在假如通過kmalloc(17, GFP_KERNEL)申請記憶體,系統會從名稱“kmalloc-32”管理的slab緩存池中分配一個對象。即使浪費了15Byte。

我們來看看kmalloc的實現方式。

static __always_inline void*kmalloc(size_t size,gfp_t flags)

{

if(__builtin_constant_p(size)){

if(size > KMALLOC_MAX_CACHE_SIZE)

return kmalloc_large(size, flags);

if(!(flags & GFP_DMA)){

int index = kmalloc_index(size);

if(!index)

return ZERO_SIZE_PTR;

return kmem_cache_alloc_trace(kmalloc_caches[index], flags, size);

}

}

return __kmalloc(size, flags);

}
  1. __builtin_constant_p是gcc工具用來判斷參數是否是一個常數,畢竟有些操作對於常數來說是可以優化的。
  2. 通過kmalloc_index函數查找符合滿足分配大小的最小kmem_cache。
  3. 將index作為下表從kmalloc_caches數組中找到符合的kmem_cache,並從slab緩存池中分配對象。

我們再看一下kmalloc_index的實現。

static __always_inline int kmalloc_index(size_t size)

{

if(!size)

return0;

if(size <= KMALLOC_MIN_SIZE)

return KMALLOC_SHIFT_LOW;

if(KMALLOC_MIN_SIZE <=32&& size >64&& size <=96)

return1;

if(KMALLOC_MIN_SIZE <=64&& size >128&& size <=192)

return2;

if(size <=8)return3;

if(size <=16)return4;

if(size <=32)return5;

if(size <=64)return6;

if(size <=128)return7;

if(size <=256)return8;

if(size <=512)return9;

if(size <=1024)return10;

if(size <=2*1024)return11;

if(size <=4*1024)return12;

if(size <=8*1024)return13;

if(size <=16*1024)return14;

if(size <=32*1024)return15;

if(size <=64*1024)return16;

if(size <=128*1024)return17;

if(size <=256*1024)return18;

if(size <=512*1024)return19;

if(size <=1024*1024)return20;

if(size <=2*1024*1024)return21;

if(size <=4*1024*1024)return22;

if(size <=8*1024*1024)return23;

if(size <=16*1024*1024)return24;

if(size <=32*1024*1024)return25;

if(size <=64*1024*1024)return26;

/* Will never be reached. Needed because the compiler may complain */

return-1;

} 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • (VSCode是最好的編輯器,沒有之一!嗯,就是這樣!) TI的處理器,官方庫是很豐富的,不論官方庫是否混亂、是否難理解,豐富多樣這一點就足夠吸引人,以至於總想著在VSCode里順利地操著官方庫來寫代碼。 前文提過,在VSCode下有兩種擴展插件可以盤弄TI的單片機開發,分別是“PlatformIO ...
  • 一、通過web瀏覽器訪問 1、打開瀏覽器,在地址欄上輸入IMM2的IP地址訪問,打開登錄頁面後,輸入用戶名和密碼 登錄 PS:第一次登錄IMM2時,初始的用戶名為USERID,密碼為PASSW0RD(零,不是字母O)。在初始配置期間更改此用戶名和密碼,以增強安全性。 二、通過IPMI 管理工具:IP ...
  • Nmap是一款針對大型網路的埠掃描工具,被廣泛應用於黑客領域做漏洞探測以及安全掃描,其主要功能有主機發現(Host Discovery)、 埠掃描(Port Scanning)、 版本偵測(Version Detection) 、操作系統偵測(Operating System Detection ...
  • 一、TensorRT支持的模型: TensorRT 直接支持的model有ONNX、Caffe、TensorFlow,其他常見model建議先轉化成ONNX。總結如下: 1 ONNX(.onnx) 2 Keras(.h5) --> ONNX(.onnx) (https://github.com/on ...
  • 添加主機與主機組 1. 進入web頁面,在 配置 主機群組,新建主機群組 2. 在 配置 主機,新建主機 在可見的名稱中建議填寫為類似 主機類型 主機名 IP或功能變數名稱 的格式,如Web Hyrule001 192.168.233.247 在 群組 中選擇之前新建的群組 agent代理程式的介面 填寫a ...
  • Let’s Encrypt 是由 Internet Security Research Group (ISRG) 開發的一個自由、自動化和開放的證書頒發機構。目前幾乎所有的現代瀏覽器都信任由 Let’s Encrypt 頒發的證書。 這個教程,將會一步一步的教你如何在 CentOS 7 上通過 C... ...
  • 詳細說明 Restricted 執行策略不允許任何腳本運行。 AllSigned 和 RemoteSigned 執行策略可防止 Windows PowerShell 運行沒有數字簽名的腳本。 允許運行簽名腳本 首次在電腦上啟動 Windows PowerShell 時,現用執行策略很可能是 Res ...
  • 1、LAMP 環境搭建 初次安裝可以先關閉selinux 和 firewall 1.1 mariadb資料庫安裝,啟動、加入啟動項 1.2 apache安裝安裝、啟動、加入啟動項 更改mysql root初始密碼: mysql> set password for root@localhost = p ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...