第4章第2节 定时器触发的实时抢占…

源码请在https://github.com/ifreecoding/MbedRtos.git下载

本节中新增的重要内容已经介绍的差不多了,下面我们来看看使用tick中断的任务上下文切换过程。

在本节中,tick中断被配置为使用Timer1产生IRQ中断。产生tick中断时,硬件会自动将PC指针指向IRQ中断向量表,IRQ中断向量里存放的是一条跳转指令,会跳转到IRQ中断服务函数,这个中断服务函数需要由我们来编写,它的功能就是备份、恢复寄存器组。在Mindows里我们使用MDS_TickContextSwitch函数来实现这个功能,在start.s文件里需要把中断向量表的IRQ中断服务函数修改为MDS_TickContextSwitch。

由于C函数会自动生成入栈出栈的指令,任务切换时调用C函数会破坏寄存器组中的数据,因此,MDS_TickContextSwitch函数需要使用汇编语言来写。备份、恢复寄存器组的原理与Wanlix中讲述是一样的,由于是在IRQ中断中执行的,实现的细节上还是有区别的,我们来看代码:

00013      .func MDS_TickContextSwitch

00014  MDS_TickContextSwitch:

00015  

00016      @保存接口寄存器

00017      STMDB  R13!, {R0 - R3, R12, R14}

00018  

00019      @调用C语言TICK中断处理函数

00020      LDR    R0, =MDS_TickIsr

00021      MOV    R14, PC

00022      BX     R0

00023  

00024      @保存当前任务的堆栈信息

00025      LDR    R0, =gpstrCurTaskSpAddr

00026      LDR    R14, [R0]

00027      MRS    R0, SPSR

00028      STMIA  R14!, {R0}

00029      LDMIA  R13!, {R0 - R3, R12}

00030      STMIA  R14, {R0 - R14}^

00031      ADD    R14, R14, #0x3C

00032      LDMIA  R13!, {R0}

00033      STMIA  R14, {R0}

00034  

00035      @任务调度完毕, 恢复将要运行任务现场

00036      LDR    R0, =gpstrNextTaskSpAddr

00037      LDR    R14, [R0]

00038      LDMIA  R14, {R0}

00039      MSR    SPSR, R0

00040      LDMIB  R14, {R0 - R14}^

00041      NOP

00042      ADD    R14, R14, #0x40

00043      LDMIA  R14, {R14}

00044      SUBS   PC, R14, #4

00045  

00046      .endfunc

00017行,保存切换前任务的寄存器。本函数在第22行会调用MDS_TickIsr函数,在2.3节中我们已经介绍了AAPCS参数传递的规则,MDS_TickIsr函数可能会破坏接口寄存器,因此在本函数中需要保存R0~R3、R12寄存器。其余的寄存器,本函数在备份它们之前并没有破坏它们的数据,因此不需要备份。这段代码是在IRQ模式下执行的,此时的SP是SPIRQ,数据被保存到IRQ的栈中。

00020~00022行,执行MDS_TickIsr函数。MDS_TickIsr函数是使用C语言写的函数,这个函数里才是真正的IRQ中断处理函数,如果不使用操作系统,这个函数是应该直接挂到IRQ中断向量表上的。MDS_TickIsr函数会判断产生IRQ的中断源,根据IRQ不同的中断源走不同的程序分支,其中包括产生tick中断的Timer1中断。MDS_TickIsr函数最终调用了MDS_TaskSched函数,做任务切换前的准备工作,包括获取将要运行的最高优先级任务,将切换前任务和切换后任务的寄存器组所在的地址存入全局变量gpstrCurTaskSpAddr和gpstrNextTaskSpAddr中等操作。

00025行,将全局变量gpstrCurTaskSpAddr的地址放入R0,gpstrCurTaskSpAddr的内容是切换前任务的寄存器组存放在栈中的地址。

00026行,将切换前任务的寄存器组存放在栈中的地址赋给LR寄存器,注意,这段程序是在IRQ中断中执行的,这个LR是LRIRQ。

00027行,将SPSRIRQ也就是CPSRUSR的值存入R0。

00028行,将R0的内容存入LRIRQ指向的地址,也就是将CPSRUSR存入切换前任务的寄存器组所在栈中的第一个地址,也就是将切换前任务的CPSRUSR存入到切换前任务TCB中的寄存器组中的CPSR的位置,这条指令就是备份寄存器组中的CPSR。

