FreeRTOS

 riscv 通用寄存器

CPU中包含32个通用寄存器,有时候也会被称为通用寄存器文件,如图1所示。通用寄存器的命名方式为X0-X31。其中第一个寄存器X0的值,被硬连线到0,因此值永远是0。其他寄存器X1-X31都是可读可写的。0-31也叫做索引号,索引号也可以理解为寄存器的地址,当指令需要调用通用寄存器时可以通过索引号查找。 对于32位系统,所有通用寄存器的宽度都是32bit,寄存器总个数也是32个。

PC(program counter)是程序计数器,也是一个寄存器。在CPU中PC寄存器并不和上述32个通用寄存器在一起,寄存器文件中不包含PC。PC的宽度和通用寄存器的宽度一样。XLEN的值一般跟RISC-V CPU架构有关系。 如果是32位架构的CPU,那么XLEN的值就是32。图1中XLEN-1 = 32-1 =31,即在一个通用寄存器中的最高位为31。在64位CPU中通用寄存器的宽度是64,同时PC宽度也是64位,最高位为64-1 =63。

 

 寄存器别名

 

 riscv没有栈基址寄存器

发生函数调用用:sp-size

函数返回:  sp+-size

复制代码
#include <stdio.h>

void swap(int *a, int *b){
    int c = *a;
    *a = *b;
    *b = c;
}


int main()
{
    int a= 101;
    int b= 202;
    swap(&a, &b);
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    return 0;
}
复制代码

 

对应的汇编代码如下(删除了print,栈溢出检查等无关部分):

 

复制代码
void swap(int *a, int *b){
    103e:   7179                    addi    sp,sp,-48       // 继续给栈开辟48字节的空间
    1040:   f406                    sd  ra,40(sp)           // 将ra保存至栈中
    1042:   f022                    sd  s0,32(sp)           // 将s0保存至栈中
    1044:   1800                    addi    s0,sp,48        // s0此时只想栈的高地址
    1046:   fea43423                sd  a0,-24(s0)          // 将a0寄存器的地址,到s0-16 ~ s0-24中
    104a:   feb43023                sd  a1,-32(s0)          // 将a1寄存器的地址,保存到s0-24 ~ s0-32中   
    int c = *a;
    104e:   fe843503                ld  a0,-24(s0)          // 从s0-16 ~ s0-24中读取八个字节到a0中,该地址为a0传入的地址,地址中存储的值为101
    1052:   4108                    lw  a0,0(a0)            // 将a0地址中的值加载到a0寄存器中,此时a0为101
    1054:   fca42e23                sw  a0,-36(s0)          // 将a0的值加载到s0-32 ~ s0-36中,此处存入的为一个值
    *a = *b;
    1058:   fe043503                ld  a0,-32(s0)          // 将s0-24 ~ s0-32的地址读取到a0中
    105c:   4108                    lw  a0,0(a0)            // 将a0的值加载给a0,此时a0为202
    105e:   fe843583                ld  a1,-24(s0)          // 将s0-16 ~ s0-24的八字节地址给a1
    1062:   c188                    sw  a0,0(a1)            // 将a0的值加载到a1的地址中,此时a1的地址,即s0-16 ~ s0-24中的值为202
    *b = c;
    1064:   fdc42503                lw  a0,-36(s0)          // 将s0-32 ~ s0-36存的值加载给a0,此处的值为101
    1068:   fe043583                ld  a1,-32(s0)          // 将s0-24 ~ s0-32的八字节地址读取到a1中
    106c:   c188                    sw  a0,0(a1)            // 将a0的值读取到a1中,原来s0-24 ~ s0-32的地址的值变成了101

}
    106e:   7402                    ld  s0,32(sp)           // 恢复s0
    1070:   70a2                    ld  ra,40(sp)           // 恢复ra
    1072:   6145                    addi    sp,sp,48        // 恢复sp指针
    1074:   8082                    ret                     // 退出

0000000000001076 <main>:


int main()
{
    1076:   7139                    addi    sp,sp,-64       // 给栈开辟64字节的,此处sp为栈指针
    1078:   fc06                    sd  ra,56(sp)           // 将返回地址ra保存到栈中
    107a:   f822                    sd  s0,48(sp)           // 将s0(fp)帧指针保存到栈中
    107c:   0080                    addi    s0,sp,64        // sp指向的是栈底,s0此时的值为sp+64的值,也就是基地址

000000000000107e <.LBB1_3>:
    107e:   00001517                auipc   a0,0x1          // 将0x1取31位到12位,然后左移12位+pc的地址,结果写入到a0寄存器
    1082:   19a53503                ld  a0,410(a0) # 2218 <__stack_chk_guard@LIBC>  // 将a0偏移410字节的值给a0,此步骤是为了防止堆栈溢出添加的检测保护       
    1086:   610c                    ld  a1,0(a0)            // 将a0的八字节赋给a1
    1088:   feb43423                sd  a1,-24(s0)          // 将a1寄存器的值保存到栈中,保存至s0-16 ~ s0-24的位置
    108c:   4581                    li  a1,0                // 将a1寄存器赋值为0,此处的作用为初始化一个寄存器为0
    108e:   fcb42e23                sw  a1,-36(s0)          // 将a1寄存器的值保存到栈中表示的地址(s0-32 ~ s0-36的位置)
    1092:   06500593                li  a1,101              // 将a1寄存器赋值为101
    int a= 101; 
    1096:   feb42223                sw  a1,-28(s0)          // 由于int在类型为四个字节,此时将101存储到s0-24 ~ s0-28所表示的地址中
    109a:   0ca00593                li  a1,202              // 还是将a0寄存器进行操作,赋值为202
    int b= 202;
    109e:   feb42023                sw  a1,-32(s0)          // 将202存储到s0-28 ~ s0-32的地址中
    10a2:   fe440593                addi    a1,s0,-28       // 将s0-28的地址给到a1
    10a6:   fe040613                addi    a2,s0,-32       // 将s0-32中地址给到a2
    swap(&a, &b);
    10aa:   fca43823                sd  a0,-48(s0)          // 将a0的地址保存到s0-40 ~ s0-48中
    10ae:   852e                    mv  a0,a1               // 将a1复制给a0
    10b0:   85b2                    mv  a1,a2               // 将a2复制给a1
    10b2:   00000097                auipc   ra,0x0          
    10b6:   f8c080e7                jalr    -116(ra) # 103e <swap> // 跳转到swap函数中执行
    printf("a= %d\n", a);
    10ba:   fe442583                lw  a1,-28(s0)          //将s0-24 ~ s0-28的地址存入到a1寄存器中

00000000000010be <.LBB1_4>:
    10be:   fffff517                auipc   a0,0xfffff
    10c2:   3da50513                addi    a0,a0,986 # 498 <abitag+0x1d8>
    10c6:   00000097                auipc   ra,0x0
    10ca:   07a080e7                jalr    122(ra) # 1140 <printf@plt> // 跳转到printf中实现打印
    printf("b= %d\n", b);
    10ce:   fe042583                lw  a1,-32(s0)

00000000000010d2 <.LBB1_5>:
    10d2:   fffff617                auipc   a2,0xfffff
    10d6:   3cd60613                addi    a2,a2,973 # 49f <abitag+0x1df>
    10da:   fca43423                sd  a0,-56(s0)
    10de:   8532                    mv  a0,a2
    10e0:   00000097                auipc   ra,0x0
    10e4:   060080e7                jalr    96(ra) # 1140 <printf@plt>
    10e8:   fd043583                ld  a1,-48(s0)          // 将s0-40 ~ s0-48的地址赋值给a1,之前存入的是a0的地址
    10ec:   6190                    ld  a2,0(a1)            // 将a1的地址复制给a2
    10ee:   fe843683                ld  a3,-24(s0)          // 将s0-16 ~ s0-24的八字节赋给a3,也就是之前存入的a0的地址
    10f2:   00d61963                bne a2,a3,1104 <.LBB1_2>    //判断此处的栈是否溢出了
    10f6:   0040006f                j   10fa <.LBB1_1>

00000000000010fa <.LBB1_1>:
    10fa:   4501                    li  a0,0                // 将a0寄存器赋值为0
    return 0;
    10fc:   7442                    ld  s0,48(sp)           // 恢复s0的值
    10fe:   70e2                    ld  ra,56(sp)           // 恢复ra的值
    1100:   6121                    addi    sp,sp,64        // 恢复sp的值
    1102:   8082                    ret                     // 整个函数退出
复制代码

 

mtime的初始化

#define configCLINT_BASE_ADDRESS                CLINT_CTRL_ADDR
#define configMTIME_BASE_ADDRESS                (CLINT_CTRL_ADDR + CLINT_MTIME)
#define configMTIMECMP_BASE_ADDRESS             (CLINT_CTRL_ADDR + CLINT_MTIMECMP)

 

 

在使用时基之前要先定义,而这个初始化应该是在FreeRTOS的任务调度之前完成,而实际,这件事做的确实很晚,是在vTaskStartScheduler();函数里执行的,具体的调用路径:

复制代码
vTaskStartScheduler();        //./FreeRTOS/Source/task.c
    |    xPortStartScheduler();        //./FreeRTOS/Source/portable/GCC/RISC-V/port.c
            |    vPortSetupTimerInterrupt();        //./FreeRTOS/Source/portable/GCC/RISC-V/port.c

#if( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 )

    void vPortSetupTimerInterrupt( void )
    {
    uint32_t ulCurrentTimeHigh, ulCurrentTimeLow;
    volatile uint32_t * const pulTimeHigh = ( volatile uint32_t * const ) ( ( configMTIME_BASE_ADDRESS ) + 4UL ); /* 8-byte typer so high 32-bit word is 4 bytes up. */
    volatile uint32_t * const pulTimeLow = ( volatile uint32_t * const ) ( configMTIME_BASE_ADDRESS );
    volatile uint32_t ulHartId;

        __asm volatile( "csrr %0, mhartid" : "=r"( ulHartId ) );
        pullMachineTimerCompareRegister  = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) );

        do
        {
            ulCurrentTimeHigh = *pulTimeHigh;
            ulCurrentTimeLow = *pulTimeLow;
        } while( ulCurrentTimeHigh != *pulTimeHigh );

        ullNextTime = ( uint64_t ) ulCurrentTimeHigh;
        ullNextTime <<= 32ULL; /* High 4-byte word is 32-bits up. */
        ullNextTime |= ( uint64_t ) ulCurrentTimeLow;
        ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;
        *pullMachineTimerCompareRegister = ullNextTime;

        /* Prepare the time to use after the next tick interrupt. */
        ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;
    }

