Linux記憶體描述之記憶體節點node--Linux記憶體管理(二)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/11/21/9992639.html
-Advertisement-
Play Games

1 記憶體節點node 1.1 為什麼要用node來描述記憶體 這點前面是說的很明白了, NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連接, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠程記憶體的速度要快 Linux適用於各種不同的體繫結構, 而 ...


1 記憶體節點node

1.1 為什麼要用node來描述記憶體

這點前面是說的很明白了, NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連接, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠程記憶體的速度要快

Linux適用於各種不同的體繫結構, 而不同體繫結構在記憶體管理方面的差別很大. 因此linux內核需要用一種體繫結構無關的方式來表示記憶體.

因此linux內核把物理記憶體按照CPU節點劃分為不同的node, 每個node作為某個cpu結點的本地記憶體, 而作為其他CPU節點的遠程記憶體, 而UMA結構下, 則任務系統中只存在一個記憶體node, 這樣對於UMA結構來說, 內核把記憶體當成只有一個記憶體node節點的偽NUMA

1.2 記憶體結點的概念

CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一個本地物理記憶體, 即一個CPU-node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點

系統的物理記憶體被劃分為幾個節點(node), 一個node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點

記憶體被劃分為結點. 每個節點關聯到系統中的一個處理器, 內核中表示為pg_data_t的實例. 系統中每個節點被鏈接到一個以NULL結尾的pgdat_list鏈表中<而其中的每個節點利用pg_data_tnode_next欄位鏈接到下一節.而對於PC這種UMA結構的機器來說, 只使用了一個成為contig_page_data的靜態pg_data_t結構.

記憶體中的每個節點都是由pg_data_t描述,而pg_data_t由struct pglist_data定義而來, 該數據結構定義在include/linux/mmzone.h, line 615

在分配一個頁面時, Linux採用節點局部分配的策略, 從最靠近運行中的CPU的節點分配記憶體, 由於進程往往是在同一個CPU上運行, 因此從當前節點得到的記憶體很可能被用到

1.3 pg_data_t描述記憶體節點

表示node的數據結構為typedef struct pglist_data pg_data_t, 這個結構定義在include/linux/mmzone.h, line 615中,結構體的內容如下:

/*
 * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM
 * (mostly NUMA machines?) to denote a higher-level memory zone than the
 * zone denotes.
 *
 * On NUMA machines, each NUMA node would have a pg_data_t to describe
 * it's memory layout.
 *
 * Memory statistics and page replacement data structures are maintained on a
 * per-zone basis.
 */
