高端記憶體映射之kmap持久內核映射--Linux記憶體管理(二十)

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

1 高端記憶體與內核映射 儘管 函數族可用於從高端記憶體域向內核映射頁幀(這些在內核空間中通常是無法直接看到的), 但這並不是這些函數的實際用途. 重要的是強調以下事實 : 內核提供了其他函數用於將 頁幀顯式映射到內核空間, 這些函數與vmalloc機制無關. 因此, 這就造成了混亂. 而在高端記憶體的頁 ...


1 高端記憶體與內核映射

儘管vmalloc函數族可用於從高端記憶體域向內核映射頁幀(這些在內核空間中通常是無法直接看到的), 但這並不是這些函數的實際用途.

重要的是強調以下事實 : 內核提供了其他函數用於將ZONE_HIGHMEM頁幀顯式映射到內核空間, 這些函數與vmalloc機制無關. 因此, 這就造成了混亂.

而在高端記憶體的頁不能永久地映射到內核地址空間. 因此, 通過alloc_pages()函數以__GFP_HIGHMEM標誌獲得的記憶體頁就不可能有邏輯地址.

在x86_32體繫結構總, 高於896MB的所有物理記憶體的範圍大都是高端記憶體, 它並不會永久地或自動映射到內核地址空間, 儘管X86處理器能夠定址物理RAM的範圍達到4GB(啟用PAE可以定址64GB), 一旦這些頁被分配, 就必須映射到內核的邏輯地址空間上. 在x86_32上, 高端地址的頁被映射到內核地址空間(即虛擬地址空間的3GB~4GB)

內核地址空間的最後128 MiB用於何種用途呢?

該部分有3個用途。

  1. 虛擬記憶體中連續、但物理記憶體中不連續的記憶體區,可以在vmalloc區域分配. 該機制通常用於用戶過程, 內核自身會試圖儘力避免非連續的物理地址。內核通常會成功,因為大部分大的記憶體塊都在啟動時分配給內核,那時記憶體的碎片尚不嚴重。但在已經運行了很長時間的系統上, 在內核需要物理記憶體時, 就可能出現可用空間不連續的情況. 此類情況, 主要出現在動態載入模塊時.

  2. 持久映射用於將高端記憶體域中的非持久頁映射到內核中

  3. 固定映射是與物理地址空間中的固定頁關聯的虛擬地址空間項,但具體關聯的頁幀可以自由選擇. 它與通過固定公式與物理記憶體關聯的直接映射頁相反,虛擬固定映射地址與物理記憶體位置之間的關聯可以自行定義,關聯建立後內核總是會註意到的.

在這裡有兩個預處理器符號很重要 __VMALLOC_RESERVE設置了vmalloc區域的長度, 而MAXMEM則表示內核可以直接定址的物理記憶體的最大可能數量.

內核中, 將記憶體劃分為各個區域是通過圖3-15所示的各個常數控制的。根據內核和系統配置, 這些常數可能有不同的值。直接映射的邊界由high_memory指定。

  1. 直接映射區

線性空間中從3G開始最大896M的區間, 為直接記憶體映射區,該區域的線性地址和物理地址存線上性轉換關係:線性地址=3G+物理地址。

  1. 動態記憶體映射區

該區域由內核函數vmalloc來分配, 特點是 : 線性空間連續, 但是對應的物理空間不一定連續. vmalloc分配的線性地址所對應的物理頁可能處於低端記憶體, 也可能處於高端記憶體.

  1. 永久記憶體映射區

該區域可訪問高端記憶體. 訪問方法是使用alloc_page(_GFP_HIGHMEM)分配高端記憶體頁或者使用kmap函數將分配到的高端記憶體映射到該區域.

  1. 固定映射區

該區域和4G的頂端只有4k的隔離帶,其每個地址項都服務於特定的用途,如ACPI_BASE等。

說明

