高端記憶體映射之vmalloc分配記憶體中不連續的頁--Linux記憶體管理(十九)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/12/29/10196467.html
-Advertisement-
Play Games

1 記憶體中不連續的頁的分配 根據上文的講述, 我們知道物理上連續的映射對內核是最好的, 但並不總能成功地使用. 在分配一大塊記憶體時, 可能竭盡全力也無法找到連續的記憶體塊. 在用戶空間中這不是問題,因為普通進程設計為使用處理器的分頁機制, 當然這會降低速度並占用TLB. 在內核中也可以使用同樣的技術. ...


1 記憶體中不連續的頁的分配

根據上文的講述, 我們知道物理上連續的映射對內核是最好的, 但並不總能成功地使用. 在分配一大塊記憶體時, 可能竭盡全力也無法找到連續的記憶體塊.

在用戶空間中這不是問題,因為普通進程設計為使用處理器的分頁機制, 當然這會降低速度並占用TLB.

在內核中也可以使用同樣的技術. 內核分配了其內核虛擬地址空間的一部分, 用於建立連續映射.

在IA-32系統中, 前16M劃分給DMA區域, 後面一直到第896M作為NORMAL直接映射區, 緊隨直接映射的前896MB物理記憶體,在插入的8MB安全隙之後, 是一個用於管理不連續記憶體的區域. 這一段具有線性地址空間的所有性質. 分配到其中的頁可能位於物理記憶體中的任何地方. 通過修改負責該區域的內核頁表, 即可做到這一點.

Persistent mappings和Fixmaps地址空間都比較小, 這裡我們忽略它們, 這樣只剩下直接地址映射和VMALLOC區, 這個劃分應該是平衡兩個需求的結果

  1. 儘量增加DMA和Normal區大小,也就是直接映射地址空間大小,當前主流平臺的記憶體,基本上都超過了512MB,很多都是標配1GB記憶體,因此註定有一部分記憶體無法進行線性映射。

  2. 保留一定數量的VMALLOC大小,這個值是應用平臺特定的,如果應用平臺某個驅動需要用vmalloc分配很大的地址空間,那麼最好通過在kernel參數中指定vmalloc大小的方法,預留較多的vmalloc地址空間。
  3. 並不是Highmem沒有或者越少越好,這個是我的個人理解,理由如下:高端記憶體就像個垃圾桶和緩衝區,防止來自用戶空間或者vmalloc的映射破壞Normal zone和DMA zone的連續性,使得它們碎片化。當這個垃圾桶較大時,那麼污染Normal 和DMA的機會自然就小了。

通過這種方式, 將內核的內核虛擬地址空間劃分為幾個不同的區域

下麵的圖是VMALLOC地址空間內部劃分情況

2 用vmalloc分配記憶體

vmalloc是一個介面函數, 內核代碼使用它來分配在虛擬記憶體中連續但在物理記憶體中不一定連續的記憶體

//  http://lxr.free-electrons.com/source/include/linux/vmalloc.h?v=4.7#L70
void *vmalloc(unsigned long size);

該函數只需要一個參數, 用於指定所需記憶體區的長度, 與此前討論的函數不同, 其長度單位不是頁而是位元組, 這在用戶空間程式設計中是很普遍的.

使用vmalloc的最著名的實例是內核對模塊的實現. 因為模塊可能在任何時候載入, 如果模塊數據比較多, 那麼無法保證有足夠的連續記憶體可用, 特別是在系統已經運行了比較長時間的情況下.

如果能夠用小塊記憶體拼接出足夠的記憶體, 那麼使用vmalloc可以規避該問題

內核中還有大約400處地方調用了vmalloc, 特別是在設備和聲音驅動程式中.

因為用於vmalloc的記憶體頁總是必須映射在內核地址空間中, 因此使用ZONE_HIGHMEM記憶體域的頁要優於其他記憶體域. 這使得內核可以節省更寶貴的較低端記憶體域, 而又不會帶來額外的壞處. 因此, vmalloc等映射函數是內核出於自身的目的(並非因為用戶空間應用程式)使用高端記憶體頁的少數情形之一.