#endif /* ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIME_BASE_ADDRESS != 0 ) */
复制代码

 

task管理

portable/GCC/RISC-V/portASM.S

复制代码
.global xPortStartFirstTask
.global freertos_risc_v_trap_handler
.global pxPortInitialiseStack
.extern pxCurrentTCB
.extern ulPortTrapHandler
.extern vTaskSwitchContext
.extern xTaskIncrementTick
.extern Timer_IRQHandler
.extern pullMachineTimerCompareRegister
.extern pullNextTime
.extern uxTimerIncrementsForOneTick /* size_t type so 32-bit on 32-bit core and 64-bits on 64-bit core. */
.extern xISRStackTop
.extern portasmHANDLE_INTERRUPT
复制代码

 

 

 

xPortStartFirstTask

portable/IAR/RISC-V/chip_specific_extensions/RV32I_CLINT_no_extensions/freertos_risc_v_chip_specific_extensions.h:57:

#define portasmHAS_SIFIVE_CLINT 1

 

复制代码
 1 #if( portasmHAS_SIFIVE_CLINT != 0 )
 2         /* If there is a clint then interrupts can branch directly to the FreeRTOS
 3         trap handler.  Otherwise the interrupt controller will need to be configured
 4         outside of this file. */
 5         la t0, freertos_risc_v_trap_handler
 6         csrw mtvec, t0
 7 #endif /* portasmHAS_CLILNT */
 8 
 9         load_x  sp, pxCurrentTCB                        /* Load pxCurrentTCB. */
10         load_x  sp, 0( sp )                                     /* Read sp from first TCB member. */
11 
12         load_x  x1, 0( sp ) /* Note for starting the scheduler the exception return address is used as the function return address. */
13 
14         portasmRESTORE_ADDITIONAL_REGISTERS     /* Defined in freertos_risc_v_chip_specific_extensions.h to restore any registers unique to the RISC-V implementation. */
15 
16         load_x  x6, 3 * portWORD_SIZE( sp )             /* t1 */
17         load_x  x7, 4 * portWORD_SIZE( sp )             /* t2 */
18         load_x  x8, 5 * portWORD_SIZE( sp )             /* s0/fp */
19         load_x  x9, 6 * portWORD_SIZE( sp )             /* s1 */
20         load_x  x10, 7 * portWORD_SIZE( sp )    /* a0 */
21         load_x  x11, 8 * portWORD_SIZE( sp )    /* a1 */
22         load_x  x12, 9 * portWORD_SIZE( sp )    /* a2 */
23         load_x  x13, 10 * portWORD_SIZE( sp )   /* a3 */
24         load_x  x14, 11 * portWORD_SIZE( sp )   /* a4 */
25         load_x  x15, 12 * portWORD_SIZE( sp )   /* a5 */
26         load_x  x16, 13 * portWORD_SIZE( sp )   /* a6 */
27         load_x  x17, 14 * portWORD_SIZE( sp )   /* a7 */
28         load_x  x18, 15 * portWORD_SIZE( sp )   /* s2 */
29         load_x  x19, 16 * portWORD_SIZE( sp )   /* s3 */
30         load_x  x20, 17 * portWORD_SIZE( sp )   /* s4 */
31         load_x  x21, 18 * portWORD_SIZE( sp )   /* s5 */
32         load_x  x22, 19 * portWORD_SIZE( sp )   /* s6 */
33         load_x  x23, 20 * portWORD_SIZE( sp )   /* s7 */
34         load_x  x24, 21 * portWORD_SIZE( sp )   /* s8 */
35         load_x  x25, 22 * portWORD_SIZE( sp )   /* s9 */
36         load_x  x26, 23 * portWORD_SIZE( sp )   /* s10 */
37         load_x  x27, 24 * portWORD_SIZE( sp )   /* s11 */
38         load_x  x28, 25 * portWORD_SIZE( sp )   /* t3 */
39         load_x  x29, 26 * portWORD_SIZE( sp )   /* t4 */
40         load_x  x30, 27 * portWORD_SIZE( sp )   /* t5 */
41         load_x  x31, 28 * portWORD_SIZE( sp )   /* t6 */
42 
43         load_x  x5, 29 * portWORD_SIZE( sp )    /* Initial mstatus into x5 (t0) */
44         addi x5, x5, 0x08                                               /* Set MIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */
45         csrrw  x0, mstatus, x5                                  /* Interrupts enabled from here! */
46         load_x  x5, 2 * portWORD_SIZE( sp )             /* Initial x5 (t0) value. */
47 
48         addi    sp, sp, portCONTEXT_SIZE
49         ret
50         .endfunc
51 /*-----------------------------------------------------------*/
复制代码

 

第8~9行,将mtvec的值设置为freertos_risc_v_trap_handler()函数的地址,即中断(异常)入口函数为freertos_risc_v_trap_handler()。

后面的代码会将当前TCB里保存的寄存器值恢复到对应的寄存器,当xPortStartFirstTask()函数返回后就会执行当前(pxCurrentTCB所指的)任务。

到这里,我们就可以知道接下来的重点就是freertos_risc_v_trap_handler()函

 

freertos_risc_v_trap_handle

复制代码
  1 .func
  2 freertos_risc_v_trap_handler:
  3     addi sp, sp, -portCONTEXT_SIZE
  4     store_x x1, 1 * portWORD_SIZE( sp )
  5     store_x x5, 2 * portWORD_SIZE( sp )
  6     store_x x6, 3 * portWORD_SIZE( sp )
  7     store_x x7, 4 * portWORD_SIZE( sp )
  8     store_x x8, 5 * portWORD_SIZE( sp )
  9     store_x x9, 6 * portWORD_SIZE( sp )
 10     store_x x10, 7 * portWORD_SIZE( sp )
 11     store_x x11, 8 * portWORD_SIZE( sp )
 12     store_x x12, 9 * portWORD_SIZE( sp )
 13     store_x x13, 10 * portWORD_SIZE( sp )
 14     store_x x14, 11 * portWORD_SIZE( sp )
 15     store_x x15, 12 * portWORD_SIZE( sp )
 16     store_x x16, 13 * portWORD_SIZE( sp )
 17     store_x x17, 14 * portWORD_SIZE( sp )
 18     store_x x18, 15 * portWORD_SIZE( sp )
 19     store_x x19, 16 * portWORD_SIZE( sp )
 20     store_x x20, 17 * portWORD_SIZE( sp )
 21     store_x x21, 18 * portWORD_SIZE( sp )
 22     store_x x22, 19 * portWORD_SIZE( sp )
 23     store_x x23, 20 * portWORD_SIZE( sp )
 24     store_x x24, 21 * portWORD_SIZE( sp )
 25     store_x x25, 22 * portWORD_SIZE( sp )
 26     store_x x26, 23 * portWORD_SIZE( sp )
 27     store_x x27, 24 * portWORD_SIZE( sp )
 28     store_x x28, 25 * portWORD_SIZE( sp )
 29     store_x x29, 26 * portWORD_SIZE( sp )
 30     store_x x30, 27 * portWORD_SIZE( sp )
 31     store_x x31, 28 * portWORD_SIZE( sp )
 32 
 33     csrr t0, mstatus                    /* Required for MPIE bit. */
 34     store_x t0, 29 * portWORD_SIZE( sp )
 35 
 36     portasmSAVE_ADDITIONAL_REGISTERS    /* Defined in freertos_risc_v_chip_specific_extensions.h to save any registers unique to the RISC-V implementation. */
 37 
 38     load_x  t0, pxCurrentTCB            /* Load pxCurrentTCB. */
 39     store_x  sp, 0( t0 )                /* Write sp to first TCB member. */
 40 
 41     csrr a0, mcause
 42     csrr a1, mepc
 43 
 44 test_if_asynchronous:
 45     srli a2, a0, __riscv_xlen - 1        /* MSB of mcause is 1 if handing an asynchronous interrupt - shift to LSB to clear other bits. */
 46     beq a2, x0, handle_synchronous        /* Branch past interrupt handing if not asynchronous. */
 47     store_x a1, 0( sp )                    /* Asynch so save unmodified exception return address. */
 48 
 49 handle_asynchronous:
 50     /* TODO: 判断是定时器中断还是其他(外部)中断 */
 51     load_x sp, xISRStackTop                /* Switch to ISR stack before function call. */
 52     call xPortClearTimerIntPending
 53     jal xTaskIncrementTick
 54     beqz a0, processed_source        /* Don't switch context if incrementing tick didn't unblock a task. */
 55     jal vTaskSwitchContext
 56     #jal portasmHANDLE_INTERRUPT            /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */
 57     j processed_source
 58 
 59 handle_synchronous:
 60     addi a1, a1, 4                        /* Synchronous so updated exception return address to the instruction after the instruction that generated the exeption. */
 61     store_x a1, 0( sp )                    /* Save updated exception return address. */
 62 
 63 test_if_environment_call:
 64     li t0, 11                             /* 11 == environment call. */
 65     bne a0, t0, is_exception            /* Not an M environment call, so some other exception. */
 66     load_x sp, xISRStackTop                /* Switch to ISR stack before function call. */
 67     jal vTaskSwitchContext
 68     j processed_source
 69 
 70 is_exception:
 71     csrr t0, mcause                        /* For viewing in the debugger only. */
 72     csrr t1, mepc                        /* For viewing in the debugger only */
 73     csrr t2, mstatus
 74     j is_exception                        /* No other exceptions handled yet. */
 75 
 76 as_yet_unhandled:
 77     csrr t0, mcause                        /* For viewing in the debugger only. */
 78     j as_yet_unhandled
 79 
 80 processed_source:
 81     load_x  t1, pxCurrentTCB            /* Load pxCurrentTCB. */
 82     load_x  sp, 0( t1 )                     /* Read sp from first TCB member. */
 83 
 84     /* Load mret with the address of the next instruction in the task to run next. */
 85     load_x t0, 0( sp )
 86     csrw mepc, t0
 87 
 88     portasmRESTORE_ADDITIONAL_REGISTERS    /* Defined in freertos_risc_v_chip_specific_extensions.h to restore any registers unique to the RISC-V implementation. */
 89 
 90     /* Load mstatus with the interrupt enable bits used by the task. */
 91     load_x  t0, 29 * portWORD_SIZE( sp )
 92     csrw mstatus, t0                        /* Required for MPIE bit. */
 93 
 94     load_x  x1, 1 * portWORD_SIZE( sp )
 95     load_x  x5, 2 * portWORD_SIZE( sp )        /* t0 */
 96     load_x  x6, 3 * portWORD_SIZE( sp )        /* t1 */
 97     load_x  x7, 4 * portWORD_SIZE( sp )        /* t2 */
 98     load_x  x8, 5 * portWORD_SIZE( sp )        /* s0/fp */
 99     load_x  x9, 6 * portWORD_SIZE( sp )        /* s1 */