註意用戶空間當然可以使用高端記憶體,而且是正常的使用,內核在分配那些不經常使用的記憶體時,都用高端記憶體空間(如果有),所謂不經常使用是相對來說的,比如內核的一些數據結構就屬於經常使用的,而用戶的一些數據就屬於不經常使用的。用戶在啟動一個應用程式時,是需要記憶體的,而每個應用程式都有3G的線性地址,給這些地址映射頁表時就可以直接使用高端記憶體。

而且還要糾正一點的是:那128M線性地址不僅僅是用在這些地方的,如果你要載入一個設備,而這個設備需要映射其記憶體到內核中,它也需要使用這段線性地址空間來完成,否則內核就不能訪問設備上的記憶體空間了.

總之,內核的高端線性地址是為了訪問內核固定映射以外的記憶體資源。進程在使用記憶體時,觸發缺頁異常,具體將哪些物理頁映射給用戶進程是內核考慮的事情. 在用戶空間中沒有高端記憶體這個概念.

即內核對於低端記憶體, 不需要特殊的映射機制, 使用直接映射即可以訪問普通記憶體區域, 而對於高端記憶體區域, 內核可以採用三種不同的機制將頁框映射到高端記憶體 : 分別叫做永久內核映射臨時內核映射以及非連續記憶體分配

2 持久內核映射

如果需要將高端頁幀長期映射(作為持久映射)到內核地址空間中, 必須使用kmap函數. 需要映射的頁用指向page的指針指定,作為該函數的參數。該函數在有必要時創建一個映射(即,如果該頁確實是高端頁), 並返回數據的地址.

如果沒有啟用高端支持, 該函數的任務就比較簡單. 在這種情況下, 所有頁都可以直接訪問, 因此只需要返回頁的地址, 無需顯式創建一個映射.

如果確實存在高端頁, 情況會比較複雜. 類似於vmalloc, 內核首先必須建立高端頁和所映射到的地址之間的關聯. 還必須在虛擬地址空間中分配一個區域以映射頁幀, 最後, 內核必須記錄該虛擬區域的哪些部分在使用中, 哪些仍然是空閑的.

2.1 數據結構

內核在IA-32平臺上在vmalloc區域之後分配了一個區域, 從PKMAP_BASEFIXADDR_START. 該區域用於持久映射. 不同體繫結構使用的方案是類似的.

永久內核映射允許內核建立高端頁框到內核地址空間的長期映射。 他們使用著內核頁表中一個專門的頁表, 其地址存放在變數pkmap_page_table中, 頁表中的表項數由LAST_PKMAP巨集產生. 因此,內核一次最多訪問2MB或4MB的高端記憶體.

#define PKMAP_BASE              (PAGE_OFFSET - PMD_SIZE)

頁表映射的線性地址從PKMAP_BASE開始. pkmap_count數組包含LAST_PKMAP個計數器,pkmap_page_table頁表中的每一項都有一個。

//  http://lxr.free-electrons.com/source/mm/highmem.c?v=4.7#L126
static int pkmap_count[LAST_PKMAP];
static  __cacheline_aligned_in_smp DEFINE_SPINLOCK(kmap_lock);

pte_t * pkmap_page_table;

高端映射區邏輯頁面的分配結構用分配表(pkmap_count)來描述,它有1024項,對應於映射區內不同的邏輯頁面。當分配項的值等於0時為自由項,等於1時為緩衝項,大於1時為映射項。映射頁面的分配基於分配表的掃描,當所有的自由項都用完時,系統將清除所有的緩衝項,如果連緩衝項都用完時,系統將進入等待狀態。

// http://lxr.free-electrons.com/source/mm/highmem.c?v=4.7#L126
/* 
高端映射區邏輯頁面的分配結構用分配表(pkmap_count)來描述,它有1024項, 
對應於映射區內不同的邏輯頁面。當分配項的值等於零時為自由項,等於1時為 
緩衝項,大於1時為映射項。映射頁面的分配基於分配表的掃描,當所有的自由 
項都用完時,系統將清除所有的緩衝項,如果連緩衝項都用完時,系 
統將進入等待狀態。 
*/  
static int pkmap_count[LAST_PKMAP];