00029行,从IRQ堆栈中恢复R0~R3和R12寄存器,执行完这条指令,USR模式下的R0~R14都已经恢复为中断发生那一时刻的值了。

00030行,将USR模式下的R0~R14备份到栈中的寄存器组中对应的位置。这条指令的第一个参数LR是LRIRQ,指向的是切换前任务的栈中寄存器组的R0所在位置,第二个参数里的LR是LRUSR,因为这条指令里有“^”符号,这个我们在2.2节中介绍过。

00031行,将LRIRQ更新到栈中的寄存器组的PC所在位置。

00032行,从IRQ栈中取出中断发生时的LRIRQ。在17行时向IRQ栈存入了6个寄存器,在29行取出了5个,剩下的最后一个LRIRQ在这里取出,它的值就是中断返回时的PC值。

00033行,将LRIRQ存入切换前任务的栈中寄存器组的PC位置,也就是将中断返回的PC值存入栈中寄存器组的PC位置。

自此,备份寄存器组的工作已经完成。

00036行,将全局变量gpstrNextTaskSpAddr的地址放入R0,gpstrNextTaskSpAddr的内容是切换后任务的寄存器组存放在栈中的地址。

00037行,将切换后任务的寄存器组存放在栈中的地址赋给LRIRQ寄存器。

00038行,从切换后任务的栈中寄存器组取出CPSR的值,存入R0。

00039行,将CPSR的值赋给SPSRIRQ,这就是恢复CPSR寄存器的过程,但真正恢复CPSR的操作还需要从IRQ模式返回到USR模式时才能执行,那时候硬件会自动将SPSRIRQ中数值恢复到CPSR中。

00040行,从栈中寄存器组中恢复USR模式下的R0~R14寄存器,此时,USR模式下的R0~R14已经全部恢复为该任务上次切出去时候的值了。

00041行,NOP,指令空闲一个周期什么也不做。别小看这个NOP指令,当初就因为没有这条指令Mindows程序老是跑飞,卡住了我好几天,最后才定位到这里,但又找不到原因。观察41行和42行,这两行指令里都使用了LR寄存器,而且是LRUSR和LRIRQ连在一起使用的,估计可能是芯片内部指令总线上起了冲突才导致程序跑飞的,最后加了这条NOP指令程序才正常,但一直不知道为什么,没有查到相关的资料,哪位知道的话请到论坛上反馈一下,多谢!求真相!

00042行,将LRIRQ更新到切换后任务的栈中的寄存器组的PC所在位置。

00043行,将返回的PC值存入LRIRQ。

00044行,跳转到USR模式下中断前的下条指令继续执行,同时将SPSRIRQ中的数值恢复到CPSR中。IRQ中断发生时,会将刚执行完的指令的地址+8存入到LRIRQ中,当中断返回时,需要执行的是+4地址的指令,因此,从IRQ返回时需要使用SUBS指令将再LRIRQ-4存入到PC中,这样才能正好跳转到中断前的下条指令继续执行。这个过程在参考资料2中有描述。

下面我们来跟踪一个tick中断产生的过程,用以了解Mindows任务的调度流程,见图37:


 

图 37  Mindows任务调度流程

MDS_TickIsr、MDS_TaskTick函数比较简单,MDS_TaskSwitch函数与Wanlix中的这个函数没有本质的区别,这3个函数就不介绍了。我们来看看任务调度函数MDS_TaskSched:

00209  void MDS_TaskSched(void)

00210  {

00211      M_TCB* pstrTcb;

00212      M_TCBQUE* pstrTaskQue;

00213      U8 ucTaskPrio;

00214  

00215      

00216      ucTaskPrio = MDS_TaskHighestPrioGet(&gstrReadyTab.strFlag);

00217      pstrTaskQue = (M_TCBQUE*)MDS_ChainEmpInq(&gstrReadyTab.astrChain[ucTaskPrio]);

00218      pstrTcb = pstrTaskQue->pstrTcb;

00219  

00220      

00221      MDS_TaskSwitch(pstrTcb);

00222  }

00216行,从ready表标志中获取最高的优先级。

00217行,获取最高优先级对应的链表根节点,并将根节点的M_CHAIN型指针强制转换成M_TCBQUE型指针。