100     load_x  x10, 7 * portWORD_SIZE( sp )    /* a0 */
101     load_x  x11, 8 * portWORD_SIZE( sp )    /* a1 */
102     load_x  x12, 9 * portWORD_SIZE( sp )    /* a2 */
103     load_x  x13, 10 * portWORD_SIZE( sp )    /* a3 */
104     load_x  x14, 11 * portWORD_SIZE( sp )    /* a4 */
105     load_x  x15, 12 * portWORD_SIZE( sp )    /* a5 */
106     load_x  x16, 13 * portWORD_SIZE( sp )    /* a6 */
107     load_x  x17, 14 * portWORD_SIZE( sp )    /* a7 */
108     load_x  x18, 15 * portWORD_SIZE( sp )    /* s2 */
109     load_x  x19, 16 * portWORD_SIZE( sp )    /* s3 */
110     load_x  x20, 17 * portWORD_SIZE( sp )    /* s4 */
111     load_x  x21, 18 * portWORD_SIZE( sp )    /* s5 */
112     load_x  x22, 19 * portWORD_SIZE( sp )    /* s6 */
113     load_x  x23, 20 * portWORD_SIZE( sp )    /* s7 */
114     load_x  x24, 21 * portWORD_SIZE( sp )    /* s8 */
115     load_x  x25, 22 * portWORD_SIZE( sp )    /* s9 */
116     load_x  x26, 23 * portWORD_SIZE( sp )    /* s10 */
117     load_x  x27, 24 * portWORD_SIZE( sp )    /* s11 */
118     load_x  x28, 25 * portWORD_SIZE( sp )    /* t3 */
119     load_x  x29, 26 * portWORD_SIZE( sp )    /* t4 */
120     load_x  x30, 27 * portWORD_SIZE( sp )    /* t5 */
121     load_x  x31, 28 * portWORD_SIZE( sp )    /* t6 */
122     addi sp, sp, portCONTEXT_SIZE
123 
124     mret
125     .endfunc
复制代码

 

第3~34行,保护现场,即将寄存器压栈。

第36行,保存额外的寄存器,这里什么都不做。

第38~39行,将sp的值保存在当前TCB的起始地址处。

第41行,读取mcause的值到a0寄存器。

第42行,读取mepc的值到a1寄存器。

第45行,将a0寄存器的值右移31位,将移位后的值写入a2寄存器。

第46行,判断a2寄存器的值是否等于0,即判断是中断(异步中断)还是异常(同步中断),如果等于0则跳转到第59行。这里假设a2的值不等于0,因此继续往下看。

第47行,将中断返回地址保存在栈顶。

第51行,使用中断栈,即在中断里有专门的栈空间来进行函数调用。

第52行,调用xPortClearTimerIntPending()函数,在这里该函数的作用是清定时器中断pending。目前在freertos这个demo里只有定时器中断,因此就没有判断是否是其他外部中断了。

第53行,调用xTaskIncrementTick()函数,这个函数的返回值决定了是否需要切换到其他任务。如果返回值为0,表示不需要切换,否则需要任务切换。

第54行,判断xTaskIncrementTick()函数的返回值是否等于0,如果是则跳转到第80行,这里假设返回值不等于0。

第55行,调用vTaskSwitchContext()函数,这个函数会切换当前TCB到将要执行的任务上。

第57行,跳转到第80行。

第81~82行,使用当前TCB的sp。

第85~86行,将中断返回地址写入mepc寄存器。

第88行,恢复额外的寄存器,这里什么都没做。

第91~122行,从栈里恢复寄存器的值,这和前面进入中断时的保存现场操作是成对的,即恢复现场。

第124行,中断返回,从mepc的值所指的地址处开始执行代码。

接下来,看回第46行,如果a2寄存器的值为0,则跳转到第第59行。

第60行,将a1的值加4,即将中断返回地址的值加4,后面会用到。

第61行,将a1的值写入sp寄存器。

第64~65行,判断a0的值是否等于11,即mcause的值是否等于11,即是否是ecall指令异常。如果不是则跳转到第70行。

第70~74行是一段死循环代码。

第66~68行,前面已经分析过了。

xTaskCreate

(gdb) bt
#0  pxPortInitialiseStack () at ../..//Source/portable/GCC/RISC-V/portASM.S:420
#1  0x00000000800011fa in prvInitialiseNewTask (pxTaskCode=0x80000284 <prvQueueReceiveTask>, 
    pcName=0x80004828 "Rx", ulStackDepth=140, pvParameters=0x0, uxPriority=2, pxCreatedTask=0x0, 
    pxNewTCB=0x80020930 <ucHeap+1336>, xRegions=0x0) at ../..//Source/tasks.c:1022
#2  0x000000008000108e in xTaskCreate (pxTaskCode=0x80000284 <prvQueueReceiveTask>, pcName=0x80004828 "Rx", 
    usStackDepth=140, pvParameters=0x0, uxPriority=2, pxCreatedTask=0x0) at ../..//Source/tasks.c:814

 

 

 

 堆栈初始化

复制代码
/*
 * Unlike other ports pxPortInitialiseStack() is written in assembly code as it
 * needs access to the portasmADDITIONAL_CONTEXT_SIZE constant.  The prototype
 * for the function is as per the other ports:
 * StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters );
 *
 * As per the standard RISC-V ABI pxTopcOfStack is passed in in a0, pxCode in
 * a1, and pvParameters in a2.  The new top of stack is passed out in a0.
 *
 * RISC-V maps registers to ABI names as follows (X1 to X31 integer registers
 * for the 'I' profile, X1 to X15 for the 'E' profile, currently I assumed).
 *
 * Register        ABI Name    Description                        Saver
 * x0            zero        Hard-wired zero                    -
 * x1            ra            Return address                    Caller
 * x2            sp            Stack pointer                    Callee
 * x3            gp            Global pointer                    -
 * x4            tp            Thread pointer                    -
 * x5-7            t0-2        Temporaries                        Caller
 * x8            s0/fp        Saved register/Frame pointer    Callee
 * x9            s1            Saved register                    Callee
 * x10-11        a0-1        Function Arguments/return values Caller
 * x12-17        a2-7        Function arguments                Caller
 * x18-27        s2-11        Saved registers                    Callee
 * x28-31        t3-6        Temporaries                        Caller
 *
 * The RISC-V context is saved t FreeRTOS tasks in the following stack frame,
 * where the global and thread pointers are currently assumed to be constant so
 * are not saved:
 *
 * mstatus
 * x31
 * x30
 * x29
 * x28
 * x27
 * x26
 * x25
 * x24
 * x23
 * x22
 * x21
 * x20
 * x19
 * x18
 * x17
 * x16
 * x15
 * x14
 * x13
 * x12
 * x11
 * pvParameters
 * x9
 * x8
 * x7
 * x6
 * x5
 * portTASK_RETURN_ADDRESS
 * [chip specific registers go here]
 * pxCode
 */
.align 8
.func
pxPortInitialiseStack:

    csrr t0, mstatus                    /* Obtain current mstatus value. */
    addi t1, x0, 0x188                    /* Generate the value 0x1880, which are the MPIE and MPP bits to set in mstatus. */
    slli t1, t1, 4
    or t0, t0, t1                        /* Set MPIE and MPP bits in mstatus value. */

    addi a0, a0, -portWORD_SIZE
    store_x t0, 0(a0)                    /* mstatus onto the stack. */
    addi a0, a0, -(22 * portWORD_SIZE)    /* Space for registers x11-x31. */
    store_x a2, 0(a0)                    /* Task parameters (pvParameters parameter) goes into register X10/a0 on the stack. */
    addi a0, a0, -(6 * portWORD_SIZE)    /* Space for registers x5-x9. */
    store_x x0, 0(a0)                    /* Return address onto the stack, could be portTASK_RETURN_ADDRESS */
    addi t0, x0, portasmADDITIONAL_CONTEXT_SIZE /* The number of chip specific additional registers. */
chip_specific_stack_frame:                /* First add any chip specific registers to the stack frame being created. */
    beq t0, x0, 1f    /*跳转到label 1*/                    /* No more chip specific registers to save. */
    addi a0, a0, -portWORD_SIZE            /* Make space for chip specific register. */
    store_x x0, 0(a0)                    /* Give the chip specific register an initial value of zero. */
    addi t0, t0, -1                        /* Decrement the count of chip specific registers remaining. */
    j chip_specific_stack_frame            /* Until no more chip specific registers. */