pkmap_count(在mm/highmem.c?v=4.7, line 126定義)是一容量為LAST_PKMAP的整數數組, 其中每個元素都對應於一個持久映射頁。它實際上是被映射頁的一個使用計數器,語義不太常見.

內核可以通過get_next_pkmap_nr獲取到pkmap_count數組中元素的個數, 該函數定義在mm/highmem.c?v=4.7, line 66

/*
 * Get next index for mapping inside PKMAP region for page with given color.
 */
static inline unsigned int get_next_pkmap_nr(unsigned int color)
{
    static unsigned int last_pkmap_nr;

    last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
    return last_pkmap_nr;
}

為了記錄高端記憶體頁框與永久內核映射包含的線性地址之間的聯繫,內核使用了page_address_htable散列表.

該表包含一個page_address_map數據結構,用於為高端記憶體中的每一個頁框進行當前映射。而該數據結構還包含一個指向頁描述符的指針和分配給該頁框的線性地址。

/*
 * Describes one page->virtual association
 */
struct page_address_map
{
    struct page *page;
    void *virtual;
    struct list_head list;
};

該結構用於建立page-->virtual的映射(該結構由此得名).

欄位 描述
page 是一個指向全局mem_map數組中的page實例的指針
virtual 指定了該頁在內核虛擬地址空間中分配的位置

為便於組織, 映射保存在散列表中, 結構中的鏈表元素用於建立溢出鏈表,以處理散列碰撞. 該散列表通過page_address_htable數組實現, 定義在mm/highmem.c?v=4.7, line 392

static struct page_address_slot *page_slot(const struct page *page)
{
    return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

2.2 page_address函數

page_address是一個前端函數, 使用上述數據結構確定給定page實例的線性地址, 該函數定義在mm/highmem.c?v=4.7, line 408)

/**
 * page_address - get the mapped virtual address of a page
 * @page: &struct page to get the virtual address of
 *
 * Returns the page's virtual address.
 */