struct bootmem_data;
typedef struct pglist_data {
    /*  包含了結點中各記憶體域的數據結構 , 可能的區域類型用zone_type表示*/
    struct zone node_zones[MAX_NR_ZONES];
    /*  指點了備用結點及其記憶體域的列表,以便在當前結點沒有可用空間時,在備用結點分配記憶體   */
    struct zonelist node_zonelists[MAX_ZONELISTS];
    int nr_zones;                                   /*  保存結點中不同記憶體域的數目    */
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
    struct page *node_mem_map;      /*  指向page實例數組的指針,用於描述結點的所有物理記憶體頁,它包含了結點中所有記憶體域的頁。    */
#ifdef CONFIG_PAGE_EXTENSION
    struct page_ext *node_page_ext;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
       /*  在系統啟動boot期間,記憶體管理子系統初始化之前,
       內核頁需要使用記憶體(另外,還需要保留部分記憶體用於初始化記憶體管理子系統)
       為解決這個問題,內核使用了自舉記憶體分配器 
       此結構用於這個階段的記憶體管理  */
    struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
    /*
     * Must be held any time you expect node_start_pfn, node_present_pages
     * or node_spanned_pages stay constant.  Holding this will also
     * guarantee that any pfn_valid() stays that way.
     *
     * pgdat_resize_lock() and pgdat_resize_unlock() are provided to
     * manipulate node_size_lock without checking for CONFIG_MEMORY_HOTPLUG.
     *
     * Nests above zone->lock and zone->span_seqlock
     * 當系統支持記憶體熱插撥時,用於保護本結構中的與節點大小相關的欄位。
     * 哪調用node_start_pfn,node_present_pages,node_spanned_pages相關的代碼時,需要使用該鎖。
     */
    spinlock_t node_size_lock;
#endif
    /* /*起始頁面幀號,指出該節點在全局mem_map中的偏移
    系統中所有的頁幀是依次編號的,每個頁幀的號碼都是全局唯一的(不只是結點內唯一)  */
    unsigned long node_start_pfn;
    unsigned long node_present_pages; /* total number of physical pages 結點中頁幀的數目 */
    unsigned long node_spanned_pages; /* total size of physical page range, including holes                     該結點以頁幀為單位計算的長度,包含記憶體空洞 */
    int node_id;        /*  全局結點ID,系統中的NUMA結點都從0開始編號  */
    wait_queue_head_t kswapd_wait;      /*  交換守護進程的等待隊列,
    在將頁幀換出結點時會用到。後面的文章會詳細討論。    */
    wait_queue_head_t pfmemalloc_wait;
    struct task_struct *kswapd;     /* Protected by  mem_hotplug_begin/end() 指向負責該結點的交換守護進程的task_struct。   */
    int kswapd_max_order;                       /*  定義需要釋放的區域的長度  */
    enum zone_type classzone_idx;

#ifdef CONFIG_COMPACTION
    int kcompactd_max_order;
    enum zone_type kcompactd_classzone_idx;
    wait_queue_head_t kcompactd_wait;
    struct task_struct *kcompactd;
#endif

#ifdef CONFIG_NUMA_BALANCING
    /* Lock serializing the migrate rate limiting window */
    spinlock_t numabalancing_migrate_lock;

    /* Rate limiting time interval */
    unsigned long numabalancing_migrate_next_window;

    /* Number of pages migrated during the rate limiting time interval */
    unsigned long numabalancing_migrate_nr_pages;
#endif

#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
    /*
     * If memory initialisation on large machines is deferred then this
     * is the first PFN that needs to be initialised.
     */
    unsigned long first_deferred_pfn;
#endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    spinlock_t split_queue_lock;
    struct list_head split_queue;
    unsigned long split_queue_len;
#endif
} pg_data_t;
欄位 描述
node_zones 每個Node劃分為不同的zone,分別為ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM
node_zonelists 這個是備用節點及其記憶體域的列表,噹噹前節點的記憶體不夠分配時,會選取訪問代價最低的記憶體進行分配。分配記憶體操作時的區域順序,當調用free_area_init_core()時,由mm/page_alloc.c文件中的build_zonelists()函數設置
nr_zones 當前節點中不同記憶體域zone的數量,1到3個之間。並不是所有的node都有3個zone的,比如一個CPU簇就可能沒有ZONE_DMA區域
node_mem_map node中的第一個page,它可以指向mem_map中的任何一個page,指向page實例數組的指針,用於描述該節點所擁有的的物理記憶體頁,它包含了該頁面所有的記憶體頁,被放置在全局mem_map數組中
bdata 這個僅用於引導程式boot 的記憶體分配,記憶體在啟動時,也需要使用記憶體,在這裡記憶體使用了自舉記憶體分配器,這裡bdata是指向記憶體自舉分配器的數據結構的實例
node_start_pfn pfn是page frame number的縮寫。這個成員是用於表示node中的開始那個page在物理記憶體中的位置的。是當前NUMA節點的第一個頁幀的編號,系統中所有的頁幀是依次進行編號的,這個欄位代表的是當前節點的頁幀的起始值,對於UMA系統,只有一個節點,所以該值總是0
node_present_pages node中的真正可以使用的page數量
node_present_pages node中的真正可以使用的page數量
node_spanned_pages 該節點以頁幀為單位的總長度,這個不等於前面的node_present_pages,因為這裡麵包含空洞記憶體
node_id node的NODE ID 當前節點在系統中的編號,從0開始
kswapd_wait node的等待隊列,交換守護列隊進程的等待列表
kswapd_max_order 需要釋放的區域的長度,以頁階為單位
classzone_idx 這個欄位暫時沒弄明白,不過其中的zone_type是對ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGH,ZONE_MOVABLE,__MAX_NR_ZONES的枚舉

1.4 結點的記憶體管理域

typedef struct pglist_data {
    /*  包含了結點中各記憶體域的數據結構 , 可能的區域類型用zone_type表示*/
    struct zone node_zones[MAX_NR_ZONES];
    /*  指點了備用結點及其記憶體域的列表,以便在當前結點沒有可用空間時,在備用結點分配記憶體   */
    struct zonelist node_zonelists[MAX_ZONELISTS];
    int nr_zones;                                   /*  保存結點中不同記憶體域的數目    */

} pg_data_t;
  • node_zones[MAX_NR_ZONES]數組保存了節點中各個記憶體域的數據結構,

  • 而node_zonelist則指定了備用節點以及其記憶體域的列表, 以便在當前結點沒有可用空間時, 在備用節點分配記憶體.

  • nr_zones存儲了結點中不同記憶體域的數目