1:
    addi a0, a0, -portWORD_SIZE
    store_x a1, 0(a0)                    /* mret value (pxCode parameter) onto the stack. */
    ret
    .endfunc
/*-----------------------------------------------------------*/
复制代码

 portasmADDITIONAL_CONTEXT_SIZE为目标芯片增加的寄存器数。如Vega 开发板的RI5CY核,包含6个额外的寄存器。因此#define portasmADDITIONAL_CONTEXT_SIZE 6

 

 

 

vTaskSwitchContext切换

 

FreeRTOS任务相关的代码大约占总代码的一半左右,这些代码都在为一件事情而努力,即找到优先级最高的就绪任务,并使之获得CPU运行权。任务切换是这一过程的直接实施者,为了更快的找到优先级最高的就绪任务,任务切换的代码通常都是精心设计的,甚至会用到汇编指令或者与硬件相关的特性,比如Cortex-M3的CLZ指令。因此任务切换的大部分代码是由硬件移植层提供的,不同的平台,实现发方法也可能不同,这篇文章以Cortex-M3为例,讲述FreeRTOS任务切换的过程。
FreeRTOS有两种方法触发任务切换:

  1. 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
  2. 系统节拍时钟中断

  在ARM平台对于Cortex-M3平台,这两种方法的实质是一样的,都会使能一个PendSV中断,在PendSV中断服务程序中,找到最高优先级的就绪任务,然后让这个任务获得CPU运行权,从而完成任务切换。

 在riscv这个宏的定义如下

/* Scheduler utilities. */
extern void vTaskSwitchContext( void );
#define portYIELD() __asm volatile( "ecall" );
#define portEND_SWITCHING_ISR( xSwitchRequired ) do { if( xSwitchRequired ) vTaskSwitchContext(); } while( 0 )
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
/*-----------------------------------------------------------*/

 

 

 调度

调度过程

    1. 保存上一个 task 的上下文
    2. 找到下一个要执行的 task
    3. 让新的任务从它上次被打断的地方开始执行

 arm下关键代码如下所示:

复制代码
lib/FreeRTOS/portable/GCC/ARM_CA53_64_BIT/portASM.S:

    portSAVE_CONTEXT            /*保存当前任务的上下文到当前任务栈中*/
    BL vTaskSwitchContext       /*挑选下一个最高优先级任务*/
    portRESTORE_CONTEXT         /*从新任务的栈中加载上下文信息,并运行新任务*/




 


 

 

 








复制代码

 

 

 选择调度task

 

vTaskSwitchContext() 函数里面会调用:

taskSELECT_HIGHEST_PRIORITY_TASK();

选择下一个最高优先级的任务

 

  /* Select a new task to run using either the generic C or port
         * optimised asm code. */
        taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
        traceTASK_SWITCHED_IN();

 

 

调度时机

谁来调度呢 ?

举个例子:系统中有两个同等优先级的任务:task1 和 task2,而且在就绪态队列中,它们的优先级是最高的,也就是说,此时系统中就它们兄弟俩在运行了,task1在执行的时候, 如果它不主动放弃 CPU,它就会一直执行,那么什么时候才能轮到 task2 执行呢??

所以此时需要一种机制能打断 task1 的执行,然后切换到 task2,后面在 task2 执行的时候,也能从 task2 中收回 CPU 的控制权,然后交给 task1,并且要让 task1 从上次被打断的地方开始执行。

通用的方法就是周期性的触发中断(一般称为 Tick 中断),在中断处理函数中,保存上一个任务的上下文,然后切换到下一个任务中。在 FreeRTOS 中使用一个硬件 timer 作为 Tick 中断。如果将 Tick 中断配置为 1ms 来一次,那么系统就会 1ms 进行一次任务切换。

至于中断来了怎么跑到对应的中断处理函数中,这个是和芯片中的中断控制器相关的,不同的中断控制器有不同的配置方法,本文就不细说了。总之,在 tick 中断的中断处理函数中做的事情都是一样的,就是上面所描述的任务切换。

 

FirstTask

 

 

 

 

(gdb) bt
#0  xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:306
#1  0x00000000800034ba in xPortStartScheduler () at ../..//Source/portable/GCC/RISC-V/port.c:196
#2  0x000000008000151c in vTaskStartScheduler () at ../..//Source/tasks.c:2067
#3  0x0000000080000228 in main_blinky () at ./blinky_demo/main_blinky.c:142
#4  0x0000000080000120 in main () at main.c:113
(gdb) 

 

 

 

timers.c:66:        #define configTIMER_SERVICE_TASK_NAME    "Tmr Svc"

 

 

 

 

 

 

 

 

复制代码
(gdb) b xTimerCreateTimerTask
Breakpoint 1 at 0x8000264c: file ../..//Source/timers.c, line 235.
(gdb) c
Continuing.

Breakpoint 1, xTimerCreateTimerTask () at ../..//Source/timers.c:235
235             BaseType_t xReturn = pdFAIL;
(gdb) bt
#0  xTimerCreateTimerTask () at ../..//Source/timers.c:235
#1  0x00000000800014e2 in vTaskStartScheduler () at ../..//Source/tasks.c:2014
#2  0x0000000080000228 in main_blinky () at ./blinky_demo/main_blinky.c:142
#3  0x0000000080000120 in main () at main.c:113
(gdb) 
复制代码

 

 

 

 