void *page_address(const struct page *page)
{
    unsigned long flags;
    void *ret;
    struct page_address_slot *pas;
    /*如果頁框不在高端記憶體中*/  
    if (!PageHighMem(page))
         /*線性地址總是存在,通過計算頁框下標 
            然後將其轉換成物理地址,最後根據相應的 
            /物理地址得到線性地址*/
        return lowmem_page_address(page);
    /*從page_address_htable散列表中得到pas*/  
    pas = page_slot(page);
    ret = NULL;
    spin_lock_irqsave(&pas->lock, flags);
    if (!list_empty(&pas->lh)) {{/*如果對應的鏈表不空, 
    該鏈表中存放的是page_address_map結構*/  
        struct page_address_map *pam;
        /*對每個鏈表中的元素*/
        list_for_each_entry(pam, &pas->lh, list) {
            if (pam->page == page) {
                /*返回線性地址*/ 
                ret = pam->virtual;
                goto done;
            }
        }
    }
done:
    spin_unlock_irqrestore(&pas->lock, flags);
    return ret;
}

EXPORT_SYMBOL(page_address);

page_address首先檢查傳遞進來的page實例在普通記憶體還是在高端記憶體.

  • 如果是前者(普通記憶體區域), 頁地址可以根據page在mem_map數組中的位置計算. 這個工作可以通過lowmem_page_address調用page_to_virt(page)來完成
  • 對於後者, 可通過上述散列表查找虛擬地址.

2.3 kmap創建映射

2.3.1 kmap函數

為通過page指針建立映射, 必須使用kmap函數.

不同體繫結構的定義可能不同, 但是大多數體繫結構的定義都如下所示, 比如arm上該函數定義在arch/arm/mm/highmem.c?v=4.7, line 37, 如下所示

/*高端記憶體映射,運用數組進行操作分配情況 
分配好後需要加入哈希表中;*/  
void *kmap(struct page *page)
{
    might_sleep();
    if (!PageHighMem(page)) /*如果頁框不屬於高端記憶體*/  
        return page_address(page);
    return kmap_high(page); /*頁框確實屬於高端記憶體*/  
}
EXPORT_SYMBOL(kmap);

kmap函數只是一個page_address的前端,用於確認指定的頁是否確實在高端記憶體域中. 否則, 結果返回page_address得到的地址. 如果確實在高端記憶體中, 則內核將工作委托給kmap_high

kmap_high的實現在函數mm/highmem.c?v=4.7, line 275中, 定義如下

2.3.2 kmap_high函數

/**
 * kmap_high - map a highmem page into memory
 * @page: &struct page to map
 *
 * Returns the page's virtual memory address.
 *
 * We cannot call this from interrupts, as it may block.
 */
void *kmap_high(struct page *page)
{
    unsigned long vaddr;

    /*
     * For highmem pages, we can't trust "virtual" until
     * after we have the lock.
     */
    lock_kmap();    /*保護頁表免受多處理器系統上的併發訪問*/  

    /*檢查是否已經被映射*/
    vaddr = (unsigned long)page_address(page);
    if (!vaddr) )/*  如果沒有被映射  */    
        /*把頁框的物理地址插入到pkmap_page_table的 
        一個項中併在page_address_htable散列表中加入一個 
        元素*/  
        vaddr = map_new_virtual(page);
    /*分配計數加一,此時流程都正確應該是2了*/  
    pkmap_count[PKMAP_NR(vaddr)]++;
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
    unlock_kmap();
    return (void*) vaddr;   ;/*返回地址*/ 
}

EXPORT_SYMBOL(kmap_high);

2.3.3 map_new_virtual函數

上文討論的page_address函數首先檢查該頁是否已經映射. 如果它不對應到有效地址, 則必須使用map_new_virtual映射該頁.

該函數定義在mm/highmem.c?v=4.7, line 213, 將執行下列主要的步驟.