00218行,通过M_TCBQUE指针类型获取任务TCB的指针。

00221行,调用MDS_TaskSwitch函数,做寄存器组备份、恢复前的准备工作。

由于增加了ready表,在创建任务时需要对相关的变量进行初始化,MDS_TaskCreate函数代码如下:

00015  M_TCB* MDS_TaskCreate(VFUNC vfFuncPointer, U8* pucTaskStack, U32 uiStackSize,

00016                        U8 ucTaskPrio)

00017  {

00018      M_TCB* pstrTcb;

00019  

00020      

00021      if(NULL == vfFuncPointer)

00022      {

00023          

00024          return (M_TCB*)NULL;

00025      }

00026  

00027      

00028      if((NULL == pucTaskStack) || (0 == uiStackSize))

00029      {

00030          

00031          return (M_TCB*)NULL;

00032      }

00033  

00034      

00035      if(MDS_RootTask == vfFuncPointer)

00036      {

00037          

00038          if(NULL != gpstrRootTaskTcb)

00039          {

00040              return (M_TCB*)NULL;

00041          }

00042      }

00043  

00044      

00045      pstrTcb = MDS_TaskTcbInit(vfFuncPointer, pucTaskStack, uiStackSize, ucTaskPrio);

00046  

00047      return pstrTcb;

00048  }

00015行,函数返回值是新创建任务的TCB指针,若为NULL,代表创建任务失败,其它值则为创建任务成功,为新任务的TCB指针。入口参数ucTaskPrio是新创建任务的优先级,其它入口参数与以前一致,没有变化。

00021~00025行,入口参数判断,若创建任务的函数指针为NULL,返回失败。

00028~00032行,入口参数判断,若创建任务的堆栈不合法,返回失败。

00035~00042行,若重复创建根任务,则返回失败。根任务只能创建一次,在操作系统初始函数MDS_SystemVarInit里会将根任务的TCB指针gpstrRootTaskTcb初始化为NULL,当根任务创建成功后gpstrRootTaskTcb就被更改为根任务的TCB值,即便根任务运行结束后gpstrRootTaskTcb中保持的TCB值仍然不变,在创建任务时可根据gpstrRootTaskTcb来判断根任务是否被重复创建。

00045行,初始化任务的TCB。

00047行,任务创建成功,返回任务的TCB指针。

建立任务时的初始化操作主要集中在MDS_TaskTcbInit函数里面,由于寄存器组被放到了TCB里面,因此MDS_TaskStackInit函数也由MDS_TaskTcbInit函数调用,来看一下MDS_TaskTcbInit函数的代码:

00059  M_TCB* MDS_TaskTcbInit(VFUNC vfFuncPointer, U8* pucTaskStack, U32 uiStackSize,

00060                         U8 ucTaskPrio)

00061  {

00062      M_TCB* pstrTcb;

00063      M_CHAIN* pstrChain;

00064      M_CHAIN* pstrNode;

00065      M_PRIOFLAG* pstrPrioFlag;

00066      U8* pucStackBy4;

00067  

00068      

00069      pucStackBy4 = (U8*)(((U32)pucTaskStack + uiStackSize) & 0xFFFFFFFC);

00070  

00071      

00072      pstrTcb = (M_TCB*)(((U32)pucStackBy4 - sizeof(M_TCB)) & 0xFFFFFFFC);

00073  

00074      

00075      MDS_TaskStackInit(pstrTcb, vfFuncPointer);

00076  

00077      

00078      pstrTcb->strTcbQue.pstrTcb = pstrTcb;

00079  

00080      

00081      pstrTcb->ucTaskPrio = ucTaskPrio;

00082  

00083      pstrChain = &gstrReadyTab.astrChain[ucTaskPrio];

00084      pstrNode = &pstrTcb->strTcbQue.strQueHead;

00085      pstrPrioFlag = &gstrReadyTab.strFlag;

00086  

00087      

00088      (void)MDS_IntLock();

00089  

00090      

00091      MDS_TaskAddToSchedTab(pstrChain, pstrNode, pstrPrioFlag, ucTaskPrio);

00092  

00093      

00094      (void)MDS_IntUnlock();

00095  

00096      return pstrTcb;

00097  }

00059行,函数返回值是新创建任务的TCB指针,若为NULL,代表创建任务失败,其它值则为创建任务成功,为新任务的TCB指针。