复制代码
235             BaseType_t xReturn = pdFAIL;
(gdb) list
230                                            Timer_t * pxNewTimer ) PRIVILEGED_FUNCTION;
231     /*-----------------------------------------------------------*/
232
233         BaseType_t xTimerCreateTimerTask( void )
234         {
235             BaseType_t xReturn = pdFAIL;
236
237             /* This function is called when the scheduler is started if
238              * configUSE_TIMERS is set to 1.  Check that the infrastructure used by the
239              * timer service task has been created/initialised.  If timers have already
(gdb) s
241             prvCheckForValidListAndQueue();
(gdb) list
236
237             /* This function is called when the scheduler is started if
238              * configUSE_TIMERS is set to 1.  Check that the infrastructure used by the
239              * timer service task has been created/initialised.  If timers have already
240              * been created then the initialisation will already have been performed. */
241             prvCheckForValidListAndQueue();
242
243             if( xTimerQueue != NULL )
244             {
245                 #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
(gdb) n
243             if( xTimerQueue != NULL )
(gdb) n
267                         xReturn = xTaskCreate( prvTimerTask,
(gdb) n
281             configASSERT( xReturn );
(gdb) n
282             return xReturn;
(gdb) n
283         }
(gdb) n
vTaskStartScheduler () at ../..//Source/tasks.c:2023
2023        if( xReturn == pdPASS )
(gdb) n
2039            portDISABLE_INTERRUPTS();
(gdb) 
复制代码

 

xPortStartFirstTask 启动timertask

复制代码
Breakpoint 2, xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:306
306             la t0, freertos_risc_v_trap_handler
(gdb) s
307             csrw mtvec, t0
(gdb) n
310             load_x  sp, pxCurrentTCB                        /* Load pxCurrentTCB. */



(gdb) set print pretty on
(gdb) p *pxCurrentTCB
$2 = {
  pxTopOfStack = 0x80021590 <ucHeap+4504>,
  xStateListItem = {
    xItemValue = 0,
    pxNext = 0x800202c8 <pxReadyTasksLists+256>,
    pxPrevious = 0x800202c8 <pxReadyTasksLists+256>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x800202b8 <pxReadyTasksLists+240>
  },
  xEventListItem = {
    xItemValue = 1,
    pxNext = 0x0,
    pxPrevious = 0x0,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x0
  },
  uxPriority = 6,
  pxStack = 0x80021370 <ucHeap+3960>,
  pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000",
  uxCriticalNesting = 0,
  uxBasePriority = 6,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
复制代码

 

 

 

复制代码
Type "apropos word" to search for commands related to "word".
0x0000000000001000 in ?? ()
(gdb) b  xPortStartFirstTask
Breakpoint 1 at 0x80003700: file ../..//Source/portable/GCC/RISC-V/portASM.S, line 306.
(gdb) c
Continuing.

Breakpoint 1, xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:306
306             la t0, freertos_risc_v_trap_handler
(gdb) n
307             csrw mtvec, t0
(gdb) n
310             load_x  sp, pxCurrentTCB                        /* Load pxCurrentTCB. */
(gdb) n
xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:311
311             load_x  sp, 0( sp )                                     /* Read sp from first TCB member. */
(gdb) n
xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:313
313             load_x  x1, 0( sp ) /* Note for starting the scheduler the exception return address is used as the function return address. */
(gdb) n
317             load_x  x6, 3 * portWORD_SIZE( sp )             /* t1 */
(gdb) n
318             load_x  x7, 4 * portWORD_SIZE( sp )             /* t2 */
(gdb) n
319             load_x  x8, 5 * portWORD_SIZE( sp )             /* s0/fp */
(gdb) n
320             load_x  x9, 6 * portWORD_SIZE( sp )             /* s1 */
(gdb) n
321             load_x  x10, 7 * portWORD_SIZE( sp )    /* a0 */
 …………………………………………………………………………………………………………………………………………………………………………………………
(gdb) n
339             load_x  x28, 25 * portWORD_SIZE( sp )   /* t3 */
(gdb) n
340             load_x  x29, 26 * portWORD_SIZE( sp )   /* t4 */
(gdb) n
341             load_x  x30, 27 * portWORD_SIZE( sp )   /* t5 */
(gdb) n
342             load_x  x31, 28 * portWORD_SIZE( sp )   /* t6 */
(gdb) n
344             load_x  x5, 29 * portWORD_SIZE( sp )    /* Initial mstatus into x5 (t0) */
(gdb) n
345             addi x5, x5, 0x08                                               /* Set MIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */
(gdb) n
346             csrrw  x0, mstatus, x5                                  /* Interrupts enabled from here! */
(gdb) n
347             load_x  x5, 2 * portWORD_SIZE( sp )             /* Initial x5 (t0) value. */
(gdb) n
349             addi    sp, sp, portCONTEXT_SIZE
(gdb) n
xPortStartFirstTask () at ../..//Source/portable/GCC/RISC-V/portASM.S:350
350             ret
(gdb) n   ----------竟然跳到这里
prvTimerTask (pvParameters=0xa5a5a5a5a5a5a5a5) at ../..//Source/timers.c:569
569         {
(gdb) bt
#0  prvTimerTask (pvParameters=0xa5a5a5a5a5a5a5a5) at ../..//Source/timers.c:569
#1  0x000000008000293a in prvProcessExpiredTimer (xNextExpireTime=11936128518282651045, xTimeNow=11936128518282651045) at ../..//Source/timers.c:565
Backtrace stopped: frame did not save the PC
(gdb) c
Continuing.
复制代码

 

 

 

 

 

prvTimerTask

 static portTASK_FUNCTION( prvTimerTask, pvParameters )

 

 ra地址-load前

 

 

 

 

 

 

 

 

prvAddNewTaskToReadyList 更改pxCurrentTCB

 

复制代码
(gdb) b prvAddNewTaskToReadyList
Breakpoint 1 at 0x80001228: file ../..//Source/tasks.c, line 1045.
(gdb) c
Continuing.

Breakpoint 1, prvAddNewTaskToReadyList (pxNewTCB=0x80020930 <ucHeap+1336>) at ../..//Source/tasks.c:1045
1045        taskENTER_CRITICAL();
(gdb) s
vTaskEnterCritical () at ../..//Source/tasks.c:4325
4325            portDISABLE_INTERRUPTS();
(gdb) list
4320
4321    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
4322
4323        void vTaskEnterCritical( void )
4324        {
4325            portDISABLE_INTERRUPTS();
4326
4327            if( xSchedulerRunning != pdFALSE )
4328            {
4329                ( pxCurrentTCB->uxCriticalNesting )++;
(gdb) n
4327            if( xSchedulerRunning != pdFALSE )
(gdb) n
4346        }
(gdb) n
prvAddNewTaskToReadyList (pxNewTCB=0x80020930 <ucHeap+1336>) at ../..//Source/tasks.c:1047
1047            uxCurrentNumberOfTasks++;
(gdb) n
1049            if( pxCurrentTCB == NULL )
(gdb) n
1053                pxCurrentTCB = pxNewTCB;
(gdb) n
1055                if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
(gdb) n
1060                    prvInitialiseTaskLists();
(gdb) n
1089            uxTaskNumber++;
(gdb) n
1099            prvAddTaskToReadyList( pxNewTCB );
(gdb) n
1103        taskEXIT_CRITICAL();
(gdb) n
1105        if( xSchedulerRunning != pdFALSE )
(gdb) n
1122    }
(gdb) n
复制代码

 

 

复制代码
0x0000000000001000 in ?? ()
(gdb) watch pxCurrentTCB
Hardware watchpoint 1: pxCurrentTCB
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x0
New value = (TCB_t * volatile) 0x80020930 <ucHeap+1336>
prvAddNewTaskToReadyList (pxNewTCB=0x80020930 <ucHeap+1336>) at ../..//Source/tasks.c:1055
1055                if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
(gdb) bt
#0  prvAddNewTaskToReadyList (pxNewTCB=0x80020930 <ucHeap+1336>) at ../..//Source/tasks.c:1055
#1  0x0000000080001096 in xTaskCreate (pxTaskCode=0x80000284 <prvQueueReceiveTask>, pcName=0x80004828 "Rx", usStackDepth=140, pvParameters=0x0, uxPriority=2, pxCreatedTask=0x0) at ../..//Source/tasks.c:815
#2  0x00000000800001d0 in main_blinky () at ./blinky_demo/main_blinky.c:127
#3  0x0000000080000120 in main () at main.c:113
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x80020930 <ucHeap+1336>
New value = (TCB_t * volatile) 0x800216a0 <ucHeap+4776>
prvAddNewTaskToReadyList (pxNewTCB=0x800216a0 <ucHeap+4776>) at ../..//Source/tasks.c:1089
1089            uxTaskNumber++;
(gdb) bt
#0  prvAddNewTaskToReadyList (pxNewTCB=0x800216a0 <ucHeap+4776>) at ../..//Source/tasks.c:1089
#1  0x0000000080001096 in xTaskCreate (pxTaskCode=0x8000293a <prvTimerTask>, pcName=0x800048a0 "Tmr Svc", usStackDepth=100, pvParameters=0x0, uxPriority=6, pxCreatedTask=0x80020168 <xTimerTaskHandle>) at ../..//Source/tasks.c:815
#2  0x0000000080002684 in xTimerCreateTimerTask () at ../..//Source/timers.c:267
#3  0x00000000800014e2 in vTaskStartScheduler () at ../..//Source/tasks.c:2014
#4  0x0000000080000228 in main_blinky () at ./blinky_demo/main_blinky.c:142
#5  0x0000000080000120 in main () at main.c:113
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x800216a0 <ucHeap+4776>
New value = (TCB_t * volatile) 0x80020930 <ucHeap+1336>
vTaskSwitchContext () at ../..//Source/tasks.c:3064
3064    }
(gdb) bt
#0  vTaskSwitchContext () at ../..//Source/tasks.c:3064
#1  0x00000000800035d0 in test_if_environment_call () at ../..//Source/portable/GCC/RISC-V/portASM.S:237
Backtrace stopped: frame did not save the PC
(gdb) b  prvAddNewTaskToReadyList
Breakpoint 2 at 0x80001228: file ../..//Source/tasks.c, line 1045.
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x80020930 <ucHeap+1336>
New value = (TCB_t * volatile) 0x80020e50 <ucHeap+2648>
vTaskSwitchContext () at ../..//Source/tasks.c:3064
3064    }
(gdb) bt
#0  vTaskSwitchContext () at ../..//Source/tasks.c:3064
#1  0x00000000800035d0 in test_if_environment_call () at ../..//Source/portable/GCC/RISC-V/portASM.S:237
Backtrace stopped: frame did not save the PC
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x80020e50 <ucHeap+2648>
New value = (TCB_t * volatile) 0x800212c0 <ucHeap+3784>
vTaskSwitchContext () at ../..//Source/tasks.c:3064
3064    }
(gdb) c
Continuing.

Hardware watchpoint 1: pxCurrentTCB

Old value = (TCB_t * volatile) 0x800212c0 <ucHeap+3784>
New value = (TCB_t * volatile) 0x800216a0 <ucHeap+4776>
vTaskSwitchContext () at ../..//Source/tasks.c:3064
3064    }
(gdb) q
A debugging session is active.

        Inferior 1 [process 1] will be detached.
复制代码

 

 Timer Task

当系统调用 xTimerStart() 开启一个 timer 的时候,会记录超时时间(调用该函数时的时间 + 定时时长)到 timer 中,并将 timer 插入到 list 上,升序排列,这样系统只需要比较 list 中第一个 timer 的超时时间和当前时间值。如果当前时间大于超时时间,说明该定时器到期,进而就会去处理定时器,也就是先从 list 中将该 timer 移除,然后执行该定时器的回调函数。如果小于,说明还没到期,等待下次比较。

时间是以 tick 数进行记录的,随着系统不断运行,记录该 tick 的变量是个整型数,不管是 32bit,还是 64bit 总有溢出的时候。在溢出的时候,如果 list 上还有 timer 没有被处理,而新的 timer 又被创建,这个时候如果将新 timer 插入到同一个 list 上,由于 list 上是按升序排列的,新加入的 timer 被插入到 list 的最前面,这就会导致溢出前的 timer 得不到执行。所以为了解决这个问题,引入第二个 list,将溢出后新创建的 timer 加入到第二个 list 中,而第一个 list 中的 timer 稍后再处理。这样就不影响了。

以上就解答了为什么需要两个 list 。

上面说,系统会不断的去比较当前时间和超时时间,那谁来做这个比较的事呢??

在 FreeRTOS 中创建了一个 task 来处理。当系统开启调度( xStartSchedule() )的时候会创建一个 Timer Task。

 

 

 

 

 

 

 

timer interrupt

 

 

    pullMachineTimerCompareRegister  = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) );

 

 

  timer 回调

复制代码
(gdb) b vExampleTimerCallback
Breakpoint 1 at 0x80000332: file ./blinky_demo/main_blinky.c, line 219.
(gdb) c
Continuing.

Breakpoint 1, vExampleTimerCallback (xTimer=0x80020f00 <ucHeap+2824>) at ./blinky_demo/main_blinky.c:219
219                 printf("timers Callback %d\r\n", cnt++);
(gdb) bt
#0  vExampleTimerCallback (xTimer=0x80020f00 <ucHeap+2824>) at ./blinky_demo/main_blinky.c:219
#1  0x0000000080002930 in prvProcessExpiredTimer (xNextExpireTime=1000, xTimeNow=1000) at ../..//Source/timers.c:564
#2  0x00000000800029b2 in prvProcessTimerOrBlockTask (xNextExpireTime=1000, xListWasEmpty=0) at ../..//Source/timers.c:625
#3  0x0000000080002962 in prvTimerTask (pvParameters=0x0) at ../..//Source/timers.c:596
#4  0x000000008000293a in prvProcessExpiredTimer (xNextExpireTime=0, xTimeNow=11936128518282651045) at ../..//Source/timers.c:565
Backtrace stopped: frame did not save the PC
(gdb) 
复制代码

 

