【TencentOS tiny】深度源碼分析(2)——調度器

来源:https://www.cnblogs.com/iot-dev/archive/2019/10/16/11688906.html
-Advertisement-
Play Games

溫馨提示:本文不描述與浮點相關的寄存器的內容,如需瞭解自行查閱(畢竟我自己也不懂) 調度器的基本概念 中提供的任務調度器是基於優先順序的全搶占式調度,在系統運行過程中,當有比當前任務優先順序更高的任務就緒時,當前任務將立刻被 ,高優先順序任務 處理器運行。 內核中也允許創建相同優先順序的任務。相同優先順序的任 ...


溫馨提示:本文不描述與浮點相關的寄存器的內容,如需瞭解自行查閱(畢竟我自己也不懂)

調度器的基本概念

TencentOS tiny中提供的任務調度器是基於優先順序的全搶占式調度,在系統運行過程中,當有比當前任務優先順序更高的任務就緒時,當前任務將立刻被切出,高優先順序任務搶占處理器運行。

TencentOS tiny內核中也允許創建相同優先順序的任務。相同優先順序的任務採用時間片輪轉方式進行調度(也就是通常說的分時調度器),時間片輪轉調度僅在當前系統中無更高優先順序就緒任務的情況下才有效。

為了保證系統的實時性,系統盡最大可能地保證高優先順序的任務得以運行。任務調度的原則是一旦任務狀態發生了改變,並且當前運行的任務優先順序小於優先順序隊列中任務最高優先順序時,立刻進行任務切換(除非當前系統處於中斷處理程式中或禁止任務切換的狀態)。

調度器是操作系統的核心,其主要功能就是實現任務的切換,即從就緒列表裡面找到優先順序最高的任務,然後去執行該任務。

啟動調度器

調度器的啟動由cpu_sched_start函數來完成,它會被tos_knl_start函數調用,這個函數中主要做兩件事,首先通過readyqueue_highest_ready_task_get函數獲取當前系統中處於最高優先順序的就緒任務,並且將它賦值給指向當前任務控制塊的指針k_curr_task,然後設置一下系統的狀態為運行態KNL_STATE_RUNNING

當然最重要的是調用彙編代碼寫的函數cpu_sched_start啟動調度器,該函數在源碼的arch\arm\arm-v7m目錄下的port_s.S彙編文件下,TencentOS tiny支持多種內核的晶元,如M3/M4/M7等,不同的晶元該函數的實現方式不同,port_s.S也是TencentOS tiny作為軟體與CPU硬體連接的橋梁。以M4的cpu_sched_start舉個例子:

__API__ k_err_t tos_knl_start(void)
{
    if (tos_knl_is_running()) {
        return K_ERR_KNL_RUNNING;
    }

    k_next_task = readyqueue_highest_ready_task_get();
    k_curr_task = k_next_task;
    k_knl_state = KNL_STATE_RUNNING;
    cpu_sched_start();

    return K_ERR_NONE;
}
port_sched_start
    CPSID   I   

    ; set pendsv priority lowest
    ; otherwise trigger pendsv in port_irq_context_switch will cause a context swich in irq
    ; that would be a disaster
    MOV32   R0, NVIC_SYSPRI14
    MOV32   R1, NVIC_PENDSV_PRI
    STRB    R1, [R0]

    LDR     R0, =SCB_VTOR
    LDR     R0, [R0]
    LDR     R0, [R0]
    MSR     MSP, R0

    ; k_curr_task = k_next_task
    MOV32   R0, k_curr_task
    MOV32   R1, k_next_task
    LDR     R2, [R1]
    STR     R2, [R0]

    ; sp = k_next_task->sp
    LDR     R0, [R2]
    ; PSP = sp
    MSR     PSP, R0

    ; using PSP
    MRS     R0, CONTROL
    ORR     R0, R0, #2
    MSR     CONTROL, R0

    ISB

    ; restore r4-11 from new process stack
    LDMFD   SP!, {R4 - R11}

    IF {FPU} != "SoftVFP"
    ; ignore EXC_RETURN the first switch
    LDMFD   SP!, {R0}
    ENDIF

    ; restore r0, r3
    LDMFD    SP!, {R0 - R3}
    ; load R12 and LR
    LDMFD    SP!, {R12, LR}
    ; load PC and discard xPSR
    LDMFD    SP!, {R1, R2}

    CPSIE    I
    BX       R1