所有有關vmalloc的數據結構和API結構聲明在include/linux/vmalloc.h

聲明頭文件 NON-MMU實現 MMU實現
include/linux/vmalloc.h mm/nommu.c mm/vmalloc.c

2.1 數據結構

內核在管理虛擬記憶體中的vmalloc區域時, 內核必須跟蹤哪些子區域被使用、哪些是空閑的. 為此定義了一個數據結構vm_struct, 將所有使用的部分保存在一個鏈表中. 該結構提的定義在include/linux/vmalloc.h?v=4.7, line 32

// http://lxr.free-electrons.com/source/include/linux/vmalloc.h?v=4.7#L32
struct vm_struct {
    struct vm_struct    *next;
    void            *addr;
    unsigned long       size;
    unsigned long       flags;
    struct page         **pages;
    unsigned int        nr_pages;
    phys_addr_t         phys_addr;
    const void          *caller;
};

註意, 內核使用了一個重要的數據結構稱之為vm_area_struct, 以管理用戶空間進程的虛擬地址空間內容. 儘管名稱和目的都是類似的, 雖然二者都是做虛擬地址空間映射的, 但不能混淆這兩個結構。

  1. 前者是內核虛擬地址空間映射,而後者則是應用進程虛擬地址空間映射。
  2. 前者不會產生page fault,而後者一般不會提前分配頁面,只有當訪問的時候,產生page fault來分配頁面。

對於每個用vmalloc分配的子區域, 都對應於內核記憶體中的一個該結構實例. 該結構各個成員的語義如下

欄位 描述
next 使得內核可以將vmalloc區域中的所有子區域保存在一個單鏈表上
addr 定義了分配的子區域在虛擬地址空間中的起始地址。size表示該子區域的長度. 可以根據該信息來勾畫出vmalloc區域的完整分配方案
flags 存儲了與該記憶體區關聯的標誌集合, 這幾乎是不可避免的. 它只用於指定記憶體區類型
pages 是一個指針,指向page指針的數組。每個數組成員都表示一個映射到虛擬地址空間中的物理記憶體頁的page實例
nr_pages 指定pages中數組項的數目,即涉及的記憶體頁數目
phys_addr 僅當用ioremap映射了由物理地址描述的物理記憶體區域時才需要。該信息保存在phys_addr中
caller

其中flags只用於指定記憶體區類型, 所有可能的flag標識以巨集的形式定義在include/linux/vmalloc.h?v=4.7, line 14

//  http://lxr.free-electrons.com/source/include/linux/vmalloc.h?v=4.7#L14
/* bits in flags of vmalloc's vm_struct below */
#define VM_IOREMAP              0x00000001      /* ioremap() and friends */
#define VM_ALLOC                0x00000002      /* vmalloc() */
#define VM_MAP                  0x00000004      /* vmap()ed pages */
#define VM_USERMAP              0x00000008      /* suitable for remap_vmalloc_range */
#define VM_UNINITIALIZED        0x00000020      /* vm_struct is not fully initialized */
#define VM_NO_GUARD             0x00000040      /* don't add guard page */
#define VM_KASAN                0x00000080      /* has allocated kasan shadow memory */
/* bits [20..32] reserved for arch specific ioremap internals */
flag標識 描述
VM_IOREMAP 表示將幾乎隨機的物理記憶體區域映射到vmalloc區域中. 這是一個特定於體繫結構的操作
VM_ALLOC 指定由vmalloc產生的子區域

VM_MAP 用於表示將現存pages集合映射到連續的虛擬地址空間中
VM_USERMAP |
VM_UNINITIALIZED|
VM_NO_GUARD |
VM_KASAN|