复制代码
复制代码
    static void prvProcessExpiredTimer( const TickType_t xNextExpireTime,
                                        const TickType_t xTimeNow )
    {
        Timer_t * const pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList ); /*lint !e9087 !e9079 void * is used as this macro is used with tasks and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */

        /* Remove the timer from the list of active timers.  A check has already
         * been performed to ensure the list is not empty. */

        ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );

        /* If the timer is an auto-reload timer then calculate the next
         * expiry time and re-insert the timer in the list of active timers. */
        if( ( pxTimer->ucStatus & tmrSTATUS_IS_AUTORELOAD ) != 0 )
        {
            prvReloadTimer( pxTimer, xNextExpireTime, xTimeNow );
        }
        else
        {
            pxTimer->ucStatus &= ( ( uint8_t ) ~tmrSTATUS_IS_ACTIVE );
        }

        /* Call the timer callback. */
        traceTIMER_EXPIRED( pxTimer );
        pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
    }
复制代码

 


 

复制代码

 

 

 

xTimerTaskHandle

复制代码
(gdb) watch xTimerTaskHandle
Hardware watchpoint 1: xTimerTaskHandle
(gdb) c
Continuing.

Hardware watchpoint 1: xTimerTaskHandle

Old value = (TaskHandle_t) 0x0
New value = (TaskHandle_t) 0x800216a0 <ucHeap+4776>
prvInitialiseNewTask (pxTaskCode=0x8000293a <prvTimerTask>, pcName=0x800048a0 "Tmr Svc", ulStackDepth=100, pvParameters=0x0, uxPriority=6, pxCreatedTask=0x80020168 <xTimerTaskHandle>, pxNewTCB=0x800216a0 <ucHeap+4776>, xRegions=0x0) at ../..//Source/tasks.c:1038
1038    }
(gdb) bt
#0  prvInitialiseNewTask (pxTaskCode=0x8000293a <prvTimerTask>, pcName=0x800048a0 "Tmr Svc", ulStackDepth=100, pvParameters=0x0, uxPriority=6, pxCreatedTask=0x80020168 <xTimerTaskHandle>, pxNewTCB=0x800216a0 <ucHeap+4776>, xRegions=0x0)
    at ../..//Source/tasks.c:1038
#1  0x000000008000108e in xTaskCreate (pxTaskCode=0x8000293a <prvTimerTask>, pcName=0x800048a0 "Tmr Svc", usStackDepth=100, pvParameters=0x0, uxPriority=6, pxCreatedTask=0x80020168 <xTimerTaskHandle>) at ../..//Source/tasks.c:814
#2  0x0000000080002684 in xTimerCreateTimerTask () at ../..//Source/timers.c:267
#3  0x00000000800014e2 in vTaskStartScheduler () at ../..//Source/tasks.c:2014
#4  0x0000000080000228 in main_blinky () at ./blinky_demo/main_blinky.c:142
#5  0x0000000080000120 in main () at main.c:113
(gdb) p xTimerTaskHandle
$1 = (TaskHandle_t) 0x800216a0 <ucHeap+4776>
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000000800020f6 in prvIdleTask (pvParameters=0x0) at ../..//Source/tasks.c:3428
3428            prvCheckTasksWaitingTermination();
(gdb) p *xTimerTaskHandle
$2 = {pxTopOfStack = 0x80021530 <ucHeap+4408>, xStateListItem = {xItemValue = 12000, pxNext = 0x80020e58 <ucHeap+2656>, pxPrevious = 0x800202f0 <xDelayedTaskList1+16>, pvOwner = 0x800216a0 <ucHeap+4776>, pvContainer = 0x800202e0 <xDelayedTaskList1>}, 
  xEventListItem = {xItemValue = 1, pxNext = 0x80020fb8 <ucHeap+3008>, pxPrevious = 0x80020fb8 <ucHeap+3008>, pvOwner = 0x800216a0 <ucHeap+4776>, pvContainer = 0x80020fa8 <ucHeap+2992>}, uxPriority = 6, pxStack = 0x80021370 <ucHeap+3960>, 
  pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000", uxCriticalNesting = 0, uxBasePriority = 6, uxMutexesHeld = 0, ulNotifiedValue = {0}, ucNotifyState = ""}
(gdb) 
复制代码

 

 

prvAddCurrentTaskToDelayedList

复制代码
Breakpoint 1, vQueueWaitForMessageRestricted (xQueue=0x80020f60 <ucHeap+2920>, xTicksToWait=998, xWaitIndefinitely=0) at ../..//Source/queue.c:2870
2870            Queue_t * const pxQueue = xQueue;
(gdb) p *pxCurrentTCB
$38 = {
  pxTopOfStack = 0x800213c0 <ucHeap+4040>,
  xStateListItem = {
    xItemValue = 6000,
    pxNext = 0x800202c8 <pxReadyTasksLists+256>,
    pxPrevious = 0x800202c8 <pxReadyTasksLists+256>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x800202b8 <pxReadyTasksLists+240>
  },
  xEventListItem = {
    xItemValue = 1,
    pxNext = 0x80020fb8 <ucHeap+3008>,
    pxPrevious = 0x80020fb8 <ucHeap+3008>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x0
  },
  uxPriority = 6,
  pxStack = 0x80021370 <ucHeap+3960>,
  pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000",
  uxCriticalNesting = 0,
  uxBasePriority = 6,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) c
Continuing.

Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=998, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
5289        const TickType_t xConstTickCount = xTickCount;
(gdb) p *pxCurrentTCB
$39 = {
  pxTopOfStack = 0x800214e0 <ucHeap+4328>,
  xStateListItem = {
    xItemValue = 6000,
    pxNext = 0x800202c8 <pxReadyTasksLists+256>,
    pxPrevious = 0x800202c8 <pxReadyTasksLists+256>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x800202b8 <pxReadyTasksLists+240>
  },
  xEventListItem = {
    xItemValue = 1,
    pxNext = 0x80020fb8 <ucHeap+3008>,
    pxPrevious = 0x80020fb8 <ucHeap+3008>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x80020fa8 <ucHeap+2992>
  },
  uxPriority = 6,
  pxStack = 0x80021370 <ucHeap+3960>,
  pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000",
  uxCriticalNesting = 0,
  uxBasePriority = 6,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) c
Continuing.

Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=18446744073709551615, xCanBlockIndefinitely=1) at ../..//Source/tasks.c:5289
5289        const TickType_t xConstTickCount = xTickCount;
(gdb) p *pxCurrentTCB
$40 = {
  pxTopOfStack = 0x80020780 <ucHeap+904>,
  xStateListItem = {
    xItemValue = 0,
    pxNext = 0x80020228 <pxReadyTasksLists+96>,
    pxPrevious = 0x80020228 <pxReadyTasksLists+96>,
    pvOwner = 0x80020930 <ucHeap+1336>,
    pvContainer = 0x80020218 <pxReadyTasksLists+80>
  },
  xEventListItem = {
    xItemValue = 5,
    pxNext = 0x80020468 <ucHeap+112>,
    pxPrevious = 0x80020468 <ucHeap+112>,
    pvOwner = 0x80020930 <ucHeap+1336>,
    pvContainer = 0x80020458 <ucHeap+96>
  },
  uxPriority = 2,
  pxStack = 0x800204c0 <ucHeap+200>,
  pcTaskName = "Rx", '\000' <repeats 13 times>,
  uxCriticalNesting = 0,
  uxBasePriority = 2,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) c
Continuing.

Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=996, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
5289        const TickType_t xConstTickCount = xTickCount;
(gdb) p *pxCurrentTCB
$41 = {
  pxTopOfStack = 0x80020ca0 <ucHeap+2216>,
  xStateListItem = {
    xItemValue = 6006,
    pxNext = 0x80020200 <pxReadyTasksLists+56>,
    pxPrevious = 0x80020200 <pxReadyTasksLists+56>,
    pvOwner = 0x80020e50 <ucHeap+2648>,
    pvContainer = 0x800201f0 <pxReadyTasksLists+40>
  },
  xEventListItem = {
    xItemValue = 6,
    pxNext = 0x0,
    pxPrevious = 0x0,
    pvOwner = 0x80020e50 <ucHeap+2648>,
    pvContainer = 0x0
  },
  uxPriority = 1,
  pxStack = 0x800209e0 <ucHeap+1512>,
  pcTaskName = "TX", '\000' <repeats 13 times>,
  uxCriticalNesting = 0,
  uxBasePriority = 1,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) 
复制代码

 

 

Tmr Svc prvAddCurrentTaskToDelayedList

复制代码

Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=998, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
5289 const TickType_t xConstTickCount = xTickCount;
(gdb) p *pxCurrentTCB
$43 = {
pxTopOfStack = 0x800214e0 <ucHeap+4328>,
xStateListItem = {
xItemValue = 7000,
pxNext = 0x800202c8 <pxReadyTasksLists+256>,
pxPrevious = 0x800202c8 <pxReadyTasksLists+256>,
pvOwner = 0x800216a0 <ucHeap+4776>,
pvContainer = 0x800202b8 <pxReadyTasksLists+240>
},
xEventListItem = {
xItemValue = 1,
pxNext = 0x80020fb8 <ucHeap+3008>,
pxPrevious = 0x80020fb8 <ucHeap+3008>,
pvOwner = 0x800216a0 <ucHeap+4776>,
pvContainer = 0x80020fa8 <ucHeap+2992>
},
uxPriority = 6,
pxStack = 0x80021370 <ucHeap+3960>,
pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000",
uxCriticalNesting = 0,
uxBasePriority = 6,
uxMutexesHeld = 0,
ulNotifiedValue = {0},
ucNotifyState = ""
}
(gdb) bt
#0 prvAddCurrentTaskToDelayedList (xTicksToWait=998, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
#1 0x0000000080001d74 in vTaskPlaceOnEventListRestricted (pxEventList=0x80020fa8 <ucHeap+2992>,
xTicksToWait=998, xWaitIndefinitely=0) at ../..//Source/tasks.c:3147
#2 0x0000000080000fde in vQueueWaitForMessageRestricted (xQueue=0x80020f60 <ucHeap+2920>, xTicksToWait=998,
xWaitIndefinitely=0) at ../..//Source/queue.c:2891
#3 0x00000000800029f4 in prvProcessTimerOrBlockTask (xNextExpireTime=8000, xListWasEmpty=0)
at ../..//Source/timers.c:642
#4 0x0000000080002962 in prvTimerTask (pvParameters=0x0) at ../..//Source/timers.c:596
#5 0x000000008000293a in prvProcessExpiredTimer (xNextExpireTime=0, xTimeNow=11936128518282651045)
at ../..//Source/timers.c:565
Backtrace stopped: frame did not save the PC
(gdb)