1.5 結點的記憶體頁面

typedef struct pglist_data
{
    struct page *node_mem_map;      /*  指向page實例數組的指針,用於描述結點的所有物理記憶體頁,它包含了結點中所有記憶體域的頁。    */

    /* /*起始頁面幀號,指出該節點在全局mem_map中的偏移
    系統中所有的頁幀是依次編號的,每個頁幀的號碼都是全局唯一的(不只是結點內唯一)  */
    unsigned long node_start_pfn;
    unsigned long node_present_pages; /* total number of physical pages 結點中頁幀的數目 */
    unsigned long node_spanned_pages; /* total size of physical page range, including holes                     該結點以頁幀為單位計算的長度,包含記憶體空洞 */
    int node_id;        /*  全局結點ID,系統中的NUMA結點都從0開始編號  */
} pg_data_t;
  • 其中node_mem_map是指向頁面page實例數組的指針, 用於描述結點的所有物理記憶體頁. 它包含了結點中所有記憶體域的頁.

  • node_start_pfn是該NUMA結點的第一個頁幀的邏輯編號. 系統中所有的節點的頁幀是一次編號的, 每個頁幀的編號是全局唯一的. node_start_pfn在UMA系統中總是0, 因為系統中只有一個記憶體結點, 因此其第一個頁幀編號總是0.

  • node_present_pages指定了結點中頁幀的數目, 而node_spanned_pages則給出了該結點以頁幀為單位計算的長度. 二者的值不一定相同, 因為結點中可能有一些空洞, 並不對應真正的頁幀.

1.6 交換守護進程

typedef struct pglist_data
{
    wait_queue_head_t kswapd_wait;      /*  交換守護進程的等待隊列,
    在將頁幀換出結點時會用到。後面的文章會詳細討論。    */
    wait_queue_head_t pfmemalloc_wait;
    struct task_struct *kswapd;     /* Protected by  mem_hotplug_begin/end() 指向負責該結點的交換守護進程的task_struct。   */
};
  • kswapd指向了負責將該結點的交換守護進程的task_struct. 在將頁幀換出結點時會喚醒該進程.

  • kswap_wait是交換守護進程(swap daemon)的等待隊列

  • 而kswapd_max_order用於頁交換子系統的實現, 用來定義需要釋放的區域的長度.

2 結點狀態

2.1 結點狀態標識node_states

內核用enum node_state變數標記了記憶體結點所有可能的狀態信息, 其定義在include/linux/nodemask.h?v=4.7, line 381

enum node_states {
    N_POSSIBLE,         /* The node could become online at some point 
                         結點在某個時候可能變成聯機*/
    N_ONLINE,           /* The node is online 
                        節點是聯機的*/
    N_NORMAL_MEMORY,    /* The node has regular memory
                            結點是普通記憶體域 */
#ifdef CONFIG_HIGHMEM
    N_HIGH_MEMORY,      /* The node has regular or high memory 
                           結點是普通或者高端記憶體域*/
#else
    N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif
#ifdef CONFIG_MOVABLE_NODE
    N_MEMORY,           /* The node has memory(regular, high, movable) */
#else
    N_MEMORY = N_HIGH_MEMORY,
#endif
    N_CPU,      /* The node has one or more cpus */
    NR_NODE_STATES
};
狀態 描述
N_POSSIBLE 結點在某個時候可能變成聯機
N_ONLINE 節點是聯機的
N_NORMAL_MEMORY 結點是普通記憶體域
N_HIGH_MEMORY 結點是普通或者高端記憶體域
N_MEMORY 結點是普通,高端記憶體或者MOVEABLE域
N_CPU 結點有一個或多個CPU

其中N_POSSIBLE, N_ONLINE和N_CPU用於CPU和記憶體的熱插拔.

對記憶體管理有必要的標誌是N_HIGH_MEMORY和N_NORMAL_MEMORY, 如果結點有普通或高端記憶體則使用N_HIGH_MEMORY, 僅當結點沒有高端記憶體時才設置N_NORMAL_MEMORY

N_NORMAL_MEMORY,    /* The node has regular memory
                            結點是普通記憶體域 */
#ifdef CONFIG_HIGHMEM
    N_HIGH_MEMORY,      /* The node has regular or high memory 
                           結點是高端記憶體域*/
#else
    /*  沒有高端記憶體域, 仍設置N_NORMAL_MEMORY  */
    N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif

同樣ZONE_MOVABLE記憶體域同樣用類似的方法設置, 僅當系統中存在ZONE_MOVABLE記憶體域記憶體域(配置了CONFIG_MOVABLE_NODE參數)時, N_MEMORY才被設定, 否則則被設定成N_HIGH_MEMORY, 而N_HIGH_MEMORY設定與否同樣依賴於參數CONFIG_HIGHMEM的設定

#ifdef CONFIG_MOVABLE_NODE
    N_MEMORY,           /* The node has memory(regular, high, movable) */
#else
    N_MEMORY = N_HIGH_MEMORY,
#endif

2.2 結點狀態設置函數

內核提供了輔助函數來設置或者清楚位域活特定結點的一個比特位

static inline int node_state(int node, enum node_states state)
static inline void node_set_state(int node, enum node_states state)
static inline void node_clear_state(int node, enum node_states state)
static inline int num_node_state(enum node_states state)

此外巨集for_each_node_state(__node, __state)用來迭代處於特定狀態的所有結點,

#define for_each_node_state(__node, __state) \
        for_each_node_mask((__node), node_states[__state])

而for_each_online_node(node)則負責迭代所有的活動結點.

如果內核編譯只支持當個結點(即使用平坦記憶體模型), 則沒有結點點陣圖, 上述操作該點陣圖的函數則變成空操作, 其定義形式如下, 參見include/linux/nodemask.h?v=4.7, line 406

參見內核

#if MAX_NUMNODES > 1
    /*   some real function  */
#else
    /*  some NULL function  */
#endif

3 查找記憶體結點

node_id作為全局節點id。 系統中的NUMA結點都是從0開始編號的

3.1 linux-2.4中的實現

pgdat_next指針域和pgdat_list記憶體結點鏈表

而對於NUMA結構的系統中, 在linux-2.4.x之前的內核中所有的節點,記憶體結點pg_data_t都有一個next指針域pgdat_next指向下一個記憶體結點. 這樣一來系統中所有結點都通過單鏈表pgdat_list鏈接起來, 其末尾是一個NULL指針標記.

這些節點都放在該鏈表中,均由函數init_bootmem_core()初始化結點

那麼內核提供了巨集函數for_each_pgdat(pgdat)來遍歷node節點, 其只需要沿著node_next以此便立即可, 參照include/linux/mmzone.h?v=2.4.37, line 187

/**
 * for_each_pgdat - helper macro to iterate over nodes
 * @pgdat - pg_data_t * variable
 * Meant to help with common loops of the form
 * pgdat = pgdat_list;
 * while(pgdat) {
 *      ...
 *      pgdat = pgdat->node_next;
 * }
 */
#define for_each_pgdat(pgdat) \
        for (pgdat = pgdat_list; pgdat; pgdat = pgdat->node_next)

3.2 linux-3.x~4.x的實現

node_data記憶體節點數組

在新的linux3.x~linux4.x的內核中,內核移除了pg_data_t的pgdat_next之指針域, 同時也刪除了pgdat_list鏈表, 參見Remove pgdat listRemove pgdat list ver.2

但是定義了一個大小為MAX_NUMNODES類型為pg_data_t數組node_data,數組的大小根據CONFIG_NODES_SHIFT的配置決定. 對於UMA來說,NODES_SHIFT為0,所以MAX_NUMNODES的值為1.

for_each_online_pgdat遍歷所有的記憶體結點

內核提供了for_each_online_pgdatfor_each_online_pgdat(pgdat)來遍歷節點

/**
 * for_each_online_pgdat - helper macro to iterate over all online nodes
 * @pgdat - pointer to a pg_data_t variable
 */
#define for_each_online_pgdat(pgdat)                    \
        for (pgdat = first_online_pgdat();              \
             pgdat;                                     \
             pgdat = next_online_pgdat(pgdat))

其中first_online_pgdat可以查找到系統中第一個記憶體節點的pg_data_t信息, next_online_pgdat則查找下一個記憶體節點.

下麵我們來看看first_online_pgdat和next_online_pgdat是怎麼實現的.

first_online_node和next_online_node返回結點編號

由於沒了next指針域pgdat_next和全局node鏈表pgdat_list, 因而內核提供了first_online_node指向第一個記憶體結點, 而通過next_online_node來查找其下一個結點, 他們是通過狀態node_states的點陣圖來查找結點信息的, 定義在include/linux/nodemask.h?v4.7, line 432

//  http://lxr.free-electrons.com/source/include/linux/nodemask.h?v4.7#L432
#define first_online_node       first_node(node_states[N_ONLINE])
#define first_memory_node       first_node(node_states[N_MEMORY])
static inline int next_online_node(int nid)
{
    return next_node(nid, node_states[N_ONLINE]);
}

first_online_node和next_online_node返回所查找的node結點的編號, 而有了編號, 我們直接去node_data數組中按照編號進行索引即可去除對應的pg_data_t的信息.內核提供了NODE_DATA(node_id)巨集函數來按照編號來查找對應的結點, 它的工作其實其實就是從node_data數組中進行索引

NODE_DATA(node_id)查找編號node_id的結點pg_data_t信息

移除了pg_data_t->pgdat_next指針域. 但是所有的node都存儲在node_data數組中, 內核提供了函數NODE_DATA直接通過node編號索引節點pg_data_t信息, 參見NODE_DATA的定義

extern struct pglist_data *node_data[];
#define NODE_DATA(nid)          (node_data[(nid)])

在UMA結構的機器中, 只有一個node結點即contig_page_data, 此時NODE_DATA直接指向了全局的contig_page_data, 而與node的編號nid無關, 參照include/linux/mmzone.h?v=4.7, line 858, 其中全局唯一的記憶體node結點contig_page_data定義在mm/nobootmem.c?v=4.7, line 27, linux-2.4.37

#ifndef CONFIG_NEED_MULTIPLE_NODES
extern struct pglist_data contig_page_data;
#define NODE_DATA(nid)          (&contig_page_data)
#define NODE_MEM_MAP(nid)       mem_map
else
/*  ......  */
#endif

first_online_pgdat和next_online_pgdat返回結點的pg_data_t

  • 首先通過first_online_node和next_online_node找到節點的編號
  • 然後通過NODE_DATA(node_id)查找到對應編號的結點的pg_data_t信息
struct pglist_data *first_online_pgdat(void)
{
        return NODE_DATA(first_online_node);
}

struct pglist_data *next_online_pgdat(struct pglist_data *pgdat)
{
    int nid = next_online_node(pgdat->node_id);

    if (nid == MAX_NUMNODES)
        return NULL;
    return NODE_DATA(nid);
}

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

-Advertisement-
Play Games
更多相關文章
  • 十、課程詳情頁功能 1、課程列表頁面 1.1 前端頁面配置 將前端頁面course-list.html放到templates目錄下, 課程相關的頁面大致和base.html頁面的機構一致,繼承這個頁面即可,重寫block部分: 1.2 課程列表介面 在course/views.py文件中編寫課程相關 ...
  • 什麼是ML.NET? ML.NET是由微軟創建,為.NET開發者準備的開源機器學習框架。它是跨平臺的,可以在macOS,Linux及Windows上運行。 機器學習管道 ML.NET通過管道(pipeline)方式組合機器學習過程。整個管道分為以下四個部分: Load Data 載入數據 Trans ...
  • 在C#中做類型轉換時有時會使用到TryParse方法,該方法如果轉換失敗,out參數的值是什麼呢?我就在這裡犯傻了,特意寫文記錄下。 ...
  • 由於開發者不熟悉不同操作系統管理時區的方式,當用.Net Core開發與時區相關的應用運行在不同操作系統上會出現錯誤。這片文章將會探索一下在不同操作系統上用.Net Core 使用時區信息出現的問題與解決方案 ...
  • 索引: 商業開發實戰總結 一.API 列表 1.Where .Where(Func<M, bool> func) 如: .Where( it => (it.Prop1>=條件1 && it.Prop2<=條件2) || it.Prop3==條件3 ) 此類寫法,用在 Deleter/Updater/ ...
  • 公眾號的應用,開發及調試環境搭建 花生殼要註冊 需要二十多塊錢 ,還要實名認證,估計要一兩天才能審核通過 主要就是在 windows搭建測試環境 1.微信的應用場景 360百科微信簡介 https://baike.so.com/doc/5567128-5782263.html 微信公眾號平臺 htt ...
  • 作者:依樂祝 原文地址:https://www.cnblogs.com/yilezhu/p/9977862.html 寫在前面 千呼萬喚始出來,首先,請允許我長吸一口氣!真沒想到 "一份來自28歲老程式員的自白" 這篇文章會這麼火,更沒想到的是張善友隊長的公眾號居然也轉載了這篇文章,這就導致兩天的時 ...
  • 一、前言 最近一段時間自己主要的學習計劃還是按照畢業後設定的計劃,自己一步步的搭建一個前後端分離的 ASP.NET Core 項目,目前也還在繼續學習 Vue 中,雖然中間斷了很長時間,好歹還是堅持下來了,嗯,看了看時間,原本決定的半年完成肯定是完不成了。這兩周重新拾起來學習 Vue,文章也在慢慢的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...