00075行,初始化任务栈。

00078行,将任务TCB指针赋给TCB队列中的指针变量。

00081行,将任务的优先级保存到TCB中。

00083行,根据任务优先级,获取ready中同等的优先级链表根节点指针。

00084行,获取TCB中可加入ready链表的链表结构指针。

00085行,获取ready表的标志结构指针。

00088行,锁Timer1中断,防止执行下面的代码时发生任务调度。在91行会对ready表进行操作,修改它的链表和标志,这个过程是要分几个步骤进行的,ready表及其上面挂接的各个任务节点对所有任务都是同时可见的,如果在这过程中切换到了其它任务,而其它任务也来对ready表进行操作,那么ready表就可能会因为多个任务同时修改它的数据而导致数据异常。因此,我们要防止这种情况发生,必须保证每次只有一个任务对ready操作,当这个任务对ready表操作完成之后才能允许其它任务再次操作ready表。为了实现这个功能,我们可以使用MDS_IntLock函数将tick中断锁住,这样就不能产生tick中断了,因此也就不会发生任务调度了,因此也就保证了只有一个任务可以对ready进行操作,保证了ready表操作的串行性。在ready表操作完之后,需要调用MDS_IntUnlock函数打开tick中断,这样操作系统又可以继续进行任务调度了。注意,锁tick中断的过程中操作系统丧失了任务调度功能,因此锁tick中断的时间一定要尽可能的短。不但是tick中断,不但是在操作系统状态下,任何情况下,我们都需要将任何锁中断的时间做的尽可能的短。

00091行,将新建立的任务挂接到ready表对应的优先级链表中,新建立的任务处于ready态,准备运行。

00094行,解锁中断,恢复任务调度功能。

00096行,任务创建成功,返回新建任务的TCB指针。

前面说过进入IRQ时,存入LRIRQ的是刚执行完的指令地址+8,在退出中断时需要使用SUBS指令将LRIRQ-4存入PC寄存器,但在USR模式下函数调用时是没有这种+8和-4操作的,而Mindows操作系统从非操作系统状态转换为操作系统状态正是在USR模式下依靠函数调用实现的,开始运行第一个任务MDS_RootTask,但其它的任务第一次调用时则是依靠IRQ中断开始调度的,因此,在初始化寄存器组中的PC值时要分别对待,来看栈初始化函数MDS_TaskStackInit的代码:

00011  void MDS_TaskStackInit(M_TCB* pstrTcb, VFUNC vfFuncPointer)

00012  {

00013      STACKREG* pstrRegSp;

00014  

00015      pstrRegSp = &pstrTcb->strStackReg;          

00016  

00017      

00018      pstrRegSp->uiCpsr = MODE_USR;               

00019      pstrRegSp->uiR0 = 0;                        

00020      pstrRegSp->uiR1 = 0;                        

00021      pstrRegSp->uiR2 = 0;                        

00022      pstrRegSp->uiR3 = 0;                        

00023      pstrRegSp->uiR4 = 0;                        

00024      pstrRegSp->uiR5 = 0;                        

00025      pstrRegSp->uiR6 = 0;                        

00026      pstrRegSp->uiR7 = 0;                        

00027      pstrRegSp->uiR8 = 0;                        

00028      pstrRegSp->uiR9 = 0;                        

00029      pstrRegSp->uiR10 = 0;                       

00030      pstrRegSp->uiR11 = 0;                       

00031      pstrRegSp->uiR12 = 0;                       

00032      pstrRegSp->uiR13 = (U32)pstrTcb;            

00033      pstrRegSp->uiR14 = 0;                       

00034  

00035      

00036      if(MDS_RootTask != vfFuncPointer)

00037      {

00038          pstrRegSp->uiR15 = (U32)vfFuncPointer + 4;  

00039      }

00040      else    

00041      {

00042          pstrRegSp->uiR15 = (U32)vfFuncPointer;      

00043      }

00044  }

00011~00033行,初始化寄存器组中的CPSR、R0~R14,与以前一致,没有变化。

00036~00039行,判断创建的若不是root任务,则将创建任务所用函数的指针+4存入寄存器组中PC的位置。若创建的不是root任务,需要由IRQ中断开始调度,在IRQ中断返回时会使用SUBS指令将返回的PC地址-4,因此创建非root任务需要将PC多+4。