复制代码

pxCurrentTCB= Tmr Svc下 prvAddCurrentTaskToDelayedList

复制代码
(gdb) n
5302        if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
(gdb) n
5306            portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task.  pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
(gdb) n
5315                if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
(gdb) n
5327                    xTimeToWake = xConstTickCount + xTicksToWait;
(gdb) n
5330                    listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
(gdb) n
5332                    if( xTimeToWake < xConstTickCount )
(gdb) n
5342                        vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
(gdb) n
5347                        if( xTimeToWake < xNextTaskUnblockTime )
(gdb) n
5395    }
(gdb) n
复制代码

 

rx   prvAddCurrentTaskToDelayedList

 

复制代码
Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=18446744073709551615, xCanBlockIndefinitely=1) at ../..//Source/tasks.c:5289
5289        const TickType_t xConstTickCount = xTickCount;
(gdb) p *pxCurrentTCB
$44 = {
  pxTopOfStack = 0x80020780 <ucHeap+904>,
  xStateListItem = {
    xItemValue = 0,
    pxNext = 0x80020228 <pxReadyTasksLists+96>,
    pxPrevious = 0x80020228 <pxReadyTasksLists+96>,
    pvOwner = 0x80020930 <ucHeap+1336>,
    pvContainer = 0x80020218 <pxReadyTasksLists+80>
  },
  xEventListItem = {
    xItemValue = 5,
    pxNext = 0x80020468 <ucHeap+112>,
    pxPrevious = 0x80020468 <ucHeap+112>,
    pvOwner = 0x80020930 <ucHeap+1336>,
    pvContainer = 0x80020458 <ucHeap+96>
  },
  uxPriority = 2,
  pxStack = 0x800204c0 <ucHeap+200>,
  pcTaskName = "Rx", '\000' <repeats 13 times>,
  uxCriticalNesting = 0,
  uxBasePriority = 2,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) bt
#0  prvAddCurrentTaskToDelayedList (xTicksToWait=18446744073709551615, xCanBlockIndefinitely=1)
    at ../..//Source/tasks.c:5289
#1  0x0000000080001cbe in vTaskPlaceOnEventList (pxEventList=0x80020458 <ucHeap+96>, 
    xTicksToWait=18446744073709551615) at ../..//Source/tasks.c:3088
#2  0x0000000080000b6e in xQueueReceive (xQueue=0x80020410 <ucHeap+24>, pvBuffer=0x800208e0 <ucHeap+1256>, 
    xTicksToWait=18446744073709551615) at ../..//Source/queue.c:1454
#3  0x00000000800002c8 in prvQueueReceiveTask (pvParameters=0x0) at ./blinky_demo/main_blinky.c:197
#4  0x0000000000000000 in ?? ()
Backtrace stopped: frame did not save the PC
(gdb) 
复制代码

 

tx  prvAddCurrentTaskToDelayedList