下圖給出了該結構使用方式的一個實例. 其中依次映射了3個(假想的)物理記憶體頁, 在物理記憶體中的位置分別是1 023、725和7 311. 在虛擬的vmalloc區域中, 內核將其看作起始於VMALLOC_START + 100的一個連續記憶體區, 大小為3*PAGE_SIZE的內核地址空間,被映射到物理頁面725, 1023和7311

2.2 創建vm_area

因為大部分體繫結構都支持mmu, 這裡我們只考慮有mmu的情況. 實際上沒有mmu支持時, vmalloc就無法實現非連續物理地址到連續內核地址空間的映射, vmalloc退化為kmalloc實現.

2.2.1 vmlist全局鏈表

在創建一個新的虛擬記憶體區之前, 必須找到一個適當的位置. vm_area實例組成的一個鏈表, 管理著vmalloc區域中已經建立的各個子區域. 定義在mm/vmalloc的全局變數vmlist是表頭. 定義在mm/vmalloc.c?v=4.7, line 1170

// http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1170
static struct vm_struct *vmlist __initdata;

2.2.2 分配函數

內核在mm/vmalloc中提供了輔助函數get_vm_area__get_vm_area, 它們負責參數準備工作, 而實際的分配工作交給底層函數__get_vm_area_node來完成, 這些函數定義在mm/vmalloc.c?v=4.7, line 1388

struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,
                unsigned long start, unsigned long end)
{
    return __get_vm_area_node(size, 1, flags, start, end, NUMA_NO_NODE,
                  GFP_KERNEL, __builtin_return_address(0));
}
EXPORT_SYMBOL_GPL(__get_vm_area);

struct vm_struct *__get_vm_area_caller(unsigned long size, unsigned long flags,
                       unsigned long start, unsigned long end,
                       const void *caller)
{
    return __get_vm_area_node(size, 1, flags, start, end, NUMA_NO_NODE,
                  GFP_KERNEL, caller);
}

/**
 *      get_vm_area  -  reserve a contiguous kernel virtual area
 *      @size:      size of the area
 *      @flags:     %VM_IOREMAP for I/O mappings or VM_ALLOC
 *
 *      Search an area of @size in the kernel virtual mapping area,
 *      and reserved it for out purposes.  Returns the area descriptor
 *      on success or %NULL on failure.
 */
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
{
    return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
                  NUMA_NO_NODE, GFP_KERNEL,
                  __builtin_return_address(0));
}

struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags,
                const void *caller)
{
    return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
                  NUMA_NO_NODE, GFP_KERNEL, caller);
}

這些函數是負責實際工作的__get_vm_area_node函數的前端. 根據子區域的長度信息, __get_vm_area_node函數試圖在虛擬的vmalloc空間中找到一個適當的位置. 該函數定義在mm/vmalloc.c?v=4.7, line 1354

由於各個vmalloc子區域之間需要插入1頁(警戒頁)作為安全隙, 內核首先適當提高需要分配的記憶體長度.

static struct vm_struct *__get_vm_area_node(unsigned long size,
        unsigned long align, unsigned long flags, unsigned long start,
        unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
    struct vmap_area *va;
    struct vm_struct *area;

    BUG_ON(in_interrupt());
    if (flags & VM_IOREMAP)
        align = 1ul << clamp_t(int, fls_long(size),
                       PAGE_SHIFT, IOREMAP_MAX_ORDER);

    size = PAGE_ALIGN(size);
    if (unlikely(!size))
        return NULL;

    area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
    if (unlikely(!area))
        return NULL;

    if (!(flags & VM_NO_GUARD))
        size += PAGE_SIZE;

    va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
    if (IS_ERR(va)) {
        kfree(area);
        return NULL;
    }

    setup_vmalloc_vm(area, va, flags, caller);

    return area;
}

start和end參數分別由調用者設置, 比如get_vm_area函數和get_vm_area_caller函數傳入VMALLOC_START和VMALLOC_END. 接下來迴圈遍歷vmlist的所有表元素,直至找到一個適當的項

2.2.3 釋放函數