00042行,创建的任务是root任务,将创建任务所用函数的指针直接存入寄存器组中PC的位置。因为root任务是在USR模式下采用函数调用的方式开始运行的,不需要多+4。

上面介绍了本节中关键部分的代码,其它代码请读者自行参考源代码,这里不再介绍了。接下来,我们使用测试函数来看看本节的成果。

本节只引入了ready表,没有其它可以控制任务调度的方法,一旦最高优先级任务开始运行就无法切换到其它任务了。

我们使用3个测试函数TEST_TestTask1、TEST_TestTask2和TEST_TestTask3,每个函数都是循环执行“打印字符串,延迟时间”的操作。

00015  void TEST_TestTask1(void)

00016  {

00017      while(1)

00018      {

00019          DEV_PutString((U8*)"\r\nTask1 is running!");

00020  

00021          DEV_DelayMs(1000);              

00022      }

00023  }

00030  void TEST_TestTask2(void)

00031  {

00032      while(1)

00033      {

00034          DEV_PutString((U8*)"\r\nTask2 is running!");

00035  

00036          DEV_DelayMs(2000);              

00037      }

00038  }

00045  void TEST_TestTask3(void)

00046  {

00047      while(1)

00048      {

00049          DEV_PutString((U8*)"\r\nTask3 is running!");

00050  

00051          DEV_DelayMs(3000);              

00052      }

00053  }

这3个函数都由MDS_RootTask任务创建,创建后都延迟1秒。

00013  void MDS_RootTask(void)

00014  {

00015      

00016      DEV_SoftwareInit();

00017  

00018      

00019      DEV_HardwareInit();

00020  

00021      

00022      (void)MDS_TaskCreate((VFUNC)TEST_TestTask1, gaucTask1Stack, TASKSTACK, 3);

00023  

00024      DEV_DelayMs(1000);

00025  

00026      (void)MDS_TaskCreate((VFUNC)TEST_TestTask2, gaucTask2Stack, TASKSTACK, 2);

00027  

00028      DEV_DelayMs(1000);

00029  

00030      (void)MDS_TaskCreate((VFUNC)TEST_TestTask3, gaucTask3Stack, TASKSTACK, 0);

00031  

00032      DEV_DelayMs(1000);

00033  }

    我们将MDS_RootTask任务的优先级设为1,TEST_TestTask1任务的优先级设定为3,TEST_TestTask2任务的优先级设定为2,TEST_TestTask3任务的优先级设定为0,我们按照本节的任务调度方式,从系统上电运行开始,推算一下任务的切换过程,如图38:


 

图 38  4.2节测试任务执行过程

t0时刻,系统开始运行。

t1时刻,从非操作系统状态切换到操作系统状态,开始运行MDS_RootTask任务,这时候MDS_RootTask任务处于running态。在MDS_RootTask任务里初始化了tick中断,在tick中断里开始任务调度,此时,ready表中只有MDS_RootTask一个任务。

t2时刻,创建了TEST_TestTask1任务,此时ready表中有MDS_RootTask和TEST_TestTask1共2个任务,现在还是在运行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1任务处于ready态。

t3时刻,tick中断到来,调度任务,由于MDS_RootTask任务比TEST_TestTask1任务的优先级高,因此调度的结果还是执行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1任务处于ready态。

t3~t4之间不断产生tick中断,调度任务,由于MDS_RootTask任务比TEST_TestTask1任务的优先级高,因此调度的结果还是执行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1任务处于ready态。

t4时刻,创建了TEST_TestTask2任务,此时ready表中有MDS_RootTask、TEST_TestTask1和TEST_TestTask2共3个任务,现在还是在运行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1和TEST_TestTask2任务处于ready态。

t5时刻,tick中断到来,调度任务,由于MDS_RootTask任务优先级最高,因此调度的结果还是执行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1和TEST_TestTask2任务处于ready态。

t5~t6之间不断产生tick中断,调度任务,由于MDS_RootTask任务优先级最高,因此调度的结果还是执行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1和TEST_TestTask2任务处于ready态。

t6时刻,创建了TEST_TestTask3任务,此时ready表中有MDS_RootTask、TEST_TestTask1、TEST_TestTask2和TEST_TestTask3共4个任务,现在还是在运行MDS_RootTask任务。MDS_RootTask任务处于running态,TEST_TestTask1和TEST_TestTask2和TEST_TestTask3任务处于ready态。