static inline unsigned long map_new_virtual(struct page *page)
{
    unsigned long vaddr;
    int count;
    unsigned int last_pkmap_nr;
    unsigned int color = get_pkmap_color(page);

start:
    count = get_pkmap_entries_count(color);
    /* Find an empty entry */
    for (;;) {
        last_pkmap_nr = get_next_pkmap_nr(color);   /*加1,防止越界*/  
        /* 接下來判斷什麼時候last_pkmap_nr等於0,等於0就表示1023(LAST_PKMAP(1024)-1)個頁表項已經被分配了 
        ,這時候就需要調用flush_all_zero_pkmaps()函數,把所有pkmap_count[] 計數為1的頁表項在TLB裡面的entry給flush掉 
        ,並重置為0,這就表示該頁表項又可以用了,可能會有疑惑為什麼不在把pkmap_count置為1的時候也 
        就是解除映射的同時把TLB也flush呢? 
        個人感覺有可能是為了效率的問題吧,畢竟等到不夠的時候再刷新,效率要好點吧。*/  
        if (no_more_pkmaps(last_pkmap_nr, color)) {
            flush_all_zero_pkmaps();
            count = get_pkmap_entries_count(color);
        }

        if (!pkmap_count[last_pkmap_nr])
            break;  /* Found a usable entry */
        if (--count)
            continue;

        /*
         * Sleep for somebody else to unmap their entries
         */
        {
            DECLARE_WAITQUEUE(wait, current);
            wait_queue_head_t *pkmap_map_wait =
                get_pkmap_wait_queue_head(color);

            __set_current_state(TASK_UNINTERRUPTIBLE);
            add_wait_queue(pkmap_map_wait, &wait);
            unlock_kmap();
            schedule();
            remove_wait_queue(pkmap_map_wait, &wait);
            lock_kmap();

            /* Somebody else might have mapped it while we slept */
            if (page_address(page))
                return (unsigned long)page_address(page);

            /* Re-start */
            goto start;
        }
    }
    /*返回這個頁表項對應的線性地址vaddr.*/  
    vaddr = PKMAP_ADDR(last_pkmap_nr);
    /*設置頁表項*/  
    set_pte_at(&init_mm, vaddr,
           &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
    /*接下來把pkmap_count[last_pkmap_nr]置為1,1不是表示不可用嗎, 
    既然映射已經建立好了,應該賦值為2呀,其實這個操作 
    是在他的上層函數kmap_high裡面完成的(pkmap_count[PKMAP_NR(vaddr)]++).*/  
    pkmap_count[last_pkmap_nr] = 1;
    /*到此為止,整個映射就完成了,再把page和對應的線性地址 
    加入到page_address_htable哈希鏈表裡面就可以了*/  
    set_page_address(page, (void *)vaddr);

    return vaddr;
}
  1. 從最後使用的位置(保存在全局變數last_pkmap_nr中)開始,反向掃描pkmap_count數組, 直至找到一個空閑位置. 如果沒有空閑位置,該函數進入睡眠狀態,直至內核的另一部分執行解除映射操作騰出空位. 在到達pkmap_count的最大索引值時, 搜索從位置0開始. 在這種情況下, 還調用 flush_all_zero_pkmaps函數刷出CPU高速緩存(讀者稍後會看到這一點)。

  2. 修改內核的頁表,將該頁映射在指定位置。但尚未更新TLB.

  3. 新位置的使用計數器設置為1。如上所述,這意味著該頁已分配但無法使用,因為TLB項未更新.

  4. set_page_address將該頁添加到持久內核映射的數據結構。 該函數返回新映射頁的虛擬地址. 在不需要高端記憶體頁的體繫結構上(或沒有設置CONFIG_HIGHMEM),則使用通用版本的kmap返回頁的地址,且不修改虛擬記憶體

2.4 kunmap解除映射

用kmap映射的頁, 如果不再需要, 必須用kunmap解除映射. 照例, 該函數首先檢查相關的頁(由page實例標識)是否確實在高端記憶體中. 倘若如此, 則實際工作委托給mm/highmem.c中的kunmap_high, 該函數的主要任務是將pkmap_count數組中對應位置在計數器減1

該機制永遠不能將計數器值降低到小於1. 這意味著相關的頁沒有釋放。因為對使用計數器進行了額外的加1操作, 正如前文的討論, 這是為確保CPU高速緩存的正確處理.

也在上文提到的flush_all_zero_pkmaps是最終釋放映射的關鍵. 在map_new_virtual從頭開始搜索空閑位置時, 總是調用該函數.

它負責以下3個操作。

  1. flush_cache_kmaps在內核映射上執行刷出(在需要顯式刷出的大多數體繫結構上,將使用flush_cache_all刷出CPU的全部的高速緩存), 因為內核的全局頁表已經修改.

  2. 掃描整個pkmap_count數組. 計數器值為1的項設置為0,從頁表刪除相關的項, 最後刪除該映射。
  3. 最後, 使用flush_tlb_kernel_range函數刷出所有與PKMAP區域相關的TLB項.

2.4.1 kunmap函數

同kmap類似, 每個體繫結構都應該實現自己的kmap函數, 大多數體繫結構的定義都如下所示, 參見arch/arm/mm/highmem.c?v=4.7, line 46

void kunmap(struct page *page)
{
    BUG_ON(in_interrupt());
    if (!PageHighMem(page))
        return;
    kunmap_high(page);
}
EXPORT_SYMBOL(kunmap);

內核首先檢查待釋放記憶體區域是不是在高端記憶體區域

  • 如果記憶體區域在普通記憶體區, 則內核並沒有通過kmap_high對其建立持久的內核映射, 當然也無需用kunmap_high釋放
  • 如果記憶體區域在高端記憶體區, 則內核通過kunmap_high釋放該記憶體空間

2.4.2 kunmap_high函數

kunmap_high函數定義在mm/highmem.c?v=4.7, line 328

#ifdef CONFIG_HIGHMEM
/**
 * kunmap_high - unmap a highmem page into memory
 * @page: &struct page to unmap
 *
 * If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called
 * only from user context.
 */
void kunmap_high(struct page *page)
{
    unsigned long vaddr;
    unsigned long nr;
    unsigned long flags;
    int need_wakeup;
    unsigned int color = get_pkmap_color(page);
    wait_queue_head_t *pkmap_map_wait;

    lock_kmap_any(flags);
    vaddr = (unsigned long)page_address(page);
    BUG_ON(!vaddr);
    nr = PKMAP_NR(vaddr);   /*永久記憶體區域開始的第幾個頁面*/  

    /*
     * A count must never go down to zero
     * without a TLB flush!
     */
    need_wakeup = 0;
    switch (--pkmap_count[nr]) {    /*減小這個值,因為在映射的時候對其進行了加2*/  
    case 0:
        BUG();
    case 1:
        /*
         * Avoid an unnecessary wake_up() function call.
         * The common case is pkmap_count[] == 1, but
         * no waiters.
         * The tasks queued in the wait-queue are guarded
         * by both the lock in the wait-queue-head and by
         * the kmap_lock.  As the kmap_lock is held here,
         * no need for the wait-queue-head's lock.  Simply
         * test if the queue is empty.
         */
        pkmap_map_wait = get_pkmap_wait_queue_head(color);
        need_wakeup = waitqueue_active(pkmap_map_wait);
    }
    unlock_kmap_any(flags);

    /* do wake-up, if needed, race-free outside of the spin lock */
    if (need_wakeup)
        wake_up(pkmap_map_wait);
}

EXPORT_SYMBOL(kunmap_high);
#endif

3 臨時內核映射

剛纔描述的kmap函數不能用於中斷處理程式, 因為它可能進入睡眠狀態. 如果pkmap數組中沒有空閑位置, 該函數會進入睡眠狀態, 直至情形有所改善.

void *kmap_atomic(struct page *page)
{
    unsigned int idx;
    unsigned long vaddr;
    void *kmap;
    int type;

    preempt_disable();
    pagefault_disable();
    if (!PageHighMem(page))
        return page_address(page);

#ifdef CONFIG_DEBUG_HIGHMEM
    /*
     * There is no cache coherency issue when non VIVT, so force the
     * dedicated kmap usage for better debugging purposes in that case.
     */
    if (!cache_is_vivt())
        kmap = NULL;
    else
#endif
        kmap = kmap_high_get(page);
    if (kmap)
        return kmap;

    type = kmap_atomic_idx_push();

    idx = FIX_KMAP_BEGIN + type + KM_TYPE_NR * smp_processor_id();
    vaddr = __fix_to_virt(idx);
#ifdef CONFIG_DEBUG_HIGHMEM
    /*
     * With debugging enabled, kunmap_atomic forces that entry to 0.
     * Make sure it was indeed properly unmapped.
     */
    BUG_ON(!pte_none(get_fixmap_pte(vaddr)));
#endif
    /*
     * When debugging is off, kunmap_atomic leaves the previous mapping
     * in place, so the contained TLB flush ensures the TLB is updated
     * with the new mapping.
     */
    set_fixmap_pte(idx, mk_pte(page, kmap_prot));

    return (void *)vaddr;
}
EXPORT_SYMBOL(kmap_atomic);

這個函數不會被阻塞, 因此可以用在中斷上下文和起亞不能重新調度的地方. 它也禁止內核搶占, 這是有必要的, 因此映射對每個處理器都是唯一的(調度可能對哪個處理器執行哪個進程做變動).

3.2 kunmap_atomic函數

可以通過函數kunmap_atomic取消映射

/*
 * Prevent people trying to call kunmap_atomic() as if it were kunmap()
 * kunmap_atomic() should get the return value of kmap_atomic, not the page.
 */
#define kunmap_atomic(addr)                     \
do {                                \
    BUILD_BUG_ON(__same_type((addr), struct page *));       \
    __kunmap_atomic(addr);                  \
} while (0)

這個函數也不會阻塞. 在很多體繫結構中, 除非激活了內核搶占, 否則kunmap_atomic根本無事可做, 因為只有在下一個臨時映射到來前上一個臨時映射才有效. 因此, 內核完全可以”忘掉”kmap_atomic映射, kunmap_atomic也無需做什麼實際的事情. 下一個原子映射將自動覆蓋前一個映射.


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

-Advertisement-
Play Games
更多相關文章
  • 上兩節介紹完Hybrid模式在MVC下的使用,包括驗證從數據獲取的User和Claim對MVC的身份授權。本節將介紹Implicit模式在JavaScript應用程式中的使用,使用Node.js+Express構建JavaScript客戶端,實現前後端分離。本節授權服務和資源伺服器基於第四和第五節。 ...
  • 在上一篇博文《 "[UWP]在UWP平臺中使用Lottie動畫" 》中我簡單介紹了一下LottieUWP項目以及如何使用它呈現Lottie動畫,這篇文章里我們來講點進階的東西——緩存Lottie動畫幀。 為什麼會有這樣的需求呢? 有兩方面原因: 直接在XAML中使用Lottie動畫時,是邊播放邊渲染 ...
  • 最近QQ影音久違的更新了,因為記得QQ影音之前體驗還算不錯(FFmepg的事另說),我也第一時間去官網下載體驗了一下,結果發現一些有趣的事情。 是的,你沒看錯,QQ影音主界面上這個動畫效果是使用Lottie動畫實現的! 這讓我大為驚奇,我對Lottie瞭解還算是比較多的,但是Lottie常見應用於移 ...
  • " 【.NET Core項目實戰 統一認證平臺】開篇及目錄索引 " 上篇文章介紹了基於 密碼授權模式,從使用場景、原理分析、自定義帳戶體系集成完整的介紹了密碼授權模式的內容,並最後給出了三個思考問題,本篇就針對第一個思考問題詳細的講解下 是如何生成access_token的,如何驗證access_t ...
  • 很久沒有寫博客了,最近做了一個公司門戶網站的小項目,其中接觸到了一些我不會的知識點,今日事情少,便記錄一下,當時想在網上搜索相關的內容,但是沒有找到。 今天想記錄一下這樣一個小的需求的做法。先說一下我的想法:因為是一個門戶網站,所以我需要從後臺傳大量的數據到前臺,我考慮的是這樣做,用一個字典類型(d ...
  • 二分搜索用於在已經排序好的集合中搜索值,每次與中間值對比,小於則搜索前半段,大於中間值則在後半段,繼續二分搜索,實現代碼: 如果查詢不到值返回的是負的最後查詢的中間值的位置,負值變正後+1 則可用來有序插入搜索值,使列表保持排序。 ...
  • 一、前言 1、本教程主要內容 ASP.NET Core MVC 集成 EF Core 介紹&操作步驟 ASP.NET Core MVC 使用 EF Core + Linq to Entity 訪問MySQL資料庫 ASP.NET Core MVC 使用 EF Core + 原生SQL訪問MySql數 ...
  • EF相關的內容園子里已經有很多很好的文章了,這篇只是把自己之前的一些整理搬運到這裡,拋磚引玉,溫故知新。 Migrations確實是個好東西,至少就升級維護Database方面,幫助筆者脫離苦海。另一個項目中開發階段忽視了DB升級的處理方面的問題,導致每次項目上線都很難去處理DB。因為只有最新版本的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...