复制代码
Breakpoint 3, prvAddCurrentTaskToDelayedList (xTicksToWait=996, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
5289        const TickType_t xConstTickCount = xTickCount;
(gdb) bt
#0  prvAddCurrentTaskToDelayedList (xTicksToWait=996, xCanBlockIndefinitely=0) at ../..//Source/tasks.c:5289
#1  0x0000000080001484 in xTaskDelayUntil (pxPreviousWakeTime=0x80020e10 <ucHeap+2584>, xTimeIncrement=1000)
    at ../..//Source/tasks.c:1288
#2  0x0000000080000256 in prvQueueSendTask (pvParameters=0x0) at ./blinky_demo/main_blinky.c:170
#3  0x0000000000000000 in ?? ()
Backtrace stopped: frame did not save the PC
(gdb) p *pxCurrentTCB
$45 = {
  pxTopOfStack = 0x80020ca0 <ucHeap+2216>,
  xStateListItem = {
    xItemValue = 7006,
    pxNext = 0x80020200 <pxReadyTasksLists+56>,
    pxPrevious = 0x80020200 <pxReadyTasksLists+56>,
    pvOwner = 0x80020e50 <ucHeap+2648>,
    pvContainer = 0x800201f0 <pxReadyTasksLists+40>
  },
  xEventListItem = {
    xItemValue = 6,
    pxNext = 0x0,
    pxPrevious = 0x0,
    pvOwner = 0x80020e50 <ucHeap+2648>,
    pvContainer = 0x0
  },
  uxPriority = 1,
  pxStack = 0x800209e0 <ucHeap+1512>,
  pcTaskName = "TX", '\000' <repeats 13 times>,
  uxCriticalNesting = 0,
  uxBasePriority = 1,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) 
复制代码

 

 

 

prvAddCurrentTaskToDelayedList之后执行portYIELD_WITHIN_API()

 

 

Tmr Svc task下执行LEDTimer
复制代码
Breakpoint 13, prvProcessExpiredTimer (xNextExpireTime=165000, xTimeNow=165000) at ../..//Source/timers.c:544
544             Timer_t * const pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList ); /*lint !e9087 !e9079 void * is used as this macro is used with tasks and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
(gdb)  p *pxTimer
Cannot access memory at address 0x0
(gdb) n
549             ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
(gdb)  p *pxTimer
$75 = {
  pcTimerName = 0x80004838 "LEDTimer",
  xTimerListItem = {
    xItemValue = 165000,
    pxNext = 0x800203b8 <xActiveTimerList1+16>,
    pxPrevious = 0x800203b8 <xActiveTimerList1+16>,
    pvOwner = 0x80020f00 <ucHeap+2824>,
    pvContainer = 0x800203a8 <xActiveTimerList1>
  },
  xTimerPeriodInTicks = 1000,
  pvTimerID = 0x0,
  pxCallbackFunction = 0x80000326 <vExampleTimerCallback>,
  ucStatus = 5 '\005'
}
(gdb)  p *pxCurrentTCB
$76 = {
  pxTopOfStack = 0x80021530 <ucHeap+4408>,
  xStateListItem = {
    xItemValue = 165000,
    pxNext = 0x800202c8 <pxReadyTasksLists+256>,
    pxPrevious = 0x800202c8 <pxReadyTasksLists+256>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x800202b8 <pxReadyTasksLists+240>
  },
  xEventListItem = {
    xItemValue = 1,
    pxNext = 0x80020fb8 <ucHeap+3008>,
    pxPrevious = 0x80020fb8 <ucHeap+3008>,
    pvOwner = 0x800216a0 <ucHeap+4776>,
    pvContainer = 0x0
  },
  uxPriority = 6,
  pxStack = 0x80021370 <ucHeap+3960>,
  pcTaskName = "Tmr Svc\000\000\000\000\000\000\000\000",
  uxCriticalNesting = 0,
  uxBasePriority = 6,
  uxMutexesHeld = 0,
  ulNotifiedValue = {0},
  ucNotifyState = ""
}
(gdb) 
复制代码

 

idle

 

 

ecall

#define portYIELD() __asm volatile( "ecall" );

 

#define portYIELD_WITHIN_API    portYIELD

 

 

 

 

 

 指令

 

 

 

J型指令在RISC-V中代表的是jal指令

  • 将当前PC+4的值存入返回地址

    • 普通的j指令将x0存入返回地址(忽略返回地址)
  • 然后将当前PC值加上19位的偏移值(相对跳转)

 

复制代码
root@ubuntu:/home/ubuntu # cat test.s 
# Jump And Link (Short-Distance CALL)
# Format:
#       JAL RD, IMM
# Description:
#       This instruction is used to call a subroutine (i.e., function). 
#       The jump and link (JAL) instruction uses the J-type format, where the 
#       immediate (20 bits width) encodes a signed offset in multiples of 2 bytes. 
#       The offset is sign-extended and added to the address of the jump 
#       instruction to form the jump target address. JAL can therefore target 
#       a ±1 MiB range.
#       JAL stores the address of the instruction following the jump (pc+4) into
#       register RD.
# Note:
#       When programming, we just provide label instead of immediate value, and
#       leave linker to provide the final immediate value.
#
# Jump And Link Register
# Format:
#       JALR RD, RS1, IMM
# Description:
#       This instruction is used to call a subroutine (i.e., function). 
#       The indirect jump instruction JALR (jump and link register) uses the 
#       I-type encoding. The target address is obtained by adding the 
#       sign-extended 12-bit I-immediate to the register RS1, then setting
#       the least-significant bit of the result to zero. JALR can therefore target 
#       a ±1 KiB range, relative to the address in RS1.
#       The address of the instruction following the jump(pc+4) is written to register RD.
#
# Sample to demo how to call a sub-function and return from it
#
# int a = 1;
# int b = 1;
#
# void sum()
# {
#     a = a + b;
# } 
#
# void _start()
# {
#     sum();
# }

        .text                   # Define beginning of text section
        .global _start          # Define entry _start

_start:
        li x6, 1
        li x7, 2
        jal x5, sum             # call sum, return address is saved in x5

stop:
        j stop                  # Infinite loop to stop execution

sum:
        add x6, x6, x7          # x6 = x6 + x7
        jalr x0, 0(x5)          # return

        .end                    # End of file
复制代码

 反汇编

root@ubuntu:code/asm/jalr# riscv64-unknown-elf-as test.s -o test

root@ubuntu:code/asm/jalr# riscv64-unknown-elf-objdump -D test > text2.txt


 

 

复制代码
cat text.txt 

test:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <_start>:
   0:   00100313                li      t1,1
   4:   00200393                li      t2,2
   8:   008002ef                jal     t0,10 <sum>

000000000000000c <stop>:
   c:   0000006f                j       c <stop>

0000000000000010 <sum>:
  10:   00730333                add     t1,t1,t2
  14:   00028067                jr      t0

Disassembly of section .riscv.attributes:

0000000000000000 <.riscv.attributes>:
   0:   2d41                    addiw   s10,s10,16
   2:   0000                    unimp
   4:   7200                    ld      s0,32(a2)
   6:   7369                    lui     t1,0xffffa
   8:   01007663                bgeu    zero,a6,14 <.riscv.attributes+0x14>
   c:   00000023                sb      zero,0(zero) # 0 <.riscv.attributes>
  10:   7205                    lui     tp,0xfffe1
  12:   3676                    fld     fa2,376(sp)
  14:   6934                    ld      a3,80(a0)
  16:   7032                    0x7032
  18:   5f30                    lw      a2,120(a4)
  1a:   326d                    addiw   tp,tp,-5
  1c:   3070                    fld     fa2,224(s0)
  1e:   615f 7032 5f30          0x5f307032615f
  24:   3266                    fld     ft4,120(sp)
  26:   3070                    fld     fa2,224(s0)
  28:   645f 7032 0030          0x307032645f
复制代码

 

jal没有rd寄存器,采用默认的ra

复制代码
# Jump And Link (Short-Distance CALL)
# Format:
#       JAL RD, IMM
# Description:
#       This instruction is used to call a subroutine (i.e., function). 
#       The jump and link (JAL) instruction uses the J-type format, where the 
#       immediate (20 bits width) encodes a signed offset in multiples of 2 bytes. 
#       The offset is sign-extended and added to the address of the jump 
#       instruction to form the jump target address. JAL can therefore target 
#       a ±1 MiB range.
#       JAL stores the address of the instruction following the jump (pc+4) into
#       register RD.
# Note:
#       When programming, we just provide label instead of immediate value, and
#       leave linker to provide the final immediate value.
#
# Jump And Link Register
# Format:
#       JALR RD, RS1, IMM
# Description:
#       This instruction is used to call a subroutine (i.e., function). 
#       The indirect jump instruction JALR (jump and link register) uses the 
#       I-type encoding. The target address is obtained by adding the 
#       sign-extended 12-bit I-immediate to the register RS1, then setting
#       the least-significant bit of the result to zero. JALR can therefore target 
#       a ±1 KiB range, relative to the address in RS1.
#       The address of the instruction following the jump(pc+4) is written to register RD.
#
# Sample to demo how to call a sub-function and return from it
#
# int a = 1;
# int b = 1;
#
# void sum()
# {
#     a = a + b;
# } 
#
# void _start()
# {
#     sum();
# }

        .text                   # Define beginning of text section
        .global _start          # Define entry _start

_start:
        li x6, 1
        li x7, 2
        jal sum         # call sum, return address is saved in x5

stop:
        j stop                  # Infinite loop to stop execution

sum:
        add x6, x6, x7          # x6 = x6 + x7
        jalr x0                 # return

        .end                    # End of file
复制代码

 

复制代码
 cat text2.txt 

test:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <_start>:
   0:   00100313                li      t1,1
   4:   00200393                li      t2,2
   8:   008000ef                jal     ra,10 <sum>

000000000000000c <stop>:
   c:   0000006f                j       c <stop>

0000000000000010 <sum>:
  10:   00730333                add     t1,t1,t2
  14:   000000e7                jalr    zero # 0 <_start>

Disassembly of section .riscv.attributes:

0000000000000000 <.riscv.attributes>:
   0:   2d41                    addiw   s10,s10,16
   2:   0000                    unimp
   4:   7200                    ld      s0,32(a2)
   6:   7369                    lui     t1,0xffffa
   8:   01007663                bgeu    zero,a6,14 <.riscv.attributes+0x14>
   c:   00000023                sb      zero,0(zero) # 0 <.riscv.attributes>
  10:   7205                    lui     tp,0xfffe1
  12:   3676                    fld     fa2,376(sp)
  14:   6934                    ld      a3,80(a0)
  16:   7032                    0x7032
  18:   5f30                    lw      a2,120(a4)
  1a:   326d                    addiw   tp,tp,-5
  1c:   3070                    fld     fa2,224(s0)
  1e:   615f 7032 5f30          0x5f307032615f
  24:   3266                    fld     ft4,120(sp)
  26:   3070                    fld     fa2,224(s0)
  28:   645f 7032 0030          0x307032645f
复制代码

 

jalr的用法

属于I型指令

 

 

 

ecall 后的 freertos_risc_v_trap_handler

复制代码
1. ecall // 此时的 寄存器状态为A(包括  ra  sp  gp  tp  t0  t1  t2  s0  s1  a0  a1  a2  a3  a4  a5  a6  a7  s2  s3  s4  s5  s6  s7  s8  s9  s10 s11 t3  t4  t5  t6  pc )
    
    此时硬件做动作
        mcause 被设置成 0xb
        mepc 被设置成 0x80001d28

2. freertos_risc_v_trap_handler // 此时寄存器状态为B // A和B  除了pc,其他都一样
freertos_risc_v_trap_handler 

    // 1. 保存 寄存器信息 到 sp
    addi sp, sp, -portCONTEXT_SIZE // sp = sp - 120 // 可以保存 30个 寄存器
    store_x x1, 1 * portWORD_SIZE( sp ) // 保存x1(第一个寄存器) 到 sp , sp = sp +4 // sw  ra,4(sp)
    store_x x5, 2 * portWORD_SIZE( sp ) // 保存x2(第二个寄存器)
    ...
    store_x x31, 28 * portWORD_SIZE( sp ) // 保存x31(第28个寄存器)
    
    csrr t0, mstatus
    store_x t0, 29 * portWORD_SIZE( sp ) // 保存 mstatus(第29个寄存器)到sp
    
    portasmSAVE_ADDITIONAL_REGISTERS // 用户自定义的 保存寄存器 的 指令,一般为空
    
    // 2. 保存sp 到 当前的 TCB
    load_x  t0, pxCurrentTCB
    store_x  sp, 0( t0 )             // 保存 sp 到 pxCurrentTCB
    
    // 3. 此时 保存完毕,开始处理异常

    // 4. 读 mcause mepc
    csrr a0, mcause
    csrr a1, mepc
    

    // 5. 判断 同步还是异步
    test_if_asynchronous:
    srli a2, a0, __riscv_xlen - 1 // 高 16 位存储到 a2
    beq a2, x0, handle_synchronous // 如果 a2 等0 ,跳转到 handle_synchronous  , 实际上ecall ,会走 handle_synchronous  
    
    // 6. 处理同步
    handle_synchronous:
    addi a1, a1, 4 // 处理返回地址 ,返回地址 = mepc +4
    store_x a1, 0( sp ) // 填充到 返回地址 到  sp 指向的内存
    
    // 7. 判断是不是 ecall
    test_if_environment_call:
    li t0, 11 // 将 11 放到 t0 // 11 表示 Environment call from M-mode
    bne a0, t0, is_exception // 如果 a0(即mcause)等于 t0(即11) , 那么就 不跳到 is_exception ,而是继续往下
    -------------------------------------------------------------------------------------------//此时切换了sp
    // 从 0x80082cf8 -> 0x80091da0
    // 为什么在此时才切换sp
    // 往前, 刚保存完 返回地址的recipe , 又往后 做了 三个汇编指令(该三条汇编指令用不到sp,也不会影响sp)
    // 往后, 要用sp 调用 C代码vTaskSwitchContext , vTaskSwitchContext 中会改动 sp
    // 所以 在此时 切换sp
    load_x sp, xISRStackTop // 设置 sp 为 xISRStackTop 
    -------------------------------------------------------------------------------------------
    

    // 8. 选择下一个 TCB , 将其 赋值给 pxCurrentTCB 
    jal vTaskSwitchContext
        taskSELECT_HIGHEST_PRIORITY_TASK/__clzSI2(UWtype x) 
            // __clzSI2 来自于crosstool-ng-crosstool-ng-1.24.0/.build/riscv64-unknown-elf/src/gcc/libgcc/libgcc2.c
            // 参数x 为 7
            count_leading_zeros (ret, x); // 这里切换了 pxCurrentTCB 
    
    // 9. load下个任务
    j processed_source
        // 获取(恢复) 下个任务 的 sp // 此时 恢复的 sp 中 保存的 是   寄存器状态 值
        load_x  t1, pxCurrentTCB // 保存 pxCurrentTCB  到 t1
        load_x  sp, 0( t1 )     //  从 TCB 中读取 sp
        
        // 获取(恢复) 下个任务 的 mepc
        load_x t0, 0( sp )
        csrw mepc, t0
        
        // 获取(恢复) 下个任务 的 mstatus
        load_x  t0, 29 * portWORD_SIZE( sp )
        csrw mstatus, t0
        
        // 恢复 各个寄存器
        load_x  x1, 1 * portWORD_SIZE( sp )
        load_x  x5, 2 * portWORD_SIZE( sp )
        ...
        load_x  x31, 28 * portWORD_SIZE( sp )

        // 恢复 下个任务的 sp // 此时 恢复的 sp 中 保存的 是  运行状态的 函数堆栈 值
        addi sp, sp, portCONTEXT_SIZE
        
        // 返回任务
        mret
            // 该指令后,下一条指令就是 prvQueueReceiveTask 任务
        

复制代码

 

 

 

 

 

 

 

 

 

 

 

 

 

FreeRTOS原理剖析:任务挂起和恢复

FreeRTOS源码探析之——任务调度相关

FreeRTOS高级篇4---FreeRTOS任务切换分析

FreeRTOS任务调度机制

FreeRTOS源码探析之——软件定时器

 

posted on   tycoon3  阅读(1102)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示