Cortex-M內核關中斷指令

從上面的彙編代碼,我又想介紹一下Cortex-M內核關中斷指令,唉~感覺還是有點麻煩!
為了快速地開關中斷, Cortex-M內核專門設置了一條 CPS 指令,用於操作PRIMASK寄存器跟FAULTMASK寄存器的,這兩個寄存器是與屏蔽中斷有關的,除此之外Cortex-M內核還存在BASEPRI寄存器也是與中斷有關的,也順帶介紹一下吧。

CPSID I     ;PRIMASK=1     ;關中斷
CPSIE I     ;PRIMASK=0     ;開中斷
CPSID F     ;FAULTMASK=1   ;關異常
CPSIE F     ;FAULTMASK=0   ;開異常
寄存器 功能
PRIMASK 它被置 1 後,就關掉所有可屏蔽的異常,只剩下 NMI 和HardFault FAULT可以響應
FAULTMASK 當它置 1 時,只有 NMI 才能響應,所有其它的異常都無法響應(包括HardFault FAULT)
BASEPRI 這個寄存器最多有 9 位(由表達優先順序的位數決定)。它定義了被屏蔽優先順序的閾值。當它被設成某個值後,所有優先順序號大於等於此值的中斷都被關(優先順序號越大,優先順序越低)。但若被設成 0,則不關閉任何中斷

更多具體的描述看我以前的文章:RTOS臨界段知識:https://blog.csdn.net/jiejiemcu/article/details/82534974

回歸正題

在啟動內核調度器過程中需要配置PendSV 的中斷優先順序為最低,就是往NVIC_SYSPRI14(0xE000ED22)地址寫入NVIC_PENDSV_PRI(0xFF)。因為PendSV都會涉及到系統調度,系統調度的優先順序要低於系統的其它硬體中斷優先順序,即優先響應系統中的外部硬體中斷,所以PendSV的中斷優先順序要配置為最低,不然很可能在中斷上下文中產生任務調度。

PendSV 異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理後才放行。為實現這個機制,需要把 PendSV 編程為最低優先順序的異常。如果 OS 檢測到某 ISR 正在活動,它將懸起一個 PendSV 異常,以便緩期執行上下文切換。也就是說,只要將PendSV的優先順序設為最低的,systick即使是打斷了IRQ,它也不會馬上進行上下文切換,而是等到ISR執行完,PendSV 服務常式才開始執行,並且在裡面執行上下文切換。過程如圖所示:

然後獲取MSP主棧指針的地址,在Cortex-M中,0xE000ED08SCB_VTOR寄存器的地址,裡面存放的是向量表的起始地址。

載入k_next_task指向的任務控制塊到 R2,從上一篇文章可知任務控制塊的第一個成員就是棧頂指針,所以此時R2等於棧頂指針。