t7时刻,tick中断到来,调度任务,由于TEST_TestTask3任务优先级最高,因此发生任务切换,TEST_TestTask3任务从ready态变为running态,而MDS_RootTask任务则从running态变为ready态,调度的结果变为执行TEST_TestTask3任务了。TEST_TestTask3任务处于running态,MDS_RootTask、TEST_TestTask1和TEST_TestTask2任务处于ready态。

t7之后不断产生tick中断,调度任务,由于TEST_TestTask3任务优先级最高,因此调度的结果还是执行TEST_TestTask3任务。TEST_TestTask3任务处于running态,MDS_RootTask、TEST_TestTask1和TEST_TestTask2任务处于ready态。

TEST_TestTask3任务每隔3秒打印一次,因此,我们最终从串口打印看到的只能是TEST_TestTask3任务每隔3秒一次的打印,而看不到TEST_TestTask1和TEST_TestTask2任务的打印。

    下面是本节输出的打印截图:



图 39  tick中断调度任务的结果

大家可以到ifreecoding_新浪博客网站下载视频,观看本节的动态打印输出。从视频中可以看到TEST_TestTask3任务每隔3秒打印一次,与我们分析的情况一致。

从图38中可以看到,拥有最高优先级的TEST_TestTask3任务在t6时刻就已经创建了,但还需要等到t7时刻tick中断到来时才能够执行,实时性还是差了一些。实时操作系统的调度周期并非完全是由tick周期决定,在下节我们将了解实时操作系统的另一种调度方式——实时事件触发的随机调度,这种调度方式对提高操作系统的实时性也是有帮助的。

