嵌入式OS编程事项----系列一

转岗阿里以前在ARM负责过mbedos,能够这样担保:嵌入式操做系统学好了就业市场至关普遍,我熟悉的大厂如华为、ARM、大疆、腾讯IOT、阿里云IOT、菜鸟网络等,普通小厂像深圳东莞大量的中小型智能家居公司彻底能够作一个联手的跳板。(顺便说一下,mbedos比arduino更适合安卓版开发)。面试

刚入门的时候,淘宝买一块cortexm3开发板便可入手,经过项目,你须要了解:任务调度、进程间通讯、内存管理、设备驱动、文件系统、TCP/IP协议栈、同步异步、中断、软件架构插件化等等基本原理,这些对你后面转Linux应用开发,安卓开发,后台开发大有好处。编程

       到这一步,就看本身职业方向想往哪里发展,若是是想深刻IOT物联网作端云链接,那么能够把几种基本总线驱动,I2C、SPI、USART理解透,若是是想拥抱互联网转入应用开发,那么能够把基础组件,如协议栈、文件系统吃透,BAT面试不是很难,问的都是这些基础。微信

       顺便说一下,学东西就要学对市场有用的,不要过于学习屠龙之术,炫技给我的带来不了财富,公司须要的是能干活的人。网络

       本文不许备讲过于偏硬件的知识如Cortex-M3的多种中断模式,操做寄存器组,芯片降噪等内容,而是专一于操做系统基本知识和项目经验,这些软件开发经验才是去互联网公司看重的能力。架构

       模块架构插件化异步

       对于嵌入式开发,插件化的意思就是应用程序不须要关心硬件层面的适配,只须要调用几个暴露的接口就能够完成自身数据处理功能。插件通常能够制做成.a库,经过暴露一个头文件与外界模块完成数据交换。ide

       回顾一下51单片机时代是怎么写跑马灯程序的?直接对P0、P1等IO口写电平值。好一点的设计方法是把具体读写操做封装成函数,应用程序调用函数接口,这样能够作到功能解耦,应用程序只须要关心本身的功能实现:模块化


这里有个问题,就是适配接口千差万别,不一样的开发板厂家提供的接口基本不相同,应用程序移植工做量很大。这个时候嵌入式操做系统的一个优点就体现出来了,像Linux就是把驱动封装在系统调用接口内,应用程序只须要调用read、write等接口就能够完成数据操做:函数


如此一来,只要上层程序调用posix标准接口操做内核资源就能够完成自身功能,可移植性大大增长。这里唠叨一下,UNIX编程环境的标准化也是个很复杂的过程,ANSI和IEEE等独立组织各自制定了自身标准,主流的有:ISO C、IEEE POSIX、Single UNIX和FIPS,编写程序时要不一样的标准之间接口行为差别。性能

说回模块化编程,一个模块必需要跟其余模块有数据交换才有功能价值,所以能够把外部须要使用的接口和全局变量放到头文件暴露出去,内部的数据操做流程能够封装成函数用static关键字声明对外隐藏实现。

中断

       理解好嵌入式系统的中断,对后面转入Linux应用编程大有好处。GNU C标准没有对中断标准化,不过程序实现上不少编译器厂商增长了__interrupt关键字用于对中断服务程序的支持,中断服务子程序(ISR)有三点要求:

一、ISR不能有返回值

不像普通函数调用,中断程序由硬件触发执行,没有具体调用者,返回值返回给谁呢。

二、ISR不能传递参数

这点不能算强制要求,对于软中断,中断函数的运行时机已经能够由程序触发,其具体运行时机由操做系统安排。这种场景能够有返回值和参数传递。

三、ISR尽可能简短高效,不要使用浮点运算

不少处理器/编译器中浮点通常都是不可重入的;有些处理器/编译器须要让额外的寄存器入栈;有些处理器/编译器之间不容许在ISR中作浮点运算。

四、printf()常常有重入和性能上的问题,不推荐使用

嵌入式项目中的经典驱动设计,最基础的数据包接收的方法是查询方式,即处理器不断从网卡芯片中读取数据,读不到数据则从新启动一个读取时序;若是可以成功读取到数据,则将数据经过网卡注册的input函数交往上层进行处理。使用查询方式实现的数据包接收进程其优先级必须低于系统中其余进程的优先级,不然它会阻塞比它优先级低的进程的运行。

为了程序性能,以太收包和串口收包就可使用中断结合信号量和队列实现。首先在以太收包IRQ中断中调用收包回调callback函数

void HAL_ETH_IRQHandler(ETH_HandleTypeDef *heth){ /* Frame received */ if (__HAL_ETH_DMA_GET_FLAG(heth,ETH_DMA_FLAG_R)) { /* Receive complete callback */ HAL_ETH_RxCpltCallback(heth); //收包回调  /* Clear the Eth DMA Rx IT pending bits */ __HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_R);  /* Set HAL State to Ready */ heth->State = HAL_ETH_STATE_READY;  /* Process Unlocked */ __HAL_UNLOCK(heth);  }

callback函数在hal库中为__weak实现,也就是用户能够修改。在callback中释放信号量。

void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *pheth){ (void)pheth; sys_sem_signal(&s_xSemaphore); //释放信号量}


能够建立一个专门的线程等待信号量,以后执行收包操做。

static voideth_thread(void *arg){ while (1) { sys_arch_sem_wait(&s_xSemaphore,TIME_WAITING_FOR_INPUT); //等待信号量,能够设置超时时间 ethernetif_input(arg); }}


线程eth_init能够在注册网卡的时候建立。

上述以太驱动设计中中断服务程序很小,具体的数据接收操做在一个单独的线程中完成。有兴趣深刻探讨的同窗能够Google“Linux中断上半部和下半部”。

任务调度

嵌入式os采用任务控制块(TCB)来管理任务调度功能,以下面代码所示,TCB结构包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。理清任务调度与互斥锁,读写队列与锁之间的关系对写出良好性能的程序及其重要。

typedef struct tagTskInfo{ CHAR acName[TOS_TASK_NAMELEN]; /**< Task entrance function */ UINT32 uwTaskID; /**< Task ID */ UINT16 usTaskStatus; /**< Task status */ UINT16 usTaskPrio; /**< Task priority */ VOID *pTaskSem; /**< Semaphorepointer */ VOID *pTaskMux; /**< Mutex pointer */ UINT32 uwSemID; /**< Sem ID */ UINT32 uwMuxID; /**< Mux ID */ EVENT_CB_S uwEvent; /**< Event */ UINT32 uwEventMask; /**< Event mask */ UINT32 uwStackSize; /**< Task stack size */ UINT32 uwTopOfStack; /**< Task stack top */ UINT32 uwBottomOfStack; /**< Task stack bottom */ UINT32 uwSP; /**< Task SPpointer */ UINT32 uwCurrUsed; /**< Current task stackusage */ UINT32 uwPeakUsed; /**< Task stack usagepeak */ BOOL bOvf; /**< Flag thatindicates whether a task stack overflow occurs */} TSK_INFO_S;


先写这么多,其余模块后面慢慢整理总结。


本文分享自微信公众号 - 机械猿(on_ourway)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索