ps : 在調度器啟動時,k_next_taskk_curr_task是一樣的(k_curr_task = k_next_task

載入R2R0,然後將棧頂指針R0更新到psp,任務執行的時候使用的棧指針是psp

ps:sp指針有兩個,分別為pspmsp。(可以簡單理解為:在任務上下文環境中使用psp,在中斷上下文環境使用msp,也不一定是正確的,這是我個人的理解)

R0為基地址,將棧中向上增長的8個字的內容載入到CPU寄存器R4~R11,同時R0也會跟著自增

接著需要載入R0 ~ R3、R12以及LR、 PC、xPSR到CPU寄存器組,PC指針指向的是即將要運行的線程,而LR寄存器則指向任務的退出。因為這是第一次啟動任務,要全部手動把任務棧上的寄存器彈到硬體里,才能進入第一個任務的上下文,因為一開始並沒有第一個任務運行的上下文環境,而在進入PendSV的時候需要上文保存,所以需要手動創造任務上下文環境(將這些寄存器載入到CPU寄存器組中),第一次的時候此彙編入口函數,sp是指向一個選好的任務的棧頂(k_curr_task)。

看看任務棧的初始化

從上面的瞭解,再來看看任務棧的初始化,可能會有更深一點的印象。主要瞭解以下幾點即可:

  • 獲取棧頂指針為stk_base[stk_size]高地址,Cortex-M內核的棧是向下增長的。
  • R0、R1、R2、R3、R12、R14、R15和xPSR的位24是會被CPU自動載入與保存的。
  • xPSR的bit24必須置1,即0x01000000。
  • entry是任務的入口地址,即PC
  • R14 (LR)是任務的退出地址,所以任務一般是死迴圈而不會return
  • R0: arg是任務主體的形參
  • 初始化棧時sp指針會自減
__KERNEL__ k_stack_t *cpu_task_stk_init(void *entry,
                                              void *arg,
                                              void *exit,
                                              k_stack_t *stk_base,
                                              size_t stk_size)
{
    cpu_data_t *sp;

    sp = (cpu_data_t *)&stk_base[stk_size];
    sp = (cpu_data_t *)((cpu_addr_t)(sp) & 0xFFFFFFF8);

    /* auto-saved on exception(pendSV) by hardware */
    *--sp = (cpu_data_t)0x01000000u;    /* xPSR     */
    *--sp = (cpu_data_t)entry;          /* entry    */
    *--sp = (cpu_data_t)exit;           /* R14 (LR) */
    *--sp = (cpu_data_t)0x12121212u;    /* R12      */
    *--sp = (cpu_data_t)0x03030303u;    /* R3       */
    *--sp = (cpu_data_t)0x02020202u;    /* R2       */
    *--sp = (cpu_data_t)0x01010101u;    /* R1       */
    *--sp = (cpu_data_t)arg;            /* R0: arg  */

    /* Remaining registers saved on process stack */
    /* EXC_RETURN = 0xFFFFFFFDL
       Initial state: Thread mode +  non-floating-point state + PSP
       31 - 28 : EXC_RETURN flag, 0xF
       27 -  5 : reserved, 0xFFFFFE
       4       : 1, basic stack frame; 0, extended stack frame
       3       : 1, return to Thread mode; 0, return to Handler mode
       2       : 1, return to PSP; 0, return to MSP
       1       : reserved, 0
       0       : reserved, 1
     */
#if defined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)
    *--sp = (cpu_data_t)0xFFFFFFFDL;
#endif

    *--sp = (cpu_data_t)0x11111111u;    /* R11      */
    *--sp = (cpu_data_t)0x10101010u;    /* R10      */
    *--sp = (cpu_data_t)0x09090909u;    /* R9       */
    *--sp = (cpu_data_t)0x08080808u;    /* R8       */
    *--sp = (cpu_data_t)0x07070707u;    /* R7       */
    *--sp = (cpu_data_t)0x06060606u;    /* R6       */
    *--sp = (cpu_data_t)0x05050505u;    /* R5       */
    *--sp = (cpu_data_t)0x04040404u;    /* R4       */

    return (k_stack_t *)sp;
}

查找最高優先順序任務

一個操作系統如果只是具備了高優先順序任務能夠立即獲得處理器並得到執行的特點,那麼它仍然不算是實時操作系統。因為這個查找最高優先順序任務的過程決定了調度時間是否具有確定性,可以簡單來說可以使用時間複雜度來描述一下吧,如果系統查找最高優先順序任務的時間是O(N),那麼這個時間會隨著任務個數的增加而增大,這是不可取的,TencentOS tiny的時間複雜度是O(1),它提供兩種方法查找最高優先順序任務,通過TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT巨集定義決定。

  1. 第一種是使用普通方法,根據就緒列表中k_rdyq.prio_mask[]的變數判斷對應的位是否被置1。
  2. 第二種方法則是特殊方法,利用計算前導零指令CLZ,直接在k_rdyq.prio_mask[]這個32位的變數中直接得出最高優先順序所處的位置,這種方法比普通方法更快捷,但受限於平臺(需要硬體前導零指令,在STM32中我們就可以使用這種方法)。

實現過程如下,建議看一看readyqueue_prio_highest_get函數,他的實現還是非常精妙的~

__STATIC__ k_prio_t readyqueue_prio_highest_get(void)
{
    uint32_t *tbl;
    k_prio_t prio;

    prio    = 0;
    tbl     = &k_rdyq.prio_mask[0];

    while (*tbl == 0) {
        prio += K_PRIO_TBL_SLOT_SIZE;
        ++tbl;
    }
    prio += tos_cpu_clz(*tbl);
    return prio;
}
__API__ uint32_t tos_cpu_clz(uint32_t val)
{
#if defined(TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT) && (TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT == 0u)
    uint32_t nbr_lead_zeros = 0;

    if (!(val & 0XFFFF0000)) {
        val <<= 16;
        nbr_lead_zeros += 16;
    }

    if (!(val & 0XFF000000)) {
        val <<= 8;
        nbr_lead_zeros += 8;
    }

    if (!(val & 0XF0000000)) {
        val <<= 4;
        nbr_lead_zeros += 4;
    }

    if (!(val & 0XC0000000)) {
        val <<= 2;
        nbr_lead_zeros += 2;
    }

    if (!(val & 0X80000000)) {
        nbr_lead_zeros += 1;
    }

    if (!val) {
        nbr_lead_zeros += 1;
    }

    return (nbr_lead_zeros);
#else
    return port_clz(val);
#endif
}

任務切換的實現

從前面我們也知道,任務切換是在PendSV中斷中進行的,這個中斷中實現的內容總結成一句精髓的話就是 上文保存,下文切換,直接看源代碼:

PendSV_Handler
    CPSID   I
    MRS     R0, PSP

_context_save
    ; R0-R3, R12, LR, PC, xPSR is saved automatically here
    IF {FPU} != "SoftVFP"
    ; is it extended frame?
    TST     LR, #0x10
    IT      EQ
    VSTMDBEQ  R0!, {S16 - S31}
    ; S0 - S16, FPSCR saved automatically here

    ; save EXC_RETURN
    STMFD   R0!, {LR}
    ENDIF

    ; save remaining regs r4-11 on process stack
    STMFD   R0!, {R4 - R11}

    ; k_curr_task->sp = PSP
    MOV32   R5, k_curr_task
    LDR     R6, [R5]
    ; R0 is SP of process being switched out
    STR     R0, [R6]

_context_restore
    ; k_curr_task = k_next_task
    MOV32   R1, k_next_task
    LDR     R2, [R1]
    STR     R2, [R5]

    ; R0 = k_next_task->sp
    LDR     R0, [R2]

    ; restore R4 - R11
    LDMFD   R0!, {R4 - R11}

    IF {FPU} != "SoftVFP"
    ; restore EXC_RETURN
    LDMFD   R0!, {LR}
    ; is it extended frame?
    TST     LR, #0x10
    IT      EQ
    VLDMIAEQ    R0!, {S16 - S31}
    ENDIF

    ; Load PSP with new process SP
    MSR     PSP, R0
    CPSIE   I
    ; R0-R3, R12, LR, PC, xPSR restored automatically here
    ; S0 - S16, FPSCR restored automatically here if FPCA = 1
    BX      LR

    ALIGN
    END

PSP的值存儲到R0。當進入PendSVC_Handler時,上一個任務運行的環境即: xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0這些CPU寄存器的值會自動存儲到任務的棧中,此時psp指針已經被自動更新。而剩下的r4~r11需要手動保存,這也是為啥要在PendSVC_Handler中保存上文(_context_save)的原因,主要是載入CPU中不能自動保存的寄存器,將其壓入任務棧中。

接著找到下一個要運行的任務k_next_task,將它的任務棧頂載入到R0,然後手動將新任務棧中的內容(此處是指R4~R11)載入到CPU寄存器組中,這就是下文切換,當然還有一些其他沒法自動保存的內容也是需要手動載入到CPU寄存器組的。手動載入完後,此時R0已經被更新了,更新psp的值,在退出PendSVC_Handler中斷時,會以psp作為基地址,將任務棧中剩下的內容(xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0)自動載入到CPU寄存器。

其實在異常發生時,R14中保存異常返回標誌,包括返回後進入任務模式還是處理器模式、使用PSP堆棧指針還是MSP堆棧指針。此時的r14等於0xfffffffd,最表示異常返回後進入任務模式(畢竟PendSVC_Handler優先順序是最低的,會返回到任務中),SP以PSP作為堆棧指針出棧,出棧完畢後PSP指向任務棧的棧頂。當調用 BX R14指令後,系統以PSP作為SP指針出棧,把接下來要運行的新任務的任務棧中剩下的內容載入到CPU寄存器:R0、R1、R2、R3、R12、R14(LR)、R15(PC)和xPSR,從而切換到新的任務。

SysTick

SysTick初始化

systick是系統的時基,而且它是內核時鐘,只要是M0/M3/M4/M7內核它都會存在systick時鐘,並且它是可以被編程配置的,這就對操作系統的移植提供極大的方便。
TencentOS tiny會在cpu_init函數中將systick進行初始化,即調用cpu_systick_init函數,這樣子就不需要用戶自行去編寫systick初始化相關的代碼。

__KERNEL__ void cpu_init(void)
{
    k_cpu_cycle_per_tick = TOS_CFG_CPU_CLOCK / k_cpu_tick_per_second;
    cpu_systick_init(k_cpu_cycle_per_tick);

#if (TOS_CFG_CPU_HRTIMER_EN > 0)
    tos_cpu_hrtimer_init();
#endif
}
__KERNEL__ void cpu_systick_init(k_cycle_t cycle_per_tick)
{
    port_systick_priority_set(TOS_CFG_CPU_SYSTICK_PRIO);
    port_systick_config(cycle_per_tick);
}

SysTick中斷

SysTick中斷服務函數是需要我們自己編寫的,要在裡面調用一下TencentOS tiny相關的函數,更新系統時基以驅動系統的運行,SysTick_Handler函數的移植如下:

void SysTick_Handler(void)
{
  HAL_IncTick();
  if (tos_knl_is_running())
  {
    tos_knl_irq_enter();
    
    tos_tick_handler();
    
    tos_knl_irq_leave();
  }
}

主要是需要調用tos_tick_handler函數將系統時基更新,具體見:

__API__ void tos_tick_handler(void)
{
    if (unlikely(!tos_knl_is_running())) {
        return;
    }

    tick_update((k_tick_t)1u);

#if TOS_CFG_TIMER_EN > 0u && TOS_CFG_TIMER_AS_PROC > 0u
    timer_update();
#endif

#if TOS_CFG_ROUND_ROBIN_EN > 0u
    robin_sched(k_curr_task->prio);
#endif
}

不得不說TencentOS tiny源碼的實現非常簡單,我非常喜歡,在tos_tick_handler中,首先判斷一下系統是否已經開始運行,如果沒有運行將直接返回,如果已經運行了,那就調用tick_update函數更新系統時基,如果使能了TOS_CFG_TIMER_EN 巨集定義表示使用軟體定時器,則需要更新相應的處理,此處暫且不提及。如果使能了TOS_CFG_ROUND_ROBIN_EN巨集定義,還需要更新時間片相關變數,稍後講解。

__KERNEL__ void tick_update(k_tick_t tick)
{
    TOS_CPU_CPSR_ALLOC();
    k_task_t *first, *task;
    k_list_t *curr, *next;

    TOS_CPU_INT_DISABLE();
    k_tick_count += tick;

    if (tos_list_empty(&k_tick_list)) {
        TOS_CPU_INT_ENABLE();
        return;
    }

    first = TOS_LIST_FIRST_ENTRY(&k_tick_list, k_task_t, tick_list);
    if (first->tick_expires <= tick) {
        first->tick_expires = (k_tick_t)0u;
    } else {
        first->tick_expires -= tick;
        TOS_CPU_INT_ENABLE();
        return;
    }

    TOS_LIST_FOR_EACH_SAFE(curr, next, &k_tick_list) {
        task = TOS_LIST_ENTRY(curr, k_task_t, tick_list);
        if (task->tick_expires > (k_tick_t)0u) {
            break;
        }

        // we are pending on something, but tick's up, no longer waitting
        pend_task_wakeup(task, PEND_STATE_TIMEOUT);
    }

    TOS_CPU_INT_ENABLE();
}

tick_update函數的主要功能就是將k_tick_count +1,並且判斷一下時基列表k_tick_list(也可以成為延時列表吧)的任務是否超時,如果超時則喚醒該任務,否則就直接退出即可。關於時間片的調度也是非常簡單,將任務的剩餘時間片變數timeslice減一,然後當變數減到0時,將該變數進行重裝載timeslice_reload,然後切換任務knl_sched(),其實現過程如下:

__KERNEL__ void robin_sched(k_prio_t prio)
{
    TOS_CPU_CPSR_ALLOC();
    k_task_t *task;

    if (k_robin_state != TOS_ROBIN_STATE_ENABLED) {
        return;
    }

    TOS_CPU_INT_DISABLE();

    task = readyqueue_first_task_get(prio);
    if (!task || knl_is_idle(task)) {
        TOS_CPU_INT_ENABLE();
        return;
    }

    if (readyqueue_is_prio_onlyone(prio)) {
        TOS_CPU_INT_ENABLE();
        return;
    }

    if (knl_is_sched_locked()) {
        TOS_CPU_INT_ENABLE();
        return;
    }

    if (task->timeslice > (k_timeslice_t)0u) {
        --task->timeslice;
    }

    if (task->timeslice > (k_timeslice_t)0u) {
        TOS_CPU_INT_ENABLE();
        return;
    }

    readyqueue_move_head_to_tail(k_curr_task->prio);

    task = readyqueue_first_task_get(prio);
    if (task->timeslice_reload == (k_timeslice_t)0u) {
        task->timeslice = k_robin_default_timeslice;
    } else {
        task->timeslice = task->timeslice_reload;
    }

    TOS_CPU_INT_ENABLE();
    knl_sched();
}

喜歡就關註我吧!

歡迎關註我公眾號

相關代碼可以在公眾號後臺獲取。
更多資料歡迎關註“物聯網IoT開發”公眾號!


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

-Advertisement-
Play Games
更多相關文章
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 本地所有文件: git rm --cached readme1.txt 刪除readme1.txt的跟蹤,並保留在本地。 git rm --f readme1.txt 刪除readme1.txt的跟蹤,並且刪除本地文件。 文件夾: 如果是對所有文件都取消跟蹤的話,就是 git rm -r --cac ...
  • 軟體定時器的基本概念 TencentOS tiny 的軟體定時器是由操作系統提供的一類 ,它構建在硬體定時器基礎之上,使系統能夠提供不受硬體定時器資源限制的定時器服務,本質上軟體定時器的使用相當於擴展了定時器的數量,允許創建更多的定時業務,它實現的功能與硬體定時器也是類似的。 硬體定時器是晶元本身提 ...
  • 引言 大家在裸機編程中很可能經常用到 這種變數,用來標誌一下某個事件的發生,然後在迴圈中判斷這些標誌是否發生,如果是等待多個事件的話,還可能會 這樣子做判斷。當然,如果聰明一點的同學就會拿 的`某些位 A`事件,第二位表示 事件,當這兩個事件都發生的時候,就判斷 的值是多少,從而判斷出哪個事件發生了 ...
  • 互斥鎖 互斥鎖又稱互斥互斥鎖,是一種特殊的信號量,它和信號量不同的是,它具有 等特性,在操作系統中常用於對臨界資源的 處理。在任意時刻互斥鎖的狀態只有兩種, ,當互斥鎖被任務持有時,該互斥鎖處於閉鎖狀態,當該任務釋放互斥鎖時,該互斥鎖處於開鎖狀態。 一個任務持有互斥鎖就表示它擁有互斥鎖的所有權,只有 ...
  • 信號量 信號量( )在操作系統中是一種實現系統中任務與任務、任務與中斷間同步或者臨界資源互斥保護的機制。在多任務系統中,各任務之間常需要同步或互斥,信號量就可以為用戶提供這方面的支持。 抽象來說,信號量是一個非負整數,每當信號量被獲取( )時,該整數會減一,當該整數的值為 時,表示信號量處於無效狀態 ...
  • 消息隊列 在前一篇文章中 "【TencentOS tiny學習】源碼分析(3)——隊列" 我們描述了TencentOS tiny的隊列實現,同時也點出了TencentOS tiny的隊列是依賴於消息隊列的,那麼我們今天來看看消息隊列的實現。 其實消息隊列是TencentOS tiny的一個 基礎組件 ...
  • 隊列基本概念 隊列是一種常用於任務間通信的數據結構,隊列可以在 傳遞消息,實現了任務接收來自其他任務或中斷的不固定長度的消息,任務能夠從隊列裡面讀取消息,當隊列中的消息是空時,讀取消息的任務將被阻塞,用戶還可以指定任務等待消息的時間 ,在這段時間中,如果隊列為空,該任務將 狀態以等待隊列數據有效。當 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...