将《底层工作者手册之C语言基础及项目开发》改名为《底层开发者手册之C语言基础及项目开发》,并做了一些修改,更新到2.4.5。不过其中有一些还没有写完。先凑合看吧   《C》这本手册是《底层开发者手册》系列的第二本,但它却是我第一个开始写的,早在2008年年底时我就开始着手写这本手册,但写了没多久就因为发生了一个意外而终止了。在2010年年初的时候我开始了第四本手册的写作——嵌入式操作系统内核,目前已接近完工状态(我的博客可以下载,blog.sina.com.cn/ifreecoding),现在我又回过头来重新写《C》这本手册,将会采用与《嵌》一样的方式,写一部分就在网上发布一部分,在这同时我又在收集第一本和第三本手册的材料,准备将这4本手册同时写完,使读者可以按照顺序阅读这4本手册,层层深入底层开发者的工作。   一个良好的C语言基础是编写嵌入式设备底层代码的必要条件,如今介绍C语言的书不少,介绍嵌入式C的书也不少,但几乎没有结合实际项目来介绍C语言的,看完这样的书,C语言会用了,但做出来的产品只能称之为中国校园式产品,为求实现功能不择手段,根本不适合在项目中使用。另有一些自称C语言精华的书或网上的面试宝典,尽是讲一些偏题怪题,这可能满足了作者的虚荣心,但却在将新手引向一个错误的方向。当然,仁者见仁智者见智,这仅是我个人的观点,也一定会被他人所反驳的,正是出现百家争鸣的局面社会才能进步。   本手册分为2部分,第一部分介绍C语言基础,在介绍C基础时会结合嵌入式中的应用加以介绍,将重点介绍在项目开发中有用的内容,对于过分追求技术但又没有什么实际用处的内容不做过多的介绍,对于这些内容最多会提一下,让读者知道有这种情况存在。第二部分介绍项目开发,从项目需求、分析、设计、编码、测试、维护的角度来介绍编写代码。记住,编码只是项目中的一个部分,在中国校园式产品中这可能几乎就是全部了,但在一个可以称之为产品的项目中,它只是一部分,并且项目越大它所占的比重越小。   我在看书学习新知识时,希望看到的是那些写的多一些详细一些,也就是废话多一些的书,可以傻瓜式的一步步跟下来,不希望看到那些对关键之处一笔带过还故作高深的书,所以本手册就以废话多为原则,可能会写的罗嗦一些,只求能看的明白一些。当然,本人知识水平有限,有些知识会有理解上的错误,或者有未触及的地方,错误一定是有的,就像做产品一样,不可能没有错误。我写本手册的目的在于分享我的知识,注重实际应用,这对某一论坛上的某些只会空谈经验、分析汉字语法的理想C语言文艺专家们来说可能会不屑一顾。如有问题,请登录我的博客blog.sina.com.cn/ifreecoding反馈,我虚心接受,但我拒绝那些假大空的为了批评而批评的建议。我们做项目不是写论文,是实实在在的东西!   为新手写一本介绍C语言的书很难,因为它牵涉到非常多的知识作为基础,其中的一些知识很可能需要使用另外几本书的篇幅来介绍,而且知识是耦合在一起的,在介绍前面的时候会涉及到后面的知识,对于新手来说,没有对C形成一个全貌,无法理解。因此给新手的建议是只能多看几遍,第一遍大致看看,掌握全貌,不要细读,然后再多读几遍,仔细分析各种问题,并结合例子,自己动手编程,调试,这个过程非常重要,一定要自己动手编程,光看是没有用的,只有自己动手解决了问题,才能从根本上理解问题。 C语言入门可能会比较容易,但不要指望几个月时间就能熟练掌握C语言,如果做底层编码的话则需要以年为单位来衡量。
Mindows操作系统更新5.1~5.3,将wanlix从ARM7内核移植到cortex内核,更多资料请登陆www.ifreecoding.com下载。 前面基础知识介绍完毕,本开始真刀真枪的移植代码了。本将Wanlix3.3的代码从ARM7内核移植到TI和ST的cortex内核的芯片上,移植完成后通过串口打印可以看到移植的效果。 如果你有STM32的板子,现在就可以跑! /***************************************************************************/ Wanlix是一个内核非常小的嵌入式操作系统,只有几百个字,但功能少,只提供任务切换功能,非常适合资源特别少但又需要任务切换的小项目。 Mindows可提供多种操作系统功能,是实时抢占式操作系统,任务支持多种优先级抢占调度,将实时性高的任务设置为高优先级就可以保证软件系统的实时性,用户也可根据自身需求选取需要的部分,也可在此基础上编写代码增加自己需要的功能,具有可裁剪性。 我将Wanlix和Mindows的开发过程记录下来,就形成了这本“底层工作者手册之嵌入式操作系统内核”一书,本手册不仅仅是从应用的角度介绍操作系统如何使用,更重要的是从原理的角度对操作系统的功能做了分析、设计,从无到有循序渐进一点点的增加操作系统的功能,并且每增加一个功能便配以一个例子加以演示,让读者能立刻看到代码运行的结果。 本手册记录了我从对操作系统内核不了解到写出操作系统内核的过程,这样的一个过程对你来说应该也是一个最好的学习过程。 如果你有一定的C语言基础,并且对硬件也有稍微的了解,那么我相信你一定会看明白本手册!也一定可以随心所欲的修改、扩展你需要的操作系统功能! 请登陆www.ifreecoding.com获取更多资料 /***************************************************************************/
Mindows操作系统更新到4.4看,增加任务任务切换钩子功能,更多资料请登陆www.ifreecoding.com下载。 Wanlix是一个内核非常小的嵌入式操作系统,只有几百个字,但功能少,只提供任务切换功能,非常适合资源特别少但又需要任务切换的小项目。 Mindows可提供多种操作系统功能,是实时抢占式操作系统,任务支持多种优先级抢占调度,将实时性高的任务设置为高优先级就可以保证软件系统的实时性,用户也可根据自身需求选取需要的部分,也可在此基础上编写代码增加自己需要的功能,具有可裁剪性。 我将Wanlix和Mindows的开发过程记录下来,就形成了这本“底层工作者手册之嵌入式操作系统内核”一书,本手册不仅仅是从应用的角度介绍操作系统如何使用,更重要的是从原理的角度对操作系统的功能做了分析、设计,从无到有循序渐进一点点的增加操作系统的功能,并且每增加一个功能便配以一个例子加以演示,让读者能立刻看到代码运行的结果。 本手册记录了我从对操作系统内核不了解到写出操作系统内核的过程,这样的一个过程对你来说应该也是一个最好的学习过程。 如果你有一定的C语言基础,并且对硬件也有稍微的了解,那么我相信你一定会看明白本手册!也一定可以随心所欲的修改、扩展你需要的操作系统功能! 请登陆www.ifreecoding.com获取更多资料
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值