• 动手写简单的嵌入式操作系统一


    业余时间想研究一下RTOS,但是现有的嵌入式系统很多,代码量也很大,厚厚的一本书,又是任务控制块,又是链表又是指针的指来指去,让人不耐心点根本看不下去,也没太多时间去研究。于是就有了自己动手去做的想法,这样可以提高兴趣.比看书有意思。慢慢的发现,操作系统也没有那么神秘。触发软中断,保存堆栈,开始进行任务切换。于是一个多任务就出来了,但是一个完整的操作系统并不简单,涉及到一系列的算法和数据结构的运用,还有系统的引导程序bootloader,内存管理,文件系统,网络管理,IO驱动管理等模块。

          有了想法,接下来就是付诸行动。但是还得学习汇编,这成了最大阻碍,工作任务多,下班后,没太多精力去学习它。不过只要能看的懂就可以。于是把ucos/II在stm32上移植部分的汇编代码招搬过来直接利用。这样可以把主要精力放在任务调度和任务间的同步和通讯上。这次任务创建和调度的原理很简单,效率肯定也不高。以后有更好的想法了,打算改进一下任务的调度算法,比如可以利用linux内核中的list_head双向循环链表,加入就绪队列和任务延时队列。利用keilRTX系统中的内存分配机制,动态allox()分配任务的TCB控制块,总之,多学习好的系统中的思想。

    以下是汇编的代码OS_CPU_A.ASM,完成任务之间的切换和堆栈的保存。

    主要是两个堆栈指针OSCurTCB,OSNewTCB。可以把主要精力放在用c写任务调度和任务间的同步与通信,

     

    利用以下汇编代码,就可以暂时不管汇编部分,该任务调度时调用OSCtxSw(),传递最高优先级任务的堆栈指针给OSNewTCB,完成两个任务切换。

     

    [cpp] view plain copy
    1. ;/*********************** (C) COPYRIGHT 2010 Libraworks ************************* 
    2. ;* File Name : os_cpu_a.asm  
    3. ;* Author  : Librae  
    4. ;* Version  : V1.0 
    5. ;* Date   : 06/10/2010 
    6. ;* Description : μCOS-II asm port for STM32 
    7. ;*******************************************************************************/  
    8.   IMPORT  OSCurTCB                    ; External references  
    9.   IMPORT  OSNewTCB  
    10.   IMPORT  OSTaskSwHook  
    11.                      
    12.   EXPORT  OSStartTask  
    13.   EXPORT  OSCtxSw  
    14.   EXPORT  OSIntCtxSw  
    15.   EXPORT  OS_CPU_SR_Save                       ; Functions declared in this file  
    16.   EXPORT  OS_CPU_SR_Restore         
    17.   EXPORT  PendSV_Handler   
    18.        
    19. NVIC_PENDSV_PRI  EQU     0xFFFF0000               ; PendSV priority value (lowest)  
    20. NVIC_PENDSVSET   EQU     0x10000000               ; value to trigger PendSV exception  
    21. NVIC_INT_CTRL    EQU     0xE000ED04               ; interrupt control state register  
    22. NVIC_SYSPRI2     EQU     0xE000ED20               ; system priority register (2)  
    23.   
    24.   PRESERVE8   
    25.     
    26.   AREA    |.text|, CODE, READONLY  
    27.         THUMB   
    28.       
    29.              
    30. ;********************************************************************************************************  
    31. ;                                   CRITICAL SECTION METHOD 3 FUNCTIONS  
    32. ;  
    33. ;********************************************************************************************************  
    34. OS_CPU_SR_Save  
    35.     MRS     R0, PRIMASK   ;读取PRIMASK到R0,R0为返回值   
    36.     CPSID   I    ;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)  
    37.     BX      LR       ;返回  
    38. OS_CPU_SR_Restore  
    39.     MSR     PRIMASK, R0     ;读取R0到PRIMASK中,R0为参数;open interrupt  
    40.     BX      LR    ;返回  
    41.   
    42. ;/************************************************************************************** 
    43. ;* 函数名称: OSStartTask 
    44. ;* 
    45. ;* 功能描述: 使用调度器运行第一个任务 
    46. ;*  
    47. ;* 参    数: None 
    48. ;* 
    49. ;* 返 回 值: None 
    50. ;**************************************************************************************/    
    51. OSStartTask  
    52.         LDR     R4, =NVIC_SYSPRI2      ; set the PendSV exception priority  
    53.         LDR     R5, =NVIC_PENDSV_PRI  
    54.         STR     R5, [R4]  
    55.         MOV     R4, #0                 ; set the PSP to 0 for initial context switch call  
    56.         MSR     PSP, R4  
    57.                                        ;切换到最高优先级的任务  
    58.         LDR     R4, =NVIC_INT_CTRL     ;rigger the PendSV exception (causes context switch)  
    59.         LDR     R5, =NVIC_PENDSVSET    ;触发PendSV异常 (causes context switch)  
    60.         STR     R5, [R4]  
    61.         CPSIE   I                      ;enable interrupts at processor level  
    62. OSStartHang  
    63.         B       OSStartHang            ;should never get here  
    64. ;/************************************************************************************** 
    65. ;* 函数名称: OSCtxSw 
    66. ;* 
    67. ;* 功能描述: 任务级上下文切换          
    68. ;* 
    69. ;* 参    数: None 
    70. ;* 
    71. ;* 返 回 值: None 
    72. ;***************************************************************************************/  
    73.     
    74. OSCtxSw  
    75.   PUSH    {R4, R5}  
    76.         LDR     R4, =NVIC_INT_CTRL   ;触发PendSV异常 (causes context switch)  
    77.         LDR     R5, =NVIC_PENDSVSET  
    78.         STR     R5, [R4]  
    79.   POP     {R4, R5}  
    80.         BX      LR  
    81.   NOP  
    82. ;/************************************************************************************** 
    83. ;* 函数名称: OSIntCtxSw 
    84. ;* 
    85. ;* 功能描述: 中断级任务切换 
    86. ;* 
    87. ;* 参    数: None 
    88. ;* 
    89. ;* 返 回 值: None 
    90. ;***************************************************************************************/  
    91. OSIntCtxSw  
    92.   PUSH    {R4, R5}  
    93.         LDR     R4, =NVIC_INT_CTRL      ;触发PendSV异常 (causes context switch)  
    94.         LDR     R5, =NVIC_PENDSVSET  
    95.         STR     R5, [R4]  
    96.   POP     {R4, R5}  
    97.         BX      LR  
    98.         NOP  
    99. ;/************************************************************************************** 
    100. ;* 函数名称: OSPendSV 
    101. ;* 
    102. ;* 功能描述: OSPendSV is used to cause a context switch. 
    103. ;* 
    104. ;* 参    数: None 
    105. ;* 
    106. ;* 返 回 值: None 
    107. ;***************************************************************************************/  
    108. PendSV_Handler  
    109.         CPSID   I                                   ; Prevent interruption during context switch  
    110.         MRS     R0, PSP                             ; PSP is process stack pointer 如果在用PSP堆栈,则可以忽略保存寄存器,参考CM3权威中的双堆栈-白菜注  
    111.         CBZ     R0, PendSV_Handler_Nosave           ; Skip register save the first time  
    112.         SUBS    R0, R0, #0x20           
    113.         STM     R0, {R4-R11}                        ; Save remaining regs r4-11 on process stack  
    114.         LDR     R1, =OSCurTCB      
    115.         LDR     R1, [R1]  
    116.         STR     R0, [R1]                             ; R0 is SP of process being switched out            
    117. PendSV_Handler_Nosave  
    118.         PUSH    {R14}                                ; Save LR exc_return value  
    119.         LDR     R0, =OSTaskSwHook                    ; OSTaskSwHook();  
    120.         BLX     R0  
    121.         POP     {R14}  
    122.         LDR     R0, =OSCurTCB                         ;OSCurTCB=OSNewTCB;       
    123.         LDR     R1, =OSNewTCB  
    124.         LDR     R2, [R1]  
    125.         STR     R2, [R0]  
    126.         LDR     R0, [R2]                              ; R0 is new process SP; SP = OSCurTCB;         
    127.         LDM     R0, {R4-R11}                ; Restore r4-11 from new process stac  
    128.         ADD     R0, R0, #0x20  
    129.         MSR     PSP, R0               
    130.         ORR     LR, LR, #0x04  
    131.   CPSIE   I                                 ; Exception return will restore remaining context  
    132.         BX      LR                     
    133.   end    

    接下来定义一个任务控制块:

     

    [cpp] view plain copy
    1. typedef struct taskControlBlock  
    2. {  
    3.  /*当前的栈顶指针*/  
    4.  OS_STK     *pStackTop;    
    5.  /*当前优先级*/  
    6.  PRIO_TYPE    CurPriority;   
    7.  /*任务状态*/  
    8.  uint8      TaskStat;  
    9.  /*等待时间片的个数*/  
    10.  int32     TCBDelay;  
    11.    
    12. } TCB;  
    13. /*当前运行的任务*/  
    14. TCB   *OSCurTCB;  
    15. /*当前准备新运行的任务*/  
    16. TCB   *OSNewTCB;  
    17. /*当前OS中所有的任务*/  
    18. uint8  TaskNUM=0;  
    19. TCB   OSTCBTable[MAX_TASK_NUM];  

     

    OSCurTCB和OSNewTCB分别是当前运行任务的堆栈指针和要运行的新任务的堆栈指针。

    下一步就是任务的创建了,任务是如何创建的。每个任务都有自己的堆栈空间,就像是单独占用CPU一样,所以创建任务还需完成任务堆栈的初始化。

    需要知道CPU是如何出栈压栈的,保存了哪些寄存器,顺序是什么,堆栈的增长方向是什么。参考《cortexM3权威指南》,书中有详细的介绍。

     

    以下是c语言写的任务堆栈的初始化函数,位于文件OS_CPU.c中,如果需要移植,除了汇编部分OS_CPU_A.asm文件修改外,OS_CPU.c和OS_TYPE.h等文件也需要修改。仅这几个文件。
    OS_STK实际上就是int32,因为stm32上堆栈指针就是32位长度。第一个参数是任务的地址,即函数的地址,第二个参数是任务的堆栈指针。

     

    [cpp] view plain copy
    1. OS_STK *OSTaskStkInit (void (*task),OS_STK *ptos)  
    2. {  
    3.     OS_STK *stk;  
    4.     stk    = ptos;                          /* get stack point       */  
    5.     *(stk)    = (uint32)0x01000000L;        /* xPSR                  */  
    6.     *(--stk)  = (uint32)task;               /* Entry Point           */  
    7.     *(--stk)  = (uint32)0xFFFFFFFEL;        /* R14 (LR)              */  
    8.     *(--stk)  = (uint32)0x12121212L;        /* R12                   */  
    9.     *(--stk)  = (uint32)0x03030303L;        /* R3                    */  
    10.     *(--stk)  = (uint32)0x02020202L;        /* R2                    */  
    11.     *(--stk)  = (uint32)0x01010101L;        /* R1                    */  
    12.     *(--stk)  = (uint32)0;                  /* R0 : argument         */  
    13.                                             /* Remaining registers   */  
    14.     *(--stk)  = (uint32)0x11111111L;        /* R11                   */  
    15.     *(--stk)  = (uint32)0x10101010L;        /* R10                   */  
    16.     *(--stk)  = (uint32)0x09090909L;        /* R9                    */  
    17.     *(--stk)  = (uint32)0x08080808L;        /* R8                    */  
    18.     *(--stk)  = (uint32)0x07070707L;        /* R7                    */  
    19.     *(--stk)  = (uint32)0x06060606L;        /* R6                    */  
    20.     *(--stk)  = (uint32)0x05050505L;        /* R5                    */  
    21.     *(--stk)  = (uint32)0x04040404L;        /* R4                    */  
    22.     return (stk);  
    23. }  
    [cpp] view plain copy
    1. /* 
    2.  * 创建新的任务 
    3. */  
    4. TCB*  OSTaskCreate(void* task, OS_STK *stack,PRIO_TYPE prio)  
    5. {  
    6.  TCB *pTCB;  
    7.  OS_CPU_SR  cpu_sr = 0;  
    8.    
    9.  OS_ENTER_CRITICAL();  
    10.    
    11.  pTCB = OSGetFreeTCB(prio);  
    12.  if (NULL == pTCB)  
    13.  {  
    14.   OS_EXIT_CRITICAL();  
    15.   return NULL;  
    16.  }  
    17.  pTCB->pStackTop = OSTaskStkInit(task, stack);  
    18.  pTCB->CurPriority = prio;  
    19.  pTCB->TCBDelay = 0;  
    20.    
    21.  TaskNUM++;  
    22.  OS_EXIT_CRITICAL();  
    23.  return pTCB;  
    24. }  

     

    创建新任务函数很简单,只要懂了OSGetFreeTCB(prio);这个函数就没啥问题。创建的任务,是一个有序的表,就是一个存储元素为
    TCB类型的数组TCB  OSTCBTable[MAX_TASK_NUM];在这个数组中,先在OSTCBTable[0]中创建一个任务,如果再创建一个任务,这个任务比上个任务的优先级高,那么OSTCBTable[0]中存储优先级高的任务,那个之前先创建的低优先级的任务搬移到OSTCBTable[1]中。如果再创建一个任务,任务优先级会与前两个任务对比,若比前两个都低,就放在第三个位置OSTCBTable[2]中,否则就重新排序,总之,使OSTCBTable数组中的任务始终是按优先级从高到低的顺序排列。
    以下是这种思想实现的OSGetFreeTCB(prio)函数:

     

    [cpp] view plain copy
    1. /*在TCB表中取一个空闲的节点,给新任务使用*/  
    2. /*对OSTCBTable表这个有序表进行插入排序*/  
    3. /*将优先级高的任务放在前面*/  
    4. TCB* OSGetFreeTCB(PRIO_TYPE prio)  
    5. {  
    6.  TCB *pTCB;  
    7.  int32 index=0,orgIndex;  
    8.  pTCB = &(OSTCBTable[0]);  
    9.  for (index = 0;index < TaskNUM+1;index++)  
    10.  {  
    11.   pTCB = OSTCBTable+index;  
    12.   /*已经是空闲TCB了,就直接使用*/  
    13.   if (NULL == pTCB->pStackTop)  
    14.   {  
    15.    return (TCB*)pTCB;  
    16.   }  
    17.   /*若新任务优先级比较低或相等,则向后继续找*/  
    18.   if (pTCB->CurPriority >= prio)  
    19.   {  
    20.    continue;  
    21.   }  
    22.   else /*pTCB->CurPriority < prio 找到了该插入的位置了*/  
    23.   {    
    24.    /*保存当前位置*/  
    25.    orgIndex = index;  
    26.    /*从当前节点遍历到最后一个使用了的节点*/  
    27.    for( index = TaskNUM ; index > orgIndex ; index-- )  
    28.    {  
    29.     pTCB = OSTCBTable+index;  
    30.     /*将前一个节点的数据,保存到当前节点*/  
    31.     _mem_copy((uint8 *)(pTCB),(uint8 *)(pTCB-1),sizeof(TCB));  
    32.    }  
    33.    _mem_clr((uint8 *)(pTCB-1),sizeof(TCB))  ;  
    34.       
    35.    return (TCB*)(pTCB-1);    
    36.   }  
    37.  }  
    38.  return (TCB*)NULL;  
    39. }  

    任务创建是基于一个有序表。这种方法虽然简单直观,但也有很大缺点。比如如果建立了100个任务,又准备建立第101个任务,且第101个任务优先级是最高的。那么创建任务就很慢,需要前面一百个任务依次都向后移,第101个任务放在数组的最前面。这是十分耗时的操作,随着任务数的增加,创建任务的时间也随着增加。后面还要讲到,这种方法建立的任务查找最高优先级时,需要遍历数组,在效率上也是不快的,尤其是任务数目多时。

    任务创建之后,接下来的事情就是何时需要任务切换,如何查找最高优先级了。先说下何时需要任务切换,每个任务都是一个while(1)死循环,在里面执行到OSTimedly()时就会挂起当前任务,查找最高优先级的任务。每个任务的控制块中都有个延时时间的变量,当这个延时时间变量大于0,说明这个任务还处于睡眠或挂起状态,不能够被执行。因此还需要一个系统时钟函数,作为整个系统的调度中心,每到一个系统时钟中断,让所有任务的延时时间减一。

     

    [cpp] view plain copy
    1. /* 
    2.  *系统时钟函数,在时钟中断中调用 
    3. */  
    4. void  OSTimeTick (void)  
    5. {  
    6.     int8 index;  
    7.     TCB  *pTCB;  
    8.     uint8 flagFirstTask=0;  
    9.     OS_CPU_SR  cpu_sr = 0;  
    10.     OS_ENTER_CRITICAL();  
    11.     /*初始化*/  
    12.     OSNewTCB = NULL;  
    13.  /*禁止调度*/  
    14.     if (OSScheLock != 0)  
    15.     {  
    16.        OS_EXIT_CRITICAL();  
    17.        return;  
    18.     }  
    19.  for (index = 0;index < TaskNUM;index++)  
    20.  {  
    21.      pTCB = OSTCBTable+index;  
    22.     /*该任务在睡眠状态,必须将所有时延都--*/  
    23.      if (pTCB->TCBDelay > 0)  
    24.      {  
    25.         pTCB->TCBDelay--;  
    26.         continue;  
    27.      }  
    28.      /*该任务被挂起*/  
    29.      if (pTCB->TaskStat == OS_Task_Pend)   
    30.      {  
    31.          continue;  
    32.      }  
    33.     /*任务优先级查找算法,以后考虑改进查找速度*/  
    34.    /* 是否找到了应该调度进去的就绪任务*/  
    35.   if (flagFirstTask==0)  
    36.   {  
    37.    /*找到了最高优先级的任务, 
    38.      并且比当前任务优先级高*/  
    39.      if (OSCurTCB->CurPriority < pTCB->CurPriority)  
    40.      {  
    41.         flagFirstTask = 1;  
    42.         OSNewTCB = pTCB;  
    43.         continue;  
    44.      }  
    45.     /*找到了比当前优先级低的任务*/  
    46.     if (OSCurTCB->CurPriority > pTCB->CurPriority)  
    47.    {  
    48.        if (OSNewTCB == NULL)  
    49.       {  
    50.           flagFirstTask = 1;  
    51.           OSNewTCB = pTCB;  
    52.           continue  ;   
    53.       }  
    54.       else  
    55.      {  
    56.          flagFirstTask = 1;  
    57.         continue  ;    
    58.       }  
    59.    }  
    60.      
    61.    /*找到了最高优先级的任务, 
    62.      并且跟当前任务优先级相等*/  
    63.    if (OSCurTCB->CurPriority == pTCB->CurPriority)  
    64.    {  
    65.     /*该任务在当前任务之后*/  
    66.     if ((pTCB > OSCurTCB)||(pTCB == OSCurTCB))  
    67.     {   
    68.      flagFirstTask = 1;  
    69.      OSNewTCB = pTCB;  
    70.      continue  ;    
    71.     }   
    72.     /*在当前任务之前或者就是当前任务 
    73.       则还需要继续向后搜索第一个同优先级的任务*/  
    74.     if ((pTCB < OSCurTCB)&&(OSNewTCB == NULL))  
    75.     {  
    76.       OSNewTCB = pTCB;  
    77.       continue;  
    78.     }  
    79.    }  
    80.    continue;  
    81.   }  
    82.     
    83.  }  
    84.  OS_EXIT_CRITICAL();  
    85. }  

    在时钟中断里,需要调用这个函数,这个函数的作用很简单,一方面让每个任务的延时时间减一,一方面查找最高优先级的任务,找到了最高优先级的任务时,就把 OSNewTCB = pTCB;把最高优先级的任务堆栈指针赋给 OSNewTCB 。

    [cpp] view plain copy
    1. void SysTick_Handler(void)  
    2. {  
    3.  OSIntEnter();  //进入中断  
    4.  OSTimeTick();  
    5.  OSIntExit();        //触发任务切换软中断  
    6. }  
    [cpp] view plain copy
    1. /* 
    2. *记录中断嵌套层数 
    3. */  
    4. void  OSIntEnter (void)  
    5. {  
    6.     if (NULL != OSCurTCB)   
    7.    {  
    8.         if (OSIntNesting < 255u)   
    9.         {  
    10.             OSIntNesting++;                      /* Increment ISR nesting level                        */  
    11.         }  
    12.     }  
    13. }  
    [cpp] view plain copy
    1. /* 
    2. *中断退出时调用,触发中断级任务切换 
    3. */  
    4. void  OSIntExit (void)  
    5. {  
    6.      OS_CPU_SR  cpu_sr = 0u;  
    7.     if (NULL!= OSCurTCB)  
    8.     {  
    9.         OS_ENTER_CRITICAL();  
    10.         if (OSIntNesting > 0u)  
    11.         {                             
    12.             OSIntNesting--;  
    13.         }  
    14.         if (OSIntNesting == 0u)   
    15.         {                            
    16.             /* 当所有的中断完成候再判断是否调度  */   
    17.             if (OSNewTCB != OSCurTCB)  
    18.             {  
    19.               /* 中断级任务切换  */  
    20.               OSIntCtxSw();      
    21.             }   
    22.         }  
    23.         OS_EXIT_CRITICAL();  
    24.     }  
    25. }  

    在时钟中断里,退出时调用 OSIntExit();        //触发任务切换软中断

    在这个函数中,比较当前任务指针是否发生了改变,若发生了改变,说明需要进行任务切换了。
    下面再看看延时函数OSTimeDly (int32 ticks)

     

    [cpp] view plain copy
    1. void  OSTimeDly (int32 ticks)  
    2. {  
    3.     OS_CPU_SR  cpu_sr = 0;  
    4.     int8   index;  
    5.     TCB    *pTCB;  
    6.    
    7.     OS_ENTER_CRITICAL();  
    8.     OSCurTCB->TCBDelay = ticks;  
    9.     OSNewTCB = NULL;  
    10.    /*任务优先级查找算法,从当前任务 
    11.     向后遍历,第一个最大的优先级的任务 
    12.     就是需要调度进去的任务*/  
    13.     for (index = 0; index < TaskNUM;index++)  
    14.     {  
    15.        pTCB = OSTCBTable+index;  
    16.        /*跳过睡眠任务*/  
    17.        if (pTCB->TCBDelay != 0)  
    18.        {  
    19.            continue;  
    20.        }  
    21.        /*跳过挂起任务*/  
    22.        if  (pTCB->TaskStat == OS_Task_Pend)   
    23.        {      
    24.             continue;  
    25.        }  
    26.       /*找到了最高优先级的任务, 
    27.        并且比当前任务优先级高*/  
    28.       if (OSCurTCB->CurPriority < pTCB->CurPriority)  
    29.      {  
    30.          OSNewTCB = pTCB;  
    31.          break;  
    32.       }  
    33.       /*找到了比当前优先级低的任务*/  
    34.      if (OSCurTCB->CurPriority > pTCB->CurPriority)  
    35.      {  
    36.          /*如果当前任务之前有同优先级的就绪任务, 
    37.          则选择该任务,否则就使用*/  
    38.         if (OSNewTCB == NULL)  
    39.         {  
    40.             OSNewTCB = pTCB;  
    41.         }          
    42.         break;  
    43.   }  
    44.      
    45.   /*找到了最高优先级的任务, 
    46.     并且跟当前任务优先级相等*/  
    47.   if (OSCurTCB->CurPriority == pTCB->CurPriority)  
    48.   {  
    49.      /*该任务在当前任务之后*/  
    50.      if ((pTCB > OSCurTCB))  
    51.      {   
    52.           OSNewTCB = pTCB;  
    53.           break  ;    
    54.      }   
    55.    /*在当前任务之前或者就是当前任务 
    56.      则还需要继续向后搜索第一个同优先级的任务*/  
    57.     if (((pTCB < OSCurTCB)||(pTCB == OSCurTCB))  
    58.         &&(OSNewTCB == NULL))  
    59.     {  
    60.          OSNewTCB = pTCB;  
    61.          continue;  
    62.     }  
    63.   }  
    64.    
    65.  }  
    66.  OS_EXIT_CRITICAL();  
    67.  OSTaskSche();   
    68. }  

    延时函数也很简单,就是一方面把需要延时的时间给任务控制结构体中的延时变量,一方面查找最高优先级的任务开始进行任务切换。以上就完成了简单的任务切换和调度。从上面可以看出,查找效率是很低的,尤其是任务数目多的时候,需要从头到尾的遍历一遍数组。创建任务和查找高优先级的任务都有改进的空间,如果以后想到更好的更有效的方法再改一改,试一试。

    以上实现了简单的任务调度和切换。接下来,就是任务间如何进行同步和通讯...

    在CSDN资源中可以下载工程的源码,KEILMDK4.23的IDE

  • 相关阅读:
    UVa 128 Software CRC
    UVa 11258 String Partition(简单DP)
    POJ 3070 Fibonacci(矩阵乘法logN)
    UVa 10280 Old Wine Into New Bottles(剪枝+完全背包)
    图论笔记第四章 欧拉图与哈密尔顿图(beta.)考点
    图。。珍藏season
    图论及其应用哈密尔顿图(alpha)
    9.保健…todo
    android 移植笔记有感
    unp_exam_要点.doc
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/6511154.html
Copyright © 2020-2023  润新知