remove_vm_area函數將一個現存的子區域從vmalloc地址空間刪除.

函數聲明如下, include/linux/vmalloc.h?v=4.7, line 121

//  http://lxr.free-electrons.com/source/include/linux/vmalloc.h?v=4.7#L121
struct vm_struct *remove_vm_area(void *addr);

函數定義在mm/vmalloc.c?v=4.7, line 1454

//  http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1446
/**
 *      remove_vm_area  -  find and remove a continuous kernel virtual area
 *      @addr:      base address
 *
 *      Search for the kernel VM area starting at @addr, and remove it.
 *      This function returns the found VM area, but using it is NOT safe
 *      on SMP machines, except for its size or flags.
 */
struct vm_struct *remove_vm_area(const void *addr)
{
    struct vmap_area *va;

    va = find_vmap_area((unsigned long)addr);
    if (va && va->flags & VM_VM_AREA) {
        struct vm_struct *vm = va->vm;

        spin_lock(&vmap_area_lock);
        va->vm = NULL;
        va->flags &= ~VM_VM_AREA;
        spin_unlock(&vmap_area_lock);

        vmap_debug_free_range(va->va_start, va->va_end);
        kasan_free_shadow(vm);
        free_unmap_vmap_area(va);

        return vm;
    }
    return NULL;
}

2.3 vmalloc分配記憶體區

vmalloc發起對不連續的記憶體區的分配操作. 該函數只是一個前端, 為__vmalloc提供適當的參數, 後者直接調用__vmalloc_node.

vmalloc只是__vmalloc_node_flags的前端介面, 複雜向__vmalloc_node_flags傳遞數據, 而__vmalloc_node_flags又是__vmalloc_node的前端介面, 而後者又將實際的工作交給__vmalloc_node_range函數來完成

vmalloc函數定義在mm/vmalloc.c?v=4.7, line 1754, 將實際的工作交給__vmalloc_node_flags函數來完成.

//  http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1754
/**
 *      vmalloc  -  allocate virtually contiguous memory
 *      @size:      allocation size
 *      Allocate enough pages to cover @size from the page level
 *      allocator and map them into contiguous kernel virtual space.
 *
 *      For tight control over page level allocator and protection flags
 *      use __vmalloc() instead.
 */
void *vmalloc(unsigned long size)
{
    return __vmalloc_node_flags(size, NUMA_NO_NODE,
                    GFP_KERNEL | __GFP_HIGHMEM);
}
EXPORT_SYMBOL(vmalloc);

__vmalloc_node_flags函數定義在mm/vmalloc.c?v=4.7, line 1747, 通過__vmalloc_node來完成實際的工作.

//  http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1747
static inline void *__vmalloc_node_flags(unsigned long size,
                    int node, gfp_t flags)
{
    return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
                    node, __builtin_return_address(0));
}

__vmalloc_node函數定義在mm/vmalloc.c?v=4.7, line 1719, 通過__vmalloc_node_range來完成實際的工作.

//  http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1719
/**
 *      __vmalloc_node  -  allocate virtually contiguous memory
 *      @size:      allocation size
 *      @align:     desired alignment
 *      @gfp_mask:      flags for the page level allocator
 *      @prot:      protection mask for the allocated pages
 *      @node:      node to use for allocation or NUMA_NO_NODE
 *      @caller:    caller's return address
 *
 *      Allocate enough pages to cover @size from the page level
 *      allocator with @gfp_mask flags.  Map them into contiguous
 *      kernel virtual space, using a pagetable protection of @prot.
 */
static void *__vmalloc_node(unsigned long size, unsigned long align,
                gfp_t gfp_mask, pgprot_t prot,
                int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, prot, 0, node, caller);
}

__vmalloc_node_range最終完成了記憶體區的分配工作

