手机做wifi中继上外国网站,网站子页怎么做 视频,乐山网站建设培训学校,做网站必备的注意事项前言
在笔者更新完Sparrow手把手教学系列后#xff0c;原本是不打算继续更新的。但关于Sparrow系列的读者又渐渐增多#xff0c;作为作者#xff0c;总感觉这个系列的文章还是稍微有些不圆满#xff0c;恐怕多少会让读者有些意兴阑珊。
最近又恰好有一点空闲时间#xf…前言
在笔者更新完Sparrow手把手教学系列后原本是不打算继续更新的。但关于Sparrow系列的读者又渐渐增多作为作者总感觉这个系列的文章还是稍微有些不圆满恐怕多少会让读者有些意兴阑珊。
最近又恰好有一点空闲时间思来想去于是决定再更上这么一篇作为Sparrow系列的补充。
拓展
对调度层进行抽象
Sparrow并没有IPC机制虽然也可以作为一个内核但是感觉还是有点残缺。于是作为拓展笔者决定对Sparrow内核的调度层进行抽象先引入任务状态这样就可以对调度层进行封装与抽象了。调度层负责提供线程状态转移的接口IPC层则利用接口完成线程间的通信。
为了规范抽象层必须要对任务的状态进行定义
线程的状态
Sparrow中任务有五种状态运行态、就绪态、延时态、阻塞态、挂起态
运行态任务正在运行毫无疑问处在运行态的任务只有一个。当任务处于运行态时它也处于就绪态。
就绪态任务可能在运行也可能准备运行当任务处于就绪态时它可能处于运行态如果它是最高优先级任务的话。只有就绪态中的任务会被执行。
延时态任务正在延时中当任务处于延时态时它还可能处于阻塞态。例如一个任务正在等待一个事件的发生任务的等待最长时间是100ms如果事件一直不发生那么任务就会等完100ms如果在这个过程中事件发生了任务会马上执行。在这种情况下任务既处于延时态又处于阻塞态。
阻塞态任务正在等待某个事件的发生此时任务也可以处于延时态。
挂起态当任务很长时间都不需要紧急执行时可以把该任务挂起。当然不止挂起任务也可以挂起调度器。挂起态通常是手动设置的这取决于用户对任务的管理。
修改Sparrow代码匹配抽象层
查找任务
由于Sparrow最大支持32个任务通常使用一个uint32_t变量的各个位表示任务的状态因此快速查找任务成为了一个难题不过我们已经实现了相关函数FindHighestPriority对其进行简单修改传入参数从而找到最优先的任务。
__attribute__( ( always_inline ) ) static inline uint8_t FindHighestPriority( uint32_t Table )
{uint8_t temp,TopZeroNumber;__asm volatile(clz %0, %2\nmov %1, #31\nsub %0, %1, %0\n:r (TopZeroNumber),r(temp):r (Table));return TopZeroNumber;
}既然有了这个函数那么我们可以修改时钟检查函数当然由于Sparrow支持的任务数量毕竟小可能对性能的提升不大。
修改代码
void CheckTicks( void )
{uint32_t LookupTable Delay;TicksBase 1;if( TicksBase 0){TicksTableSwitch( );}//find delaying Taskwhile(LookupTable ! 0){uint8_t i FindHighestPriority(LookupTable);LookupTable ~(1 i );if ( TicksBase WakeTicksTable[i] ) {WakeTicksTable[i] 0;Delay ~(1 i);//it is retained for the sake of specification.Ready | (1 i);}}switchTask();
}封装
封装与接口就是为每个模块定义清晰的接口这些接口描述了模块的输入、输出和预期行为。接口应尽量简洁隐藏模块内部的实现细节。将具体实现封装在模块内部通过接口暴露功能。实现应尽量保持私有性避免外部直接访问内部细节。
添加抽象层
//The abstraction layer of scheduling !!!
uint32_t StateAdd( TCB_t *self,uint32_t *State)
{uint32_t xre xEnterCritical();(*State) | (1 self-uxPriority);xExitCritical(xre);return *State;
}uint32_t StateRemove( TCB_t *self, uint32_t *State)
{uint32_t xre1 xEnterCritical();(*State) ~(1 self-uxPriority);xExitCritical(xre1);return *State;
}uint8_t CheckState( TCB_t *self,uint32_t State )// If task is the State,return the State
{uint32_t xre2 xEnterCritical();State (1 self-uxPriority);xExitCritical(xre2);return State;
}// the abstraction layer is end同时支持挂起调度器如果空闲任务进入了挂起态代表挂起调度器因为空闲任务通常是进入低功耗是为了防止单片机无事可做而衍生出来的任务修改SysTick_Handler如下
void SysTick_Handler(void)
{uint32_t xre xEnterCritical();uint32_t temp Suspend;temp 1;// If the idle task is suspended, the scheduler is suspendedif(temp ! 1) {CheckTicks();}xExitCritical(xre);
}修改命名
将状态表修改如下,这样更符合接口的原则
uint32_t Ready 0;
uint32_t Delay 0;
uint32_t Suspend 0;
uint32_t Block 0;现在我们对调度层的抽象已经基本完成了是时候引入IPC机制了。
IPC机制
信号量
笔者第一个引入Sparrow的IPC机制是信号量它是由Dijkstra大神Dijkstra算法、三色标记和并行垃圾回收算法等等算法的提出者发明的。
信号量的出现是在Dijkstra的一篇文章中有趣的是文章的内容是关于一个叫THE的操作系统基于一组并发进程这些并发进程通过一种名叫信号量的机制相互同步并且与硬件同步。不得不说信号量的思想真的是太精妙了。
初始化时的值不同功能不同
信号量有两种功能一种是互斥另一种是条件同步决定信号量的功能的关键在于它初始化时的值。
初始化为1互斥功能
据笔者所知2.6.9版本linux内核中几乎所有的信号量互斥锁、自旋锁都是用于互斥也就是对某个资源的独占访问。
用于互斥时使用方法如下
lock.semtake(上锁)
访问资源
lock.semrelease解锁当然简单的信号量会导致优先级反转现象所以必须使用优先级继承等方法实现互斥锁这样就能确保万无一失。
初始化为0同步功能
有时候我们往往会设置一个条件变量当变量触发时任务内部的代码才会执行这同样可以通过信号量实现。
使用方法如下
taskA()
{释放信号量semrelease()}taskB()
{ 没信号量继续阻塞semtake()有信号量了不阻塞了任务往下执行执行内部代码
}信号量的操作
信号量有P和V两种操作也叫down和up操作。
P操作如果信号量的值大于1就减1并允许任务继续执行否则阻塞任务。
V操作对信号量的值加1如果有任务在等待这个信号量就唤醒它。
学会了信号量一般RTOS的IPC机制基本都学会了这也就是笔者为什么给Sparrow引入信号量的原因。
代码实现信号量
P操作是获取信号量V操作是释放信号量这么一对照代码就显而易见了。
顺便一提FreeRTOS的信号量是基于队列机制实现的导致有很多人认为信号量是队列的一种说实话笔者很纳闷这种说法明显是错误的为什么会流行呢难道因为一个正方形是由两个三角形组成的你就认为三角形是正方形的一种吗还有把信号量看作长度为某个值的消息队列的说法这些都让笔者感到匪夷所思FreeRTOS的消息队列就是利用了发送消息和接受消息时的计数来创建信号量的根本不会开辟内存空间没有长度这一说法。不得不说互联网上很多资料漏洞百出不仅没有帮助作用反而误导了不少读者
调度层抽象的应用
有意思的是阻塞和延时的实现由于我们已经对调度层进行了抽象和封装对任务状态的转化通过StateRemove和StateAdd接口进行其实这是很简单的封装所以读者可能没什么感觉甚至觉得在画蛇添足。这是因为Sparrow太小了但是如果项目非常庞大建立一层抽象是非常有必要的。笔者只是为读者展示如何建立一层简单的抽象来规范代码。
线程状态的改变
semaphore_take获取信号量时如果没有信号量那么线程的状态转变为阻塞态和延时态。
semaphore_release释放信号量时如果有线程因为该信号量阻塞那么线程的状态从阻塞态和延时态中移除并转变为就绪态。
当延时结束或者阻塞被唤醒时线程会继续执行。
为了防止任务没有进行调度就往下执行笔者建立了一个空循环PendSV是标志位如果调度没发生那么就会慢慢等。
获取信号量的两种结果
1.释放信号量时被唤醒
此时线程的状态从阻塞态和延时态中移除并转变为就绪态。说明此时信号量可用可以执行获取信号量的操作。
2.任务超时
此时线程的状态从延时态中移除但是阻塞态并没有移除需要被移除阻塞态。同时说明等了半天信号量都不能用只能返回错误。
通过调度层提供的接口判断任务的状态可以得到是哪一种结果并改变对应的状态。
由于每个任务可能阻塞在不同的信号量上因此不能都使用总的阻塞表每个任务都需要有自己的阻塞表但同时需要更新总阻塞表这是一种状态转移的规范。
Class(Semaphore_struct)
{uint8_t value;uint32_t Block;
};Semaphore_struct *semaphore_creat(uint8_t value)
{Semaphore_struct *xSemaphore heap_malloc(sizeof (Semaphore_struct) );xSemaphore-value value;xSemaphore-Block 0;return xSemaphore;
}void semaphore_delete(Semaphore_struct *semaphore)
{heap_free(semaphore);
}uint8_t semaphore_release( Semaphore_struct *semaphore)
{uint32_t xre xEnterCritical();if (semaphore-Block) {uint8_t i FindHighestPriority(semaphore-Block);StateRemove(TcbTaskTable[i],semaphore-Block);StateRemove(TcbTaskTable[i],Block);// Also synchronize with the total blocking stateStateRemove(TcbTaskTable[i],Delay);StateAdd(TcbTaskTable[i], Ready);}semaphore-value;switchTask();xExitCritical(xre);return true;
}uint8_t semaphore_take(Semaphore_struct *semaphore,uint32_t Ticks)
{uint32_t xre xEnterCritical();if( semaphore-value 0) {semaphore-value--;switchTask();xExitCritical(xre);return true;}if(Ticks 0 ){return false;}uint8_t volatile temp PendSV;if(Ticks 0){StateAdd(pxCurrentTCB,Block);StateAdd(pxCurrentTCB,semaphore-Block);TaskDelay(Ticks);}xExitCritical(xre);while(temp PendSV){ }//It loops until the schedule is start.uint32_t xReturn xEnterCritical();//Check whether the wake is due to delay or due to semaphore availabilityif( CheckState(pxCurrentTCB,Block) ){//if true ,the task is Block!StateRemove(pxCurrentTCB,semaphore-Block);StateRemove(pxCurrentTCB,Block);xExitCritical(xReturn);return false;}else{semaphore-value--;switchTask();xExitCritical(xReturn);return true;}
}
总结
调度层的抽象和IPC机制中最经典的信号量机制已经介绍完毕笔者就介绍这么多。只要搞定了信号量其他的的IPC机制都大差不差读者甚至可以根据需求自己定制IPC机制比如信号量的同步都是一个又一个的同步有没有办法让一个信号量的释放同步多个任务呢当然可以只要在信号量的基础上简单修改为唤醒所有阻塞事件就行。这其实跟linux内核中的completion机制非常相似。
笔者希望读者能动手自己完成消息队列、互斥锁、事件等IPC机制在Sparrow系列的学习中能够学有所获。
所以剩下的IPC机制笔者就懒得敲了(_, )这是留给读者自己的拓展就像苏格拉底所说除了你自己没有人能够教会你知识其他人只能激发你的知识
Sparrow系列的文章一般都是一边是算法思路一边是代码而且代码里往往没什么注释因为笔者并不希望读者看代码看半天而是希望读者动手敲起来然后进行代码的调试通过代码来理解算法的思路。
综上Sparrow拓展篇结束o(▽)
Sparrow源码的地址https://github.com/skaiui2/SKRTOS_sparrow/tree/source