前语:线程不是什么奥秘的东西,当你了解后你会有一种恍然大悟的感觉,其实它本身就很简略。
榜首节:程序代码运转条件
回想一下:
1.一个链接过的程序由以下组成:代码段,只读数据段,可读写数据段。
2.单片机上常运用的两个资源:Flash(只读),RAM(可读写)
3.关于单片机,咱们习惯于一种形式,代码段和只读数据放在FLASH上,可读写数据放在RAM的开端地址,栈从RAM中最高地址向下开端运转。
4.处理器从代码段提取代码,有三种方法,次序,跳转,调用。一切代码段有必要放在正确的方位上。
5.处理器上数据段是经过地址来处理数据,所以数据也有必要放在正确的方位上。
6.处理上暂时变量经过栈指针的向下偏移来提取变量,所以暂时变量的地址是不固定的。
7.至于堆,那是C言语的技巧,不列入条件范围内。
总结:1.程序运转需求代码段,数据段和栈区,而代码段和数据段都有必要放在修正时对应的地址上,只需栈区是可以设置的。那么在不运用暂时变量地址作为核算因数的情况下,就算改动栈顶的方位,程序的运转成果相同。
第二节:多程序运转原理
多线程的原理其实很简略,体系为每个线程供给一片内存作为栈区。然后选取FLASH中一点作为线程代码的开端地址,最终调转到线程代码的开端地址开端履行。线程代码可以随意操作被分配的栈中的暂时变量而不会搅扰就任何其他线程。除非你的暂时变量过大超过了分配的栈区,这个就要你运用线程的经历和感觉有关了,一般人都不会去细心的核算运用多大暂时变量空间。线程也有个缺点,便是线程在拜访栈区以外的地址时,包含数据区,都会存在这样一种或许,多个线程一起读取和修正一个数据区中的数据时,就会产生鸿沟现象,即多线程共管的数据。当然一起是相对的,相对咱们的感觉,现在举例阐明:A线程在从Addr地址处读取出数据data后,恰巧被别的一线程B交代,而B线程也读取了Addr地址处的data数据并修正了data的一位,然后…,当再次运转到A线程时,A线程也修正了data并写回到Addr地址处,这样就存在了一个bug,B线程修正的位就被A线程的写回覆盖了。这点其实咱们不忧虑,由于体系一般都会供给许多防止这种现象的机制。
假如你对多线程仍是不了解的话,我估量你应该便是对栈的知道不行精确,可参阅相关材料。
第三节:ucos体系介绍
关于ucos的广告部分我现已屏蔽,我直接进入正题,ucos是一个抢占式体系,抢占式是指使命以抢占式的方法来运转。把Ucos中的使命当成进程来了解是不恰当的,这会影响咱们对Windows进程和Linux进程的了解。Ucos中的使命只能相当于线程的人物。Ucos内容包含两大部分,一个是体系部分:包含使命操作,时刻操作,事情操作,内存操作,这些不随处理器的不同而不同。别的一个是接口部分:由汇编和C言语组成。供给使命切换函数,守时器接口,CPU寄存器保存和读取,及中止处理等硬件相关操作函数。
第四节:创立ucos使命
运用下面函数创立一个使命:
INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio);
创立函数设置使命函数(使命代码首地址)和使命参数,分配栈顶(ptos),和优先级。Ptos的传入做法在可读写数据区分配一个数据OS_STKStk【size】.然后把stk最高地址传送给ptos。而stk数组便是默许的分配给使命的栈区,使命task运转后运用stk存储暂时变量。
别的,stk还有个缩水便是体系需求从stk最高位减去一部分空间用来存储寄存器信息,关于ARM是16个unsignedlong长度,用来存储该使命的r0-r15,CPSR.
体系也会为每一个创立的使命分配一个使命操控块(TCB)。TCB管理者使命的状况和信息。别的还有一个TCB指针指向当时正在运转的使命。TCB操控着当时进程是否在运转,假如不是在运转是否是由于事情堵塞,当使命运转时,从什么当地找到前次运转时保存的信息等等。
关于每个创立后或运转的使命,都有两个重要的部分,一个是TCB,一个是栈头(栈区顶上保存的空间)。使命开端调度的榜首步便是找到该使命的TCB,然后从TCB中找到栈头地址,然后运用栈头保存的数据仿制到CPU寄存器上和CPSR上,最终跳转到栈头上前次运转保存的地址处开端履行。当运转的使命被调度时,相同是首要找到TCB所指向的栈头,然后把CPU一切寄存器内容和CPSR及当时地址悉数仿制曩昔,再去找到别的一个被使命是应该运转的进程,然后调度那个进程。
多使命的布景来自一点,其实咱们写的大部分程序其实都有太多的推迟,关于没有体系的程序,真实履行功率(即不做无效循环)的时刻或许只占到处理器运转的5%都不到。刺进一句,假如你长于处理器编程的话,你看代码不应该只看到代码的长度,而是这段代码运转占用了多长时刻,和占用哪些资源和多大空间。体系的引进会让咱们从头知道使命运转时刻,咱们不期望程序长时刻做无效循环,咱们要运用这段时刻去做其他的事,然后进步处理器的功率。所以不让以为体系会占用你的资源,体系会协助你尽力回收那95%以上功率。实际上回收悉数资源是不或许的。这就看你怎么运用架构,和你的使命级别了。每个项目或许都会不同。
第四节:抢占式调度(ucos的经典)
调度的意思便是从一切的使命行列中找到最应该运转的使命,然后运转该使命。而调度的方法决议了体系的功能。Ucos的经典就来自于它只用了一个数组采取了最单纯的行为来进行使命调度,一起也占用了最小的资源。所以,即便是8位处理器,许多人也会运用ucos体系。
上面说过,ucos是抢占式调度。抢占式调度的概念便是,只需一个CPU,一切线程以抢占的方法占有CPU,然后运转使命,除非他自动让出,或他被其他使命抢占,不然,他会一向占用CPU.UCOS的抢占方法是比较优先级,每个使命都需求分配一个且仅有的优先级。每次调度便是比较一切使命的优先级,找到优先级最高的使命(这点其实不杂乱,下段介绍),然后调度该使命并运转,最高优先级的使命需求自己自动退出,不然,永远是这一个在运转。当这个使命运转到推迟或等候事情时,体系函数就会把这个使命从运转行列屏蔽掉,然后从头调度,再次查找最高优先级的使命,这样就找到了别的一个优先级的使命,然后运转该使命,到这个使命睡觉或等候事情时,也会睡觉,然后再次调度,这时,假如前面睡觉的最高优先级的使命被唤醒,那么他将也会被放到优先级行列中。不然,再进入下一个优先级。
关于使命的行列,睡觉,运转等概念都是一种了解概念,实际上ucos在这点是很简略的,也是很经典的。现在阐明下ucos的调度行列。首要,咱们需求知道下面个数组是干什么用的。
INT8UconstOSUnMapTbl[256]={
0,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,
6,0,1,0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
随意给一个8位数data,这个数组的效果便是以查表的方法最快的速度找到data数据中从低位到高位中为榜首个为1的数的方位(bit0为0)。即替代下面的函数的效果。运用方法:prto= OSUnMapTbl[data]
for (i=0; i<8; i++)
{
}
了解了那个数组后咱们再引进两个变量,
OS_EXTINT8U OSRdyGrp;
OS_EXTINT8U OSRdyTbl[OS_RDY_TBL_SIZE];
每个使命的优先级对应于OSRdyTbl数组中的一个位。对应联系是OSRdyTbl[prio/8]中的的第(prio%8)位,(prio/8)和(prio%8)运用使命操控块TCB中的->OSTCBX和->OSTCBY表明。将OSRdyTbl数组中使命对应的方方位一表明该使命预备稳当,可参与抢占运转, OSRdyTbl数组中使命对应的方位为0表明该使命不存在,或该使命当时被堵塞无法运转。OSRdyGrp变量的效果是运用8个位顺次对应OSRdyTbl数组的前8个字节,对第n位为0,代表 OSRdyTbl[n]悉数为0,假如第n位为1,代表对应的OSRdyTbl[n]至少有一个为1;
然后调度东西就开端运用下面机制来得到最高优先级的使命,即优先级号最低的那个使命
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
榜首行代码,经过查表找到OSRdyTbl数组中不为0的最低的一个数组。然后经过第二条代码的OSUnMapTbl[OSRdyTbl[y]]);找到对应的OSRdyTbl[y]中的最低的一个是1的方位。然后与(y << 3)相加,就得到了OSRdyTbl数组中从低到高的最低的为1的1位的方位。即最高优先级使命的优先级,然后依据优先级找到对应的TCB进行调度。
每次调度时都是封闭了前一个进程,因而ucos需求行列中至少有一个可运转的程序,为此UCOS制作了一个IDLE使命,这个使命优先级最低,在最高位,意图是一切使命进程都睡觉时,让体系依然有使命可调,不至于溃散。别的一个用作是核算CPU运用率。假如IDLE使命从没调用过,那就阐明的使命抢占度过高,优先级高的使命有需求开释一些空间让优先级低的使命运转。
第五节:UCOS的实时功能
按我了解,UCOS的实时功能是一种想象,让一切的使命等处于等候信号阶段,当有中止触发时,履行中止处理函数,经过信号唤醒进程,来完结使命,完结后可以持续睡觉。即便有多个中止呼应,只需中止函数能及时呼应,那么使命就排着队来完结后续作业。这便是我的UCOS想象。
所以,咱们需求在两个当地调度,一个是中止,每次进入中止后封闭调度,可是答应信号唤醒使命,然后在最终一个中止嵌套完结后退出时进行调度,检测有没有被唤醒的可履行使命。别的一个便是守时中止。每次tick完结后都进行一次重调度。意图是当低优先级履行时没有开释资源,而高优先级的使命已被唤醒。特别是IDLE使命,除非你问它要,不然他不会给你开释资源的。
第六节:事情处理
UCOS的事情首要包含SEMAPHORE,Mutex,和Mbox,Q,运用起来都很简略,一般都只适用三个函数创立,挂起等候,开释。
SEMAPHORE效果是当某个使命运转到有必要得到某种资源时进行挂起等候资源满意,其他的使命或中止发送SEMAPHORE表明资源现已树立,你可以运转了,假如是使命在发送SEMAPHORE时发现有使命因而被挂起,会唤醒并调度到该使命上履行。
Mutex有一种锁的概念,当得到一个东西后,就立马对其上锁,其他的使命就只能等候该使命完结后打开锁才干运转。这里有一个问题便是一旦低优先级的使命占用锁后而高优先级的就有必要等候,而恰巧低优先级的又被中优先级的使命抢去履行就会产生,高优先级等低优先级,低优先级等中优先级的现象称为优先级翻转,一切Mutex有一个机制便是高优先级想得到锁的话就暂时进步低优先级的优先级,使低优先级赶快完结完结开释锁。
邮箱MBox根本和SEMPAPHORE相同,仅仅SEMPAPHORE被作为一个信号标志来传送,Mbox也可以被作为SEMPAPHORE运用,可是会回来一个地址指针。
Q音讯行列没有用过,看样子是首要初始化一个数组,然后对数组运用FIFO的方法发送和承受函件。
我一般还会再加上一些原子读写函数atom_read/wirte,首要针对鸿沟变量。其实很简略便是读取前关中止,读取后开中止罢了。
第七节:tick
Tick是体系时刻,他和守时的概念是不同的,如OSTimeDly (OS_TICKS_PER_SEC/100),实际上不是严厉的推迟了OS_TICKS_PER_SEC/100秒,存在0-1/OS_TICKS_PER_SEC之间的差错。Tick相当于挂钟在不断的跑,秒表改变的瞬间被称为tick,而咱们是不或许从tick那一瞬间开端计时的。所以这是一个概念是要辨明的。
第八节:ucos的缺点
UCOS毕竟是一个小体系,乃至可以在8位处理器上运转,所以关于咱们完结更杂乱的使命和对体系功率更高的要求的话,它是存在必定的局限性的。如:
1. 体系和运用,中止等联系密切,开发人员需求了解体系特性,如。使命被创立后是不能直接退出的,有必要运用API函数毁掉它。
2. 调度方法过于单一,使命较少时可以到达平衡,使命较多时,高优先级的和低优先级的运转时刻就会存在严峻不平衡,而且会添加考虑调度问题。
3. 短少异步读取机制,如我想向串口发送数据,而此刻串口缓存已满,咱们就需求抛弃资源调度其他使命。串口可以经过多开缓存来补偿,可是关于TCP,退出就需求至少等候下一个Tick,时刻就显得有些长久了,这个机制其实我一向在考虑。
第九节:写后
不喜欢LPC21xx和周建功的UCOS体系还有个原因便是LPC21xx的中止机制看起来不错,但实际上现已可以影响了咱们代码的发挥。也或许我自己懒散的原因,没有来及在LPC上改造ucos。周建功的中止函数运用__irq声明,这一点现已和上面第五节所说内容想违反。
两外,周建功的关中止函数和开中止函数运用swi中止,我觉得是不如原版的较好。原版的函数是保存寄存器关中止函数和康复寄存器内容。我原本考虑着周建功或许是考虑软中止可直接进入中止来防止中止搅扰,而原版的在关中止函数中心仍有或许被中止,如下
MRSR0, CPSR;//仿制CPSR,履行后或许被中止
ORR R1, R0, #0xC0;//核算,也有或许被中止
MSRCPSR_c, R1;//这个代码完结才真实封闭中止
但后来相通之后,觉得周建功是多此一举,即便关中止前被中止也没有什么的,由于中止后它会原模原样的回来给你。仍是不喜欢周建功的UCOS和LPC
后来在三星的s3c2440上也架构了一个ucos,而且搭配了TFTP传输和TCP对话,感觉用起来要比LPC的好用许多。
当然,这仅仅个人用法和感觉,每个芯片只需写好了软件应该也是不错的。下面略微提下个人用法,我一般如下界说main函数
int main(void)
{
OSInit();
OSTaskCreate(MainTask,(void *)1,&MainTaskStk[MainTaskStkLengh-1], MainTaskPrio);
OSStart();
return 0;
}
直接创立一个MainTask使命,然后在MainTask中进行初始化硬件和创立使命,事情
void MainTask(void *pdata)
{
}