//  http://lxr.free-electrons.com/source/mm/vmalloc.c?v=4.7#L1658
/**
 *      __vmalloc_node_range  -  allocate virtually contiguous memory
 *      @size:      allocation size
 *      @align:     desired alignment
 *      @start:     vm area range start
 *      @end:       vm area range end
 *      @gfp_mask:      flags for the page level allocator
 *      @prot:      protection mask for the allocated pages
 *      @vm_flags:      additional vm area flags (e.g. %VM_NO_GUARD)
 *      @node:      node to use for allocation or NUMA_NO_NODE
 *      @caller:    caller's return address
 *
 *      Allocate enough pages to cover @size from the page level
 *      allocator with @gfp_mask flags.  Map them into contiguous
 *      kernel virtual space, using a pagetable protection of @prot.
 */
void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, unsigned long vm_flags, int node,
            const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;

    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages)
        goto fail;

    area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED |
                vm_flags, start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;

    addr = __vmalloc_area_node(area, gfp_mask, prot, node);
    if (!addr)
        return NULL;

    /*
     * In this function, newly allocated vm_struct has VM_UNINITIALIZED
     * flag. It means that vm_struct is not fully initialized.
     * Now, it is fully initialized, so remove this flag here.
     */
    clear_vm_uninitialized_flag(area);

    /*
     * A ref_count = 2 is needed because vm_struct allocated in
     * __get_vm_area_node() contains a reference to the virtual address of
     * the vmalloc'ed block.
     */
    kmemleak_alloc(addr, real_size, 2, gfp_mask);

    return addr;

fail:
    warn_alloc_failed(gfp_mask, 0,
              "vmalloc: allocation failure: %lu bytes\n",
              real_size);
    return NULL;
}

實現分為3部分

  • 首先, get_vm_areavmalloc地址空間中找到一個適當的區域.
  • 接下來從物理記憶體分配各個頁
  • 最後將這些頁連續地映射到vmalloc區域中, 分配虛擬記憶體的工作就完成了.

如果顯式指定了分配頁幀的結點, 則內核調用alloc_pages_node, 否則,使用alloc_page從當前結點分配頁幀.

分配的頁從相關結點的伙伴系統移除. 在調用時, vmalloc將gfp_mask設置為GFP_KERNEL | __GFP_HIGHMEM,內核通過該參數指示記憶體管理子系統儘可能從ZONE_HIGHMEM記憶體域分配頁幀. 理由已經在上文給出:低端記憶體域的頁幀更為寶貴,因此不應該浪費到vmalloc的分配中,在此使用高

3 備選映射方法

除了vmalloc之外,還有其他方法可以創建虛擬連續映射。這些都基於上文討論的__vmalloc函數或使用非常類似的機制

  • vmalloc_32的工作方式與vmalloc相同,但會確保所使用的物理記憶體總是可以用普通32位指針定址。如果某種體繫結構的定址能力超出基於字長計算的範圍, 那麼這種保證就很重要。例如,在啟用了PAEIA-32系統上,就是如此.
  • vmap使用一個page數組作為起點,來創建虛擬連續記憶體區。與vmalloc相比,該函數所用的物理記憶體位置不是隱式分配的,而需要先行分配好,作為參數傳遞。此類映射可通過vm_map實例中的VM_MAP標誌辨別。
  • 不同於上述的所有映射方法, ioremap是一個特定於處理器的函數, 必須在所有體繫結構上實現. 它可以將取自物理地址空間、由系統匯流排用於I/O操作的一個記憶體塊,映射到內核的地址空間中.

該函數在設備驅動程式中使用很多, 可將用於與外設通信的地址區域暴露給內核的其他部分使用(當然也包括其本身).

4 釋放記憶體

有兩個函數用於向內核釋放記憶體, vfree用於釋放vmalloc和vmalloc_32分配的區域,而vunmap用於釋放由vmap或ioremap創建的映射。這兩個函數都會歸結到__vunmap

void __vunmap(void *addr, int deallocate_pages)

addr表示要釋放的區域的起始地址, deallocate_pages指定了是否將與該區域相關的物理記憶體頁返回給伙伴系統. vfree將後一個參數設置為1, 而vunmap設置為0, 因為在這種情況下只刪除映射, 而不將相關的物理記憶體頁返回給伙伴系統. 圖3-40給出了__vunmap的代碼流程圖

不必明確給出需要釋放的區域長度, 長度可以從vmlist中的信息導出. 因此__vunmap的第一個任務是在__remove_vm_area(由remove_vm_area在完成鎖定之後調用)中掃描該鏈表, 以找到 相關項。

unmap_vm_area使用找到的vm_area實例,從頁表刪除不再需要的項。與分配記憶體時類似,該函 數需要操作各級頁表,但這一次需要刪除涉及的項。它還會更新CPU高速緩存。

如果__vunmap的參數deallocate_pages設置為1(在vfree中),內核會遍歷area->pages的所 有元素,即指向所涉及的物理記憶體頁的page實例的指針。然後對每一項調用__free_page,將頁釋放 到伙伴系統。

最後,必須釋放用於管理該記憶體區的內核數據結構。


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

-Advertisement-
Play Games
更多相關文章
  • 接觸Linux已經有一段時間了,由於實際需要,三三兩兩地掌握了一些基本語法和實用語句,主要都是在日常開發中用得比較多的,條理不是特別清晰,請見諒!下麵開始上硬貨!! 基本操作: 關閉Linux系統的命令:init 0 切換虛擬終端的命令:Ctrl + Alt + F[1~6] 釋:Linux共有6個 ...
  • 有關Linux ipv6模塊載入失敗的問題 同事一個SUSE11sp3環境配置ipv6地址失敗,提示不支持IPv6,請求幫助,第一反應是應該ipv6相關內核模塊沒有載入。 主要檢查內容: ipv6地址是否存在 ifconfig |grep inet6 沒有預設inet6地址 ipv6模塊是否存在 # ...
  • 小編最近想學習一下小程式的開發,然後發現小程式的開發需要功能變數名稱為Https協議,所以就特地去配置了一下,配置成功了就放出來和大家一起分享 ...
  • 網路上有很多關於優秀的關於Paxos 演算法的文章,我下麵進行整理搜集一下: 分散式理論之一:Paxos演算法的通俗理解 維基的簡介:Paxos演算法是萊斯利·蘭伯特(Leslie Lamport,就是 LaTeX 中的"La",此人現在在微軟研究院)於1990年提出的一種基於消息傳遞且具有高度容錯特性的 ...
  • 綜述 Page cache是通過將磁碟中的數據緩存到記憶體中,從而減少磁碟I/O操作,從而提高性能。此外,還要確保在page cache中的數據更改時能夠被同步到磁碟上,後者被稱為page回寫(page writeback)。一個inode對應一個page cache對象,一個page cache對象 ...
  • 一、獲取root許可權 輸入root密碼 二、檢查是否安裝 如果安裝,會顯示安裝版本號,沒有就什麼都不顯示 三、若已安裝過vsftpd,先卸載。卸載前,先停止服務 ,然後再卸載。 停止服務: 卸載: 四、安裝vsftpd **此時可能遇見的錯誤** 這是由於 yum 進程被占用了,執行一下命令,關閉y ...
  • Page cache和buffer cache一直以來是兩個比較容易混淆的概念,在網上也有很多人在爭辯和猜想這兩個cache到底有什麼區別,討論到最後也一直沒有一個統一和正確的結論,在我工作的這一段時間,page cache和buffer cache的概念曾經困擾過我,但是仔細分析一下,這兩個概念實 ...
  • 一 簡介 Docker最初是dotCloud公司的一個內部項目,誕生於 2013 年初,由google公司開源的Go語言開發。 Docker是一個開源的引擎,可以輕鬆的為任何應用創建一個輕量級的、可移植的、自給自足的容器。開發者在筆記本上編譯測試通過的容器可以批量地在生產環境中部署,包括VMs(虛擬 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...