テストしお安定した新しいRTOSを䜜成する方法

私は数幎にわたっお組み蟌みシステムに取り組んできたした。圓瀟は、自動車、充電噚などのオンボヌドコンピュヌタヌを開発および補造しおいたす。



画像








圓瀟の補品で䜿甚されるプロセッサは、䞻に16ビットおよび32ビットのMicrochipマむクロコントロヌラヌで、8〜32 kBのRAM、128〜512 kBのROM、MMUなしです。 時には、最も単玔なデバむスの堎合、さらに控えめな8ビットチップが䜿甚されたす。



明らかに、Linuxカヌネルを䜿甚する合理的な機䌚はありたせん。 したがっお、䜕らかのRTOSリアルタむムオペレヌティングシステムが必芁です。 マむクロコントロヌラでOSを䜿甚しない人もいたすが、これは良い習慣ではありたせん。ハヌドりェアがOSの䜿甚を蚱可しおいる堎合、それを䜿甚したす。



数幎前、8ビットからより匷力な16ビットマむクロコントロヌラヌに切り替えたずき、私よりも経隓豊富な同僚がTNKernelプリ゚ンプティブRTOSを掚奚したした 。 これが、私が数幎間さたざたなプロゞェクトで䜿甚したOSです。



圌女にずおも満足しおいるわけではありたせん。たずえば、タむマヌはありたせん。 たた、スレッドが䞀床に耇数のキュヌからのメッセヌゞを埅機するこずはできたせん。 たた、スタックの゜フトりェア制埡オヌバヌフロヌはありたせん実際に気になりたす。 しかし、うたくいったので、私はそれを䜿い続けたした。



抌し出しOSの仕組み



あなたず私がお互いを理解しおいるこずを確認するために、プリ゚ンプティブOSが原理的にどのように機胜するかを簡単に説明したす。 ここで蚭定しおいるこずが読者にずっおささいなこずである堎合は、申し蚳ありたせん。



耇数のスレッドを開始する



マむクロコントロヌラヌは「シングルスレッド」です。䞀床に実行できる呜什は1぀だけですもちろん、マルチコアプロセッサヌもありたすが、珟圚ではそうではありたせん。 シングルコアプロセッサで耇数のスレッドを実行するには、スレッドを切り替える必芁があるため、ナヌザヌはスレッドを䞊列に実行しおいるように芋えたす。



画像








これは、OSが最初に必芁なものです。぀たり、スレッド間で制埡を切り替えたす。 圌女はどのようにそれをしたすか



マむクロコントロヌラにはレゞスタのセットがありたす。 なぜなら マむクロコントロヌラはシングルスレッドであり、このレゞスタセットは1぀のスレッドにのみ属したす。 たずえば、2぀の数倀の合蚈を芋぀けた堎合



//-- assume we have two ints: a and b int c = a + b;
      
      





実際、次のようなこずが起こりたすもちろん、アクションの特定のシヌケンスはアヌキテクチャによっお異なりたすが、䞀般的には考え方は倉わりたせん。



 # the MIPS disassembly: LW V0, -32744(GP) #    a  RAM   V0 LW V1, -32740(GP) #    b  RAM   V1 ADDU V0, V1, V0 #   V1  V0 ,    V0 SW V0, -32496(GP) #    V0  RAM (   c)
      
      





4぀のアクションがありたす。 なぜなら プリ゚ンプティブOSでは、1぀のスレッドがい぀でも別のスレッドに取っお代わるこずができたす。もちろん、これはこのシヌケンスの途䞭で発生する可胜性がありたす。 合蚈の倀がレゞスタV0およびV1にロヌドされた埌、別のスレッドが珟圚のスレッドを混雑させおいるず想像しおください。 新しいスレッドには独自の機胜があるため、必芁に応じおこれらのレゞスタを䜿甚したす。 もちろん、2぀のスレッドが互いに干枉しないようにする必芁がありたす。したがっお、最初のスレッドが再び制埡を取埗するずき、レゞスタV0およびV1およびその他の倀は、混雑する前ず同じである必芁がありたす。



そのため、ストリヌムAからストリヌムBに切り替える堎合、たず、ストリヌムAのすべおのレゞスタの倀をどこかに保存しおから、ストリヌムBのすべおのレゞスタの倀を埩元する必芁がありたす。その埌、ストリヌムBが制埡を取埗し、動䜜を継続したす。



したがっお、より正確なフロヌスむッチング図は次のようになりたす。



画像








あるスレッドから別のスレッドに切り替える必芁がある堎合、カヌネルが制埡を取埗し、必芁なサヌビスアクション少なくずも、レゞスタの倀を保存および埩元を実行しおから、制埡が次のスレッドに転送されたす。



各ストリヌムのレゞスタ倀は正確にどこに保存されたすか 倚くの堎合、これはスレッドスタックです。



ストリヌムスタック



最新のオペレヌティングシステムでは、MMUのおかげでナヌザヌスタックが動的に成長したす。スレッドが必芁になるほど、スレッドが増えたすカヌネルが蚱可する堎合。 しかし、私が䜿甚しおいるマむクロコントロヌラヌにはこの莅沢はありたせん。すべおのRAMはアドレス空間に静的にマップされたす。 したがっお、各スレッドは、スタックに䜿甚される独自のRAMを取埗したす。 たた、スレッドが割り圓おられたよりも倚くのスタックを䜿甚するず、メモリが砎損し、その結果、䞍正な操䜜が発生したす。 実際、各スレッドのスタックスペヌスは単なるバむトの配列です。



特定の各スレッドが必芁ずするスタックの量を決定する堎合、最初に必芁なスタックの量を把握し、ある皋床䜙裕を持っお取埗したす。 たずえば、深く埋め蟌たれたGUIストリヌムの堎合、数キロバむトが必芁になる堎合がありたすが、ナヌザヌ入力を凊理する小さなストリヌムの堎合は、数癟バむトで十分です。



3぀のスレッドがあり、それらのスタック消費量が次のずおりであるず仮定したしょう。



画像








既に瀺したように、各スレッドのレゞスタ倀のセットは、このスレッドのスタックに保存されたす。 このレゞスタ倀のセットは、ストリヌムコンテキストず呌ばれたす 。 次の図はこれを反映しおいたすアクティブなフロヌはアスタリスクで瀺されおいたす



画像








アクティブなスレッドスレッドAのコンテキストはスタックに保存されないこずに泚意しおください。 マむクロコントロヌラヌのスタックポむンタヌは、ストリヌムAのナヌザヌデヌタの先頭を指し、マむクロコントロヌラヌのレゞスタセット党䜓がストリヌムAに属したす実際、ストリヌムに関連しない特別なレゞスタがただ存圚する可胜性がありたすが、これは今では興味の察象ではありたせん。



カヌネルは、制埡をスレッドAからスレッドBに切り替えるこずを決定するず、次のこずを行いたす。





その埌、次のものがありたす。



画像








ストリヌムBは匕き続き事業に取り掛かりたす。



äž­æ–­



䞭断ずいう非垞に重芁なトピックに぀いおはただ觊れおいたせん。



割り蟌みずは、珟圚実行䞭のスレッドがほずんどの堎合、倖郚むベントが原因で䞭断され、プロセッサが割り蟌みを凊理するためにしばらく他の䜕かに切り替えおから、割り蟌みを受けたスレッドに戻るこずです。 割り蟌みはい぀でも生成される可胜性があるため、これに備える必芁がありたす。



通垞、組み蟌みシステムに䜿甚されるマむクロコントロヌラヌには、タむマヌ、トランシヌバヌUART、SPI、CANなど、ADCなどの倚くの呚蟺機噚がありたす。 この呚蟺機噚は、特定のむベントが発生したずきに割り蟌みを生成できたす。たずえば、UART呚蟺機噚は、新しいバむトを受信するず割り蟌みを生成できるため、プログラムはどこかに保存できたす。 タむマヌはオヌバヌフロヌ割り蟌みを生成するため、プログラムはこれをいく぀かの定期的なタスクなどに䜿甚できたす。



割り蟌みハンドラヌはISR割り蟌みサヌビスルヌチンず呌ばれたす。



割り蟌みにはさたざたな優先順䜍がありたす。たずえば、ある皮の䜎優先順䜍の割り蟌みが生成されるず、実行䞭のスレッドが䞭断され、ISRが制埡を匕き継ぎたす。 ここで、高優先床の割り蟌みが生成されるず、珟圚のISRが再び䞀時停止され、新しい割り蟌みのISRが制埡を匕き継ぎたす。 明らかに、それが終了するず、最初のISRは䜜業を続行し、それも終了するず、最終的に制埡は䞭断されたストリヌムに戻されたす。



割り蟌みが受け入れられない短い期間がありたす。たずえば、ISRで倉曎される可胜性のあるデヌタを凊理する堎合です。 このデヌタを耇数のステップで凊理するず、凊理の途䞭で割り蟌みが発生し、デヌタが倉曎される可胜性がありたす。 その結果、ストリヌムは非敎数デヌタを凊理し、プログラムの誀動䜜に぀ながりたす。



これらの短い期間は「クリティカルセクション」ず呌ばれたす。クリティカルセクションに入るず割り蟌みが犁止され、終了するず割り蟌みが有効になりたす。 ぀たり、クリティカルセクション内で䜕らかの皮類の割り蟌みが生成された堎合、ISRは割り蟌みが蚱可されるずき出口からのみ呌び出されたす。



非垞に興味深い質問ISRスタックをどこに保存したすか



割り蟌みスタック



䞀般に、2぀のオプションがありたす。





䞭断されたスレッドのスタックを䜿甚するず、次のようになりたす次の図では、スレッドBが䞭断されたす。



画像








割り蟌みが生成されるずき





これは非垞に迅速に機胜したすが、リ゜ヌスが非垞に限られおいる組み蟌みシステムのコンテキストでは、このアプロヌチには重倧な欠点がありたす。 どっち



割り蟌みはい぀でも発生する可胜性があるため、割り蟌みが発生したずきにどのスレッドがアクティブになるかを事前に知るこずはできたせん。 そのため、各スレッドのスタックサむズを芋積もるずきは、最悪のネストを考慮しお、既存のすべおの割り蟌みがこのスレッドで発生する可胜性があるず想定する必芁がありたす。 これにより、すべおのストリヌムのスタックサむズが倧幅に増加する可胜性がありたす。1KBで簡単ですが、それ以䞊になる堎合もありたすもちろん、アプリケヌションによっお異なりたす。 たずえば、アプリケヌションに7぀のスレッドがある堎合、割り蟌みに必芁なRAMサむズは1 * 7 = 7 kBです。 マむクロコントロヌラヌのRAMが32 kBのみの堎合これは既に豊富なマむクロコントロヌラヌです、7 kBは20です あ〜



合蚈で、各スレッドのスタックには次のものが含たれおいる必芁がありたす。





割り蟌みに別のスタックを䜿甚する堎合は、次のオプションに進みたす。



画像








これで、前の䟋のISRに1 kBを1回だけ割り圓おる必芁がありたす。 これははるかに有胜なアプロヌチだず思いたす。組み蟌みシステムでは、RAMは非垞に高䟡なリ゜ヌスです。



このようなRTOSの原則の衚面的なレビュヌの埌、次に進みたす。



TNKernel



蚘事の冒頭で瀺したように、16ビットおよび32ビットのマむクロコントロヌラヌの開発にはTNKernelを䜿甚したした。



残念ながら、 PIC32のTNKernelポヌトの䜜成者であるAlex Borisovは、割り蟌みが割り蟌みスレッドのスタックを䜿甚する堎合にこのアプロヌチを䜿甚しおいたした。 倧量のRAMを無駄にし、私をずおも幞せにしたせんが、それ以倖の堎合TNKernelは芋栄えがよく、コンパクトで高速なので、䜿い続けたした。 X日たで、実際、すべおが非垞に悪いこずを知っお非垞に驚きたした。



PIC32の䞋のファむルTNKernel



私は別のプロゞェクトに取り組みたした車のキャンドルからのアナログ信号を分析し、ナヌザヌがこの信号のいく぀かのパラメヌタヌを芋るこずができるデバむス持続時間、振幅など。 信号は急速に倉化するため、十分に頻繁に枬定する必芁がありたす。1たたは2マむクロ秒ごずに1回です。



このタスクでは、PIC32ファミリのMicrochipプロセッサMIPSコア付きが遞択されたした。



タスクはそれほど難しくないはずですが、ある日問題が発生したした。デバむスが枬定を開始したずきに、プログラムがたったく予期しない堎所でクラッシュするこずがありたした。 「それは汚染された蚘憶であるに違いない」ず私は思い、非垞に怒っおいた。 メモリ砎損に関連する゚ラヌを芋぀けるプロセスは長く、完党に非自明である可胜性がありたす前述したように、MMUがなく、システム内のすべおのスレッドがすべおのRAMを䜿甚できるため、スレッドの1぀が制埡䞍胜になり、他のスレッドのメモリを台無しにした堎合、その埌、問題は実際の堎所から非垞に遠く離れた堎所に珟れ、゚ラヌが発生したす。



TNKernelには゜フトりェアスタックオヌバヌフロヌ制埡がないこずは既に述べたので、メモリ砎損の疑いがある堎合は、たず、スレッドがスタックでオヌバヌフロヌしおいるかどうかを確認する䟡倀がありたす。 スレッドが䜜成されるず、そのスタックは特定の倀PIC32の䞋のTNKernelでは0xffffffff



で初期化されるため、スタックの終わりが汚れおいるかどうかを簡単に確認できたす。 確認したずころ、実際、アむドル状態のスレッドスタックは明らかにいっぱいです。



画像








MIPSでは、スタックが倧きくなるため、 task_idle_stack[0]



はアむドルスレッドスタックで最埌に䜿甚可胜なワヌドです。



たあ、これはすでに䜕かです。 しかし、実際には、このスレッドのスタックには倧きなマヌゞンが割り圓おられおいたす。デバむスが正垞に動䜜しおいる堎合、880のうち玄300バむトしか䜿甚されおいたせん。 スタックをオヌバヌフロヌさせるある皮の野生のバグが存圚するはずです。



その埌、私は蚘憶をより泚意深く研究し始め、スタックが繰り返しパタヌンで満たされおいるこずが明らかになりたした。 参照シヌケンス0xFFFFFFFF, 0xFFFFFFFA, 0xA0006280, 0xA0005454







画像








そしお再び同じシヌケンス



画像








アドレス0xA000051C



および0xA00005A4



違いは136バむトです。 4ワヌドサむズで割りたす。これらは34ワヌドです。



うヌん、34ワヌド...これはMIPSのコンテキストのサむズです そしお、同じパタヌンが䜕床も繰り返されたす。 そのため、コンテキストは数回連続しお保存されおいるようです。 しかし...これはどのようにできたすか



残念ながら、すべおを理解するために倚くの時間がかかりたした。 たず、保存されたコンテキストをより詳现に調査しようずしたした。特に、䞭断されたストリヌムが埌で䜜業を再開する必芁があるアドレスがプログラムメモリにある必芁がありたす。 PIC32では、プログラムメモリは0x9D000000



から0x9D007FFF



領域にマッピングされるため、これらのアドレスは残りのデヌタず簡単に区別できたす。 これらのアドレスは保存されたコンテキストから0x9D012C28



。そのうちの1぀は0x9D012C28



です。 逆アセンブラを確認したす。



 9D012C04 AD090000 SW T1, 0(T0) 9D012C08 8FA80008 LW T0, 8(SP) 9D012C0C 8FA9000C LW T1, 12(SP) 9D012C10 01000013 MTLO T0, 0 9D012C14 01200011 MTHI T1, 0 9D012C18 8FA10010 LW AT, 16(SP) 9D012C1C 8FA20014 LW V0, 20(SP) 9D012C20 8FA30018 LW V1, 24(SP) 9D012C24 8FA4001C LW A0, 28(SP) 9D012C28 8FA50020 LW A1, 32(SP) # <-   9D012C2C 8FA60024 LW A2, 36(SP) 9D012C30 8FA70028 LW A3, 40(SP) 9D012C34 8FA8002C LW T0, 44(SP) 9D012C38 8FA90030 LW T1, 48(SP) 9D012C3C 8FAA0034 LW T2, 52(SP) 9D012C40 8FAB0038 LW T3, 56(SP) 9D012C44 8FAC003C LW T4, 60(SP) 9D012C48 8FAD0040 LW T5, 64(SP) 9D012C4C 8FAE0044 LW T6, 68(SP) 9D012C50 8FAF0048 LW T7, 72(SP) 9D012C54 8FB0004C LW S0, 76(SP) 9D012C58 8FB10050 LW S1, 80(SP) 9D012C5C 8FB20054 LW S2, 84(SP) 9D012C60 8FB30058 LW S3, 88(SP) 9D012C64 8FB4005C LW S4, 92(SP) 9D012C68 8FB50060 LW S5, 96(SP) 9D012C6C 8FB60064 LW S6, 100(SP) 9D012C70 8FB70068 LW S7, 104(SP) 9D012C74 8FB8006C LW T8, 108(SP) 9D012C78 8FB90070 LW T9, 112(SP) 9D012C7C 8FBA0074 LW K0, 116(SP) 9D012C80 8FBB0078 LW K1, 120(SP) 9D012C84 8FBC007C LW GP, 124(SP) 9D012C88 8FBE0080 LW S8, 128(SP) 9D012C8C 8FBF0084 LW RA, 132(SP) 9D012C90 41606000 DI ZERO 9D012C94 000000C0 EHB 9D012C98 8FBA0000 LW K0, 0(SP) 9D012C9C 8FBB0004 LW K1, 4(SP) 9D012CA0 409B7000 MTC0 K1, EPC
      
      





SPスタックポむンタヌに関連するアドレスからのLWロヌドワヌドからのこの特城的なシヌケンスは、コンテキスト回埩手順です。 これで、コンテキストがスタックから埩元されたずきにスレッドがプッシュされたこずは明らかです。 さお、これは䞭断が原因である可胜性がありたすが、なぜこれほど頻繁に連続するのでしょうか システムにはそれほど倚くの割り蟌みさえありたせん。



それ以前は、TNKernelを䜿甚したのは、それがどのように機胜するか明確に理解せずに䜿甚しただけでした。 だから、私はコアに深く入るのが少し怖かったです。 しかし、今回はそうしなければなりたせんでした。



PIC32でのTNKernelのコンテキストの切り替え



コンテキストスむッチングのプロセス党般に぀いおは既に説明したしたが、ここでは、このトピックを曎新し、特定の実装TNKernelの詳现を远加したしょう。



カヌネルは、コンテキストをスレッドAからスレッドBに切り替えるこずを決定するず、次のこずを行いたす。





ご芧のように、カヌネルがストリヌム蚘述子ずスタックの最䞊郚ぞのポむンタヌで動䜜しおいる間、短いクリティカルセクションがありたすそうでない堎合、これらのアクションの間に割り蟌みが生成されるず状況が発生する可胜性があり、もちろんデヌタの䞍完党性は誀った動䜜に぀ながりたす。



PIC32でのTNKernel割り蟌み



TNKernelにはPIC32の䞋に2皮類の割り蟌みがありたす。





珟圚、システム割り蟌みにのみ関心がありたす。 たた、TNKernelにはこのタむプの割り蟌みに察する制限がありたす。アプリケヌション内のすべおのシステム割り蟌みは同じ優先床を持぀必芁があるため、これらの割り蟌みはネストできたせん。



小さなリマむンダヌずしお、割り蟌みが生成されたずきに䜕が起こるかを次に瀺したす。





これでISRがアクティブになり、スタックの䜿甚は次のずおりです。



画像








すでに述べたように、このアプロヌチではスレッドに必芁なスタックサむズが倧幅に増加したす。各スレッドは、次の芁玠を収容できる倧きさでなければなりたせん。





ISRスタックにスレッドの数を掛ける必芁はありたせんが、䞀般的に蚀っお、私はISRスタックに察応する準備ができおいたした。



コンテキスト切り替え時の䞭断



そしお、コンテキストの切り替え䞭に割り蟌みが生成された堎合、぀たり 珟圚のスレッドのコンテキストはスタックに保存されるか、スタックから埩元されたすか



あなたが掚枬したず思うなぜなら コンテキストの保存/埩元プロセス䞭の䞭断は犁止されおいたせん。コンテキストは2回保存されたす。 ここに



画像








そのため、カヌネルがスレッドBからスレッドAに切り替えるこずを決定するず、次のようになりたす。





次の図が衚瀺されたす。



画像








参照コンテキストはストリヌムBのスタックに2回保存されたす。実際、スタックがいっぱいでない堎合、これは灜害ではありたせん。 この二重保存されたコンテキストは、スレッドBが制埡を取埗するずすぐに2回埩元されたす。 たずえば、スレッドAが䜕かを埅ち、カヌネルが制埡をスレッドBに戻すず仮定したす。





実際、スレッドAに切り替える前にスレッドBのスタックにコンテキストが保存された時点に戻っおいたす。したがっお、コンテキストの保存を続けたす。





その埌、スレッドBは、䜕も起こらなかったかのように機胜し続けたす。



画像








ご芧のずおり、実際には䜕も砎るこずはありたせんでしたが、この調査から重芁な結論を䞋す必芁がありたす。各ストリヌムのコンテキストに含たれるべきであるずいう仮定は正しくありたせんでした。 少なくずも、1぀ではなく2぀のコンテキストに察応する必芁がありたす。



芚えおいるように、TNKernelのすべおのシステム割り蟌みは同じ優先床を持぀必芁があるため、ネストするこずはできたせん。぀たり、コンテキストを2回以䞊保存できないこずを意味したす。



その堎合、最終的な結論は次のずおりです。各スレッドのスタックには次のものが含たれおいる必芁がありたす。





ああ...各ストリヌムにさらに136バむト。繰り返したすが、スレッド数で乗算したす。7スレッドの堎合、これはほが1キロバむトで、32 kBのさらに3です。



わかった いいね私は、おそらく、このような状況に同意するだろうが、私たちの最終的な結論は、実際には、それは最終的ではありたせん。すべおのより悪化し。



より深く掘る



二重コンテキストをより深く保存するプロセスを詳しく調べおみたしょう前回の調査の埌でも、スレッドスタックにコンテキストが䜕回保存されたのかを説明するこずはできたせん。 、および別のシステム割り蟌みが発生した堎合、珟圚のISRが制埡を返した埌に凊理されたす。



もう䞀床この図を芋おみたしょう。ISRはすでに制埡を返しおいるので、ストリヌムAに切り替えたした。



画像








この時点で、プロセッサの割り蟌みレベルが再び䞋げられ、コンテキストがスレッドBスタックに2回保存されたす。



次は䜕だず思いたすか



そうです。ストリヌムBに切り替えお、コンテキストを埩元しおいるずきに、別の割り蟌みが発生する可胜性がありたす。考慮しおください



画像








はい、コンテキストはすでに3回スレッドスタックに保存されおいたす。さらに悪いこずに、同じ割り蟌みである可胜性もありたす。同じ割り蟌みでスレッドスタックのコンテキストを数回保存できたす。



したがっお、割り蟌みが定期的に発生し、スレッドが前埌に切り替わるのずたったく同じ速床で運が悪ければ、コンテキストはストリヌムスタックに䜕床も保存され、最終的にはスタックオヌバヌフロヌ。



これは、PIC32でのTNKernelポヌトの䜜成者によっお明らかに考慮されおいたせんでした。



そしおこれがたさにアナログ信号を枬定したデバむスで起こったこずです。ADC割り蟌みはそのような「成功した」呚波数で正確に生成されたした。起こったこずは次のずおりです。





もちろん、これはADC割り蟌みが非垞に頻繁に生成されるこずですが、システムの動䜜はたったく受け入れられたせん。正しい動䜜このような頻繁な割り蟌みの生成が停止されるたで、スレッドは䞀床に制埡を受信しなくなりたす。スタックは保存されたコンテキストの束で満たされるべきではなく、割り蟌みが頻繁に生成されなくなるず、システムは静かに動䜜し続けたす。



そしおもう1぀の結果そのような定期的な割り蟌みがない堎合でも、アプリケヌションに存圚するさたざたな割り蟌みが䞍幞な瞬間に発生しお、コンテキストが䜕床も保存される可胜性がれロではないたたです。組み蟌みシステムは、倚くの堎合、時間数幎および数幎にわたる連続動䜜甚に蚭蚈されおいたす。たずえば、車のアラヌム、オンボヌドコンピュヌタヌなどです。そしお、デバむスの動䜜時間が無限になる傟向がある堎合、そのようなむベントの発生の可胜性は䞀臎する傟向がありたす。遅かれ早かれ、これは起こっおいたす。もちろん、これは受け入れられないので、物事の珟圚の状態を離れるこずはできたせん。



良い少なくずも今は問題の原因を知っおいたす。次の質問この理由を排陀する方法



おそらく、最速か぀最もダヌティなハックは、コンテキストの保存/埩元䞭の割り蟌みを単に犁止するこずです。はい、これは問題を解決したすが、それは非垞に非垞に悪い解決策ですクリティカルセクションはできるだけ短くする必芁があり、そのような長時間の割り蟌みを犁止するこずはほずんど良い考えではありたせん。



はるかに優れた゜リュヌションは、割り蟌み甚に別のスタックを䜿甚するこずです。



TNKernelを改善しようずしおいたす



Anders MontonenによるPIC32の䞋には別のTNKernelポヌトがあり、割り蟌み甚に別のスタックを䜿甚したす。しかし、このポヌトには、Alex Borisovのポヌトにある䟿利な機胜はありたせん。システム割り蟌みを宣蚀するための䟿利なSyshnyマクロ、システムティックを操䜜するためのサヌビスなどです。



そこで、私はそれをフォヌクし、必芁なものを実装するこずにしたした。もちろん、カヌネルでこのような倉曎を行うためには、その仕組みを十分に理解する必芁がありたした。そしお、TNKernelコヌドを勉匷すればするほど、それが奜きではなくなりたした。TNKernelは、ひざたずいお曞かれたプロゞェクトの印象を䞎えたす。重耇したコヌドが倚く、敎合性がありたせん。



悪い実装䟋



ルヌル「1぀の入口点、1぀の出口点」の違反



カヌネルのどこにでも芋られる最も䞀般的な䟋は、次のようなコヌドです。



 int my_function(void) { tn_disable_interrupt(); //-- do something if (error()){ //-- do something tn_enable_interrupt(); return ERROR; } //-- do something tn_enable_interrupt(); return SUCCESS; }
      
      





耇数の挔算子return



があり、さらに戻る前に特定のアクションを実行する必芁がある堎合は、そのようなコヌドが問題の鍵になりたす。次のように曞き換えるこずをお勧めしたす。



 int my_function(void) { int rc = SUCCESS; tn_disable_interrupt(); if (error()){ rc = ERROR; } else { //-- so something } tn_enable_interrupt(); return rc; }
      
      





さお、戻る前に割り蟌みを蚱可する必芁があるこずを芚えおおく必芁はありたせん。コンパむラに䜜業を任せおください。



あなたは結果のために遠くに行く必芁はありたせん珟時点で最新のTNKernel 2.7からの機胜はここにありたす



 int tn_sys_tslice_ticks(int priority,int value) { TN_INTSAVE_DATA TN_CHECK_NON_INT_CONTEXT tn_disable_interrupt(); if(priority <= 0 || priority >= TN_NUM_PRIORITY-1 || value < 0 || value > MAX_TIME_SLICE) return TERR_WRONG_PARAM; tn_tslice_ticks[priority] = value; tn_enable_interrupt(); return TERR_NO_ERR; }
      
      





参照間違ったパラメヌタヌが関数に枡されるず、関数は戻りTERR_WRONG_PARAM



、割り蟌みは犁止されたたたになりたす。1぀の゚ントリポむント、1぀の出口ポむントのルヌルに埓えば、この゚ラヌは発生しなかった可胜性が高くなりたす。



DRY原則の違反

繰り返しおはいけたせん

オリゞナルのTNKernel 2.7コヌドには、倧量のコヌドの重耇が含たれおいたす。倚くの類䌌したこずは、単玔なコピヌを通じお異なる堎所で行われたす。



いく぀かの類䌌したサヌビスたずえば、メッセヌゞを送信するサヌビスストリヌムから、埅機せずにストリヌムから、たたは割り蟌みからがある堎合、これらは3぀の非垞に類䌌した関数であり、1-2行が異なり、物事を䞀般化する詊みはありたせん。



ストリヌム状態の切り替えは非垞に党䜓的に実装されおいたす。たずえば、スレッドをRunnableからWaitに移動する必芁がある堎合、1぀のフラグをクリアしお別のフラグを蚭定するだけでは十分ではありたせん。スレッドが存圚する開始キュヌからも削陀する必芁がありたす。埅機、埅機キュヌぞのスレッドの远加必芁な堎合、タむムアりトの蚭定必芁な堎合など TNKernel 2.7には、このための䞀般的なメカニズムはありたせん。それぞれの堎合に、コヌドはここに蚘述されおいたす。



それたでの間、これらのこずを実装する正しい方法は、状態ごずに3぀の関数を蚘述するこずです。





珟圚、ある状態から別の状態にストリヌムを転送する必芁がある堎合、通垞は2぀の関数を呌び出す必芁がありたす。1぀は叀い状態からストリヌムを取り出し、もう1぀は新しい状態に入る関数です。シンプルで信頌性の高い。



DRYルヌルの定期的な違反の結果ずしお、䜕かを倉曎する必芁がある堎合、いく぀かの堎所でコヌドを線集する必芁がありたす。蚀うたでもなく、これは悪質な行為です。



芁するに、TNKernelにはさたざたな実装が必芁なものが山ほどありたす。



培底的にリファクタリングするこずにしたした。䜕も壊さないようにするために、カヌネルの単䜓テストの実装を開始したした。そしおすぐに、TNKernelがたったくテストされおいないこずが明らかになりたした。カヌネル自䜓に䞍快なバグがありたす



発芋され修正されたバグに関する特定の情報に぀いおは、TNKernelを再実装する理由。



䌚うTNeo



ある時点で、私がやっおいるこずが「リファクタリング」の範囲をはるかに超えおいるこずが明らかになりたした。実際、私はほずんどすべおを完党に曞き盎したした。さらに、TNKernel APIには長い間気になっおいたこずがいく぀かあったので、APIを少し倉曎したした。たた、芋逃したこずがあるので、それらを実装したした。タむマヌ、スタックの゜フトりェア制埡オヌバヌフロヌ、耇数のキュヌからのメッセヌゞを埅機する機胜などです。



名前に぀いおはかなり前から考えおいたした。TNKernelずの盎接接続を瀺したいが、新鮮でクヌルなものを远加したかったのです。したがっお、最初の名前はTNeoKernelでした。



しかし、しばらくしお、それ自䜓は簡朔なTNeoに瞮小されたした。



TNeoには、RTOS甚の暙準機胜セットに加えお、どこにもない䟿利な機胜がありたす。ほずんどの機胜はオプションであるため、それらを無効にしおメモリを節玄し、パフォヌマンスをわずかに向䞊させるこずができたす。





プロゞェクトはGitHubTNeoでホストされおいたす。



珟圚、カヌネルは次のアヌキテクチャに移怍されおいたす。





完党なドキュメントは、htmlずpdfの2぀のバヌゞョンで入手できたす。





TNeoの実装



もちろん、1぀の蚘事でカヌネル党䜓の実装をカバヌするこずは非垞に困難です。代わりに、自分にははっきりしおいなかったこずを思い出しお、これらのこずに集䞭しようずしたす。



しかし、たず第䞀に、1぀の内郚構造、リンクリストを考慮する必芁がありたす。



関連リスト



リンクリストはよく知られたデヌタ構造であり、読者はおそらくそれをすでに知っおいるでしょう。ただし、完党を期すために、TNeoでのリンクリストの実装を芋おみたしょう。



リンクリストは、TNeoのあらゆる堎所で䜿甚されたす。より具䜓的には、埪環双方向リンクリストが䜿甚されたす。Cの構造は次のずおりです。



 /** * Circular doubly linked list item, for internal kernel usage. */ struct TN_ListItem { /// /// pointer to previous item struct TN_ListItem *prev; /// /// pointer to next item struct TN_ListItem *next; };
      
      





src / core / tn_list.cで宣蚀されおいたす。



ご芧のずおり、構造には同じ構造のむンスタンスぞのポむンタが含たれおいたす。぀たり、前の芁玠ず次の芁玠です。このような構造のチェヌンを線成しお、次のように接続できたす。



画像








これはすばらしいこずですが、あたり䜿い道がありたせん。各オブゞェクトにいく぀かの有甚なデヌタが必芁ですよね



解決策はstruct TN_ListItem



、むンスタンスをバむンドしたい別の構造に埋め蟌むこずです。たずえば、次のような構造があるずしたすMyBlock







 struct MyBlock { int field1; int field2; int field3; };
      
      





そしお、この構造のむンスタンスのシヌケンスを構築できるようにしたいず思いたす。たず第䞀に、私たちはstruct TN_ListItem



それをそれに組み蟌みたす。



この䟋struct TN_ListItem



では、先頭に配眮するのが論理的ですが、先頭だけでなく、どこにでも配眮できるstruct MyBlock



ものを匷調するstruct TN_ListItem



ために、䞭倮に配眮したしょう。



 struct MyBlock { int field1; int field2; //-- say, embed it here. struct TN_ListItem list_item; int field3; };
      
      





OK、そしお今いく぀かのむンスタンスを䜜成したす



 //-- blocks to put in the list struct MyBlock block_first = { /* ... */ }; struct MyBlock block_second = { /* ... */ }; struct MyBlock block_third = { /* ... */ };
      
      





そしお今、もう䞀぀の重芁なポむントリスト自䜓を䜜成したす。これは空でも空でもありたせん。これは同じ構造の単なるむンスタンスですが、struct TN_ListItem



どこにも埋め蟌たれおいたせん。



 //-- list head struct TN_ListItem my_blocks;
      
      





リストが空の堎合、前の芁玠ず次の芁玠ぞのポむンタの䞡方が自分自身を指したすmy_blocks



。



これで、リストを次のように敎理できたす。



画像








次のようなコヌドを䜿甚しお䜜成できたす。



 //-- ,         , // .     . my_blocks.next = &block_first.list_item; my_blocks.prev = &block_third.list_item; block_first.list_item.next = &block_second.list_item; block_first.list_item.prev = &my_blocks; block_second.list_item.next = &block_third.list_item; block_second.list_item.prev = &block_first.list_item; block_third.list_item.next = &my_blocks; block_third.list_item.prev = &block_second.list_item;
      
      





これは玠晎らしいこずですが、䞊蚘のこずから、ただでTN_ListItem



はなく、のリストがあるこずがわかりMyBlock



たす。しかし、アむデアは、開始MyBlock



から開始たでのオフセットlist_item



がすべおのむンスタンスで同じであるずいうこずですMyBlock



。ですから、ぞのポむンタがありTN_ListItem



、このむンスタンスが組み蟌たれおいるこずがわかっおいる堎合MyBlock



、ポむンタから特定のオフセットを枛算し、ぞのポむンタを取埗できたすMyBlock



。



これには特別なマクロがありたすcontainer_of()



src / core / internal / _tn_sys.hで定矩されおいたす



 #if !defined(container_of) /* given a pointer @ptr to the field @member embedded into type (usually * struct) @type, return pointer to the embedding instance of @type. */ #define container_of(ptr, type, member) \ ((type *)((char *)(ptr)-(char *)(&((type *)0)->member))) #endif
      
      





のようにポむンタを持っおいるためTN_ListItem



、MyBlock



次のように倖郚ぞのポむンタを取埗したす。



 struct TN_ListItem *p_list_item = /* ... */; struct MyBlock *p_my_block = container_of(p_list_item, struct MyBlock, list_item);
      
      





これで、たずえば、リスト内のすべおの芁玠を走査できたすmy_blocks



。



 //-- loop cursor struct TN_ListItem *p_cur; //--      my_blocks for (p_cur = my_blocks.next; p_cur != &my_blocks; p_cur = p_cur->next) { struct MyBlock *p_cur_block = container_of(p_cur, struct MyBlock, list_item); //-- , p_cur_block     MyBlock }
      
      





このコヌドは機胜したすが、やや混乱し、リストの実装の詳现でオヌバヌロヌドされたす。リストをクロヌルするための特別なマクロを甚意するこずをお勧めしたすsrc / core / internal / _tn_list.hファむルで_tn_list_for_each_entry()



定矩されたす。



次に、すべおの詳现を非衚瀺にしお、むンスタンスのリストを次のMyBlock



ように移動できたす。



 struct MyBlock *p_cur_block; _tn_list_for_each_entry( p_cur_block, struct MyBlock, &my_blocks, list_item ) { //-- , p_cur_block     MyBlock }
      
      





結果ずしお、これはオブゞェクトのリストを䜜成する非垞に䟿利な方法です。そしお、もちろん、同じオブゞェクトを耇数のリストに含めるこずができたす。このため、構造には耇数の組み蟌みむンスタンスが必芁struct TN_ListItem



です。このオブゞェクトを含める予定のリストごずに。






Hacker Newsの元の蚘事英語ぞのリンクを公開した埌、読者の1人が質問したした。リンクリストstruct TN_ListItem



は他の構造に埋め蟌むこずで実装されるのはなぜですか。



 struct TN_ListItem { TN_ListItem *prev; TN_ListItem *next; void *data; //--      }
      
      





この質問は興味深いので、蚘事自䜓に回答を含めるこずにしたした



。TNeoはヒヌプからメモリを割り圓おたせん。ポむンタが特定のカヌネルサヌビスにパラメヌタずしお枡されるオブゞェクトでのみ機胜したす。実際、これは非垞に優れおいたす。倚くの堎合、組み蟌みシステムは束をたったく䜿甚したせん。圌女の行動は十分に決定されおいたせん。



したがっお、たずえば、タスクがミュヌテックスを予期するようになるず、このタスクはこの特定のミュヌテックスを埅機しおいるタスクのリストに远加され、この操䜜の耇雑さはO1です。垞に䞀定のそしお、ちなみに小さな時間で実行されたす。



アプロヌチcを䜿甚する堎合void *data;



、2぀のオプションがありたす。





. , , Linux (. «Linux Kernel Development» by Robert Love). helper- ( ) Linux, : , GCC- , typeof()



, .. TNeo GCC.



, TNeo :





()



タスク、たたはスレッドはシステムの最も重芁な郚分です。結局のずころ、これはたさにRTOSが䞀般的に存圚するものです。比范的単玔なマむクロコントロヌラヌ向けのTNeoおよびその他のRTOSのコンテキストでは、タスクは、あたかも他のタスクず䞊行しお実行されるサブルヌチンです。



䞀般的に、スレッドずいう甚語を奜みたすが、TNKernelではタスクずいう甚語を䜿甚しおいるため、TNeoではタスクずいう甚語も䜿甚しお互換性を維持しおいたす。この蚘事では、䞡方の甚語を同じ意味で䜿甚したす。



システム内の既存の各タスクにはstruct TN_Task



、src / core / tn_tasks.hファむルで宣蚀された独自の蚘述子がありたす。これはかなり倧きな構造です。



タスク蚘述子の最初の芁玠は、タスクスタックの最䞊郚ぞのポむンタヌです。stack_cur_pt



。この事実は、アセンブラコンテキストスむッチングルヌチンで積極的に䜿甚されおいたす。タスクハンドルぞのポむンタがあれば、単玔にリダむレクトするこずができ、結果の倀はタスクスタックの最䞊郚を指したす。かなり快適です。



珟圚および次のタスク



カヌネルには2぀のポむンタヌがありたす。珟圚実行䞭のタスクず、次に開始されるタスクです。



䞭のsrc /コア/内郚/ _tn_sys.h



 /// task that is running now extern struct TN_Task *_tn_curr_run_task; /// task that should run as soon as possible (if it isn't equal to /// _tn_curr_run_task, context switch is needed) extern struct TN_Task *_tn_next_task_to_run;
      
      





コメントからわかるように、それらが異なる蚘述子を指しおいる堎合は、コンテキストをできるだけ早く切り替える必芁がありたす。



タスクの優先順䜍ず起動キュヌ



タスクには異なる優先順䜍がありたす。䜿甚可胜な優先順䜍の最倧数は、プロセッサワヌドの次元によっお決たりたす。16ビットマむクロコントロヌラヌでは16の優先順䜍があり、32ビットの優先順䜍では32です。これが理由で明らかになるので、䞀般的に蚀えば、私はアプリケヌションにこれ以䞊必芁ずしたせん。 5぀たたは6぀の優先順䜍よりも。



優先床ごずに、この優先床を持぀実行可胜タスクのリンクリストがありたす。



䞭のsrc /コア/内郚/ _tn_sys.h



 /// list of all ready to run (TN_TASK_STATE_RUNNABLE) tasks extern struct TN_ListItem _tn_tasks_ready_list[TN_PRIORITIES_CNT];
      
      





TN_PRIORITIES_CNT



ナヌザヌ蚭定可胜な倀はどこにありたすかもちろん、最倧倀を超えるこずはできたせん。



タスク蚘述子には、struct TN_ListItem



次のタスクリストのいずれかに远加されるむンスタンスがありたす。



 /** * Task */ struct TN_Task { /* ... */ /// queue is used to include task in ready/wait lists struct TN_ListItem task_queue; /* ... */ }
      
      





カヌネルには、各ビットが1぀の優先床に察応するビットマスク1ワヌドサむズもありたす。ビットが蚭定されおいる堎合、これは、察応する優先床を持぀タスクを開始する準備がシステムにあるこずを意味したす。



 /// bitmask of priorities with runnable tasks. /// lowest priority bit (1 << (TN_PRIORITIES_CNT - 1)) should always be set, /// since this priority is used by idle task which should be always runnable, /// by design. extern volatile unsigned int _tn_ready_to_run_bmp;
      
      





そのため、カヌネルはどのタスクを転送する必芁があるかを刀断する必芁がある堎合、アヌキテクチャを実行したす-の特定のfind-first-set呜什を実行し_tn_ready_to_run_bmp



、すぐに開始する準備ができたすべおのタスクから最高の優先床を取埗したす 次に、察応する優先床のタスクリストから最初のタスクを取埗し、それに制埡を移したす。



もちろん、タスクがRunnable状態実行準備完了に出入りする堎合、察応するビットを提䟛したすが_tn_ready_to_run_bmp



、これは非垞に簡単です。䞀般的に、すべおが高速です。



タスクコンテキスト



タスクが珟圚実行されおいない堎合、そのコンテキストすべおのレゞスタの倀ず、タスクを続行するプログラムメモリ内のアドレスがスタックに栌玍されるこずを思い出しおください。たたstack_cur_ptr



、タスク蚘述子では、この保存されたコンテキストの最䞊郚を正確に指したす。



カヌネルがサポヌトする各アヌキテクチャMIPS、ARM Cortex-Mなどには、特定のコンテスト構造がありたす。これらのすべおのレゞスタがスタック䞊にどのように配眮されおいるか。タスクが䜜成され、開始の準備が完了するず、スタックの先頭に「初期化」コンテキストが入力されるため、タスクが最終的に制埡を取埗するず、この初期化コンテキストが埩元されたす。したがっお、各タスクは分離されたクリヌンな環境で実行されたす。



タスク状態



タスクは、次のいずれかの状態になりたす。





タスクが状態を離れるか、逆にその状態に入るず、実行する必芁がある特定のアクションセットがありたす。 䟋





実際、たずえば、タスクがRunnable状態を離れるずき、タスクが新しい状態になるのを心配する必芁はありたせん。䞀時停止䌑眠問題ではありたせん。どの堎合でも、垞に起動キュヌから削陀しお、残りの必芁なアクションを実行する必芁がありたす。



これを実装する簡単で信頌性の高い方法は、状態ごずに3぀の関数を蚘述するこずです。





したがっお、src / core / tn_tasks.cファむルには次の関数がありたす。



 void _tn_task_set_runnable(struct TN_Task * task) { /* ... */ } void _tn_task_clear_runnable(struct TN_Task * task) { /* ... */ } void _tn_task_set_waiting( struct TN_Task *task, struct TN_ListItem *wait_que, enum TN_WaitReason wait_reason, TN_TickCnt timeout ) { /* ... */ } void _tn_task_clear_waiting(struct TN_Task *task, enum TN_RCode wait_rc) { /* ... */ } //-- etc.
      
      





たた、ある状態から別の状態にタスクを転送する必芁がある堎合、通垞は次のように芁玄されたす。



  _tn_task_clear_dormant(task); _tn_task_set_runnable(task);
      
      





そしお、すべおの内政が解決されるこずを確信できたす。かっこいい



タスク䜜成



タスクが完了する必芁があるものが1぀ありたす。スタック甚のスペヌスです。そのため、タスクを䜜成する前に、このタスクのスタックずしお䜿甚される配列を割り圓お、この配列を残りのものずずもにに枡す必芁がありtn_task_create()



たす。



カヌネルは、タスクスタックの先頭をこの配列の先頭たたはアヌキテクチャに応じお末尟に蚭定し、タスクを䌑眠状態にしたす。珟時点では、圌女はただ打ち䞊げの準備ができおいたせん。ナヌザヌが呌び出すずtn_task_activate()



たたはフラグTN_TASK_CREATE_OPT_START



が枡された堎合tn_task_create()



、カヌネルは次のように動䜜したす。





これで、スケゞュヌラがこのタスクを凊理し、必芁に応じお実行したす。



タスクの開始方法を芋おみたしょう。



タスク起動



もちろん、タスクを実行するために必芁な手順の正確なシヌケンスは、アヌキテクチャによっお異なりたす。ただし、䞀般的な考え方ずしお、カヌネルは次のように機胜したす。





カヌネルがタスクに制埡をどのように正確に転送するかは、すでにアヌキテクチャに完党に䟝存しおいたす。たずえば、MIPSでは、プログラムカりンタヌタスクをEPCレゞスタ䟋倖プログラムカりンタヌに保存し、呜什を実行するeret



䟋倖から戻る必芁がありたす。぀たり、コアはプロセッサを「だたし」、「通垞の」割り蟌みから戻っおきたかのように動䜜したす。実行埌eret



、珟圚のPCプログラムカりンタヌはEPCに保存されおいる倀に蚭定され、実際、タスクは実行を継続したす。



コンテキストスむッチ



カヌネルが珟圚のタスクの実行を䞀時停止し、次のタスクに制埡を移すプロセスは、「コンテキストスむッチング」ず呌ばれたす。この手順は、垞に最䜎の優先床を持぀特別なISRで実行されたす。そのため、コンテキストを切り替える必芁がある堎合、カヌネルは察応する割り蟌みビットを蚭定したす。ナヌザヌタスクが珟圚実行されおいる堎合、コンテキストを切り替えるISRは、そこでプロセッサによっお呌び出されたす。このビットが割り蟌みの優先順䜍に関係なく他のISRから蚭定されおいる堎合、コンテキスト切り替えは埌で開始されたす珟圚実行䞭のすべおのISRが制埡を返したす。



もちろん、コンテキストの切り替えに䜿甚される特定の割り蟌みは、アヌキテクチャによっお異なりたす。䟋えば、ARM䞊のCortex-M䞊のOSのコンテキストスむッチのために提䟛される特別な䟋倖があるPIC32コア゜フトりェア割り蟌み0を䜿甚したすPendSV



。



カヌネルISRスむッチングコンテキストが呌び出されるず、次のこずを行いたす。





コンテキストの切り替えは、珟圚のタスクよりも優先床の高いタスクが実行可胜になるず発生したす。たたは、珟圚のタスクが埅機状態になったずき。



たずえば、優先床の䜎い送信機ず優先床の高い受信機の2぀のタスクがあるずしたす。受信者はキュヌからメッセヌゞを取埗しようずするため、キュヌが空の堎合、タスクは埅機状態になりたす。実行できなくなったため、カヌネルは優先床の䜎いTransmitterタスクに移行したす。



画像








図からわかるように、䜎優先床のTransmitterタスクがserviceを呌び出しおメッセヌゞを送信するずtn_queue_send()



、このタスクは高優先床のReceiverを優先しおカヌネルによっおプッシュされたす。そのため、tn_queue_send()



送信機に戻るたでに、倚くのこずが起こりたした。





したがっお、システムの応答性は非垞に高くなりたす。タスクに適切な優先床を蚭定するず、むベントは非垞に迅速に凊理されたす。



タスクアむドル



TNeoには1぀の特別なタスクがありたすアむドル。最も䜎い優先床を持ちナヌザヌタスクにそれほど䜎い優先床を蚭定するこずはできたせん、垞に実行する準備ができおいる必芁がありたす。したがっお、_tn_ready_to_run_bmp



垞に少なくずも1぀のビットが蚭定されたす。明らかに、実行する準備ができおいる他のタスクがない堎合、カヌネルはこのタスクに制埡を枡したす。



アプリケヌションは、アむドルタスクから継続的に呌び出されるコヌルバック関数を実装できたす。これは、さたざたな䟿利な目的に䜿甚できたす。





なぜなら アむドルタスクは垞に開始する準備ができおいる必芁がありたす。タスクを埅機状態にするこずができるこのコヌルバックからカヌネルサヌビスを呌び出すこずは犁止されおいたす。



タむマヌ



カヌネルには時間の抂念が必芁です。静的ティックず動的ティックの 2぀のスキヌムを䜿甚できたす。



静的ティック



静的ティックは、タむムアりトを実装する最も簡単な方法です。定期的な間隔で割り蟌みを生成するハヌドりェアタむマヌが必芁です。この蚘事では、このようなタむマヌをシステムタむマヌず呌びたす。このタむマヌの期間はナヌザヌ定矩です私は垞に1 msを䜿甚したしたが、もちろん、任意の期間を蚭定できたす。このタむマヌのISRでは、特別なカヌネルサヌビスを呌び出すだけです。



 //-- example for PIC32, hardware timer 5 interrupt: tn_p32_soft_isr(_TIMER_5_VECTOR) { INTClearFlag(INT_T5); tn_tick_int_processing(); }
      
      





tn_tick_int_processing()



呌び出されるたびに、システムティックが発生したず蚀いたす。この関数内で、カヌネルは、䜕らかのタむマヌのコヌルバックを呌び出す時間であるかどうかを確認し、そうである堎合は呌び出したす。



タむマヌの最も単玔な実装は次のようになりたす。すべおのアクティブタむマヌのリンクリストがあり、システムティックごずにタむマヌのリスト党䜓を調べ、各タむマヌに察しお次のアクションを実行したす。





このアプロヌチには重倧な欠点がありたす。





おそらく、最埌のポむントは組み蟌みシステムではそれほど重芁ではありたせん。誰もがこのような倚数のタむマヌを必芁ずするこずはたずありたせん。ただし、最初のポむントは非垞に重芁です。タむマヌコヌルバックからタむマヌを远加たたは再構成できるようにしたいのです。



したがっお、TNeoはより巧劙なアプロヌチを取りたす。基本的な考え方はLinuxカヌネルから取られおいたすが、1組み蟌みシステムはLinuxが蚘述されおいるマシンよりもリ゜ヌスが倧幅に少ないため、2TNeoはLinuxのスケヌリングず同様にスケヌリングする必芁がありたせん。Linuxデバむスドラむバヌ、第3版のLinuxタむマヌに぀いお読むこずができたす。





この本は、リンクhttp://lwn.net/Kernel/LDD3/ で誰でも利甚できたす



。そのため、TNeoでのタむマヌの実装に目を向けたす。



Nのカスタム倀があり、これは2のべき乗でなければなりたせん。通垞の倀は4、8、たたは16です。リストの配列いわゆるティックリストがあり、この配列はN個の芁玠で構成されおいたす。぀たり、N個のティックリストがありたす。



システムの数が1からN-1になった埌にタむマヌが切れるず、このタむマヌはこれらのティックリストの1぀に远加されたす。ティックリスト番号は、適切なマスクをタむムアりトにオヌバヌレむするだけで蚈算されたすこれが、Nが2のべき乗でなければならない理由です。タむマヌが埌で期限切れになるず、「ゞェネリック」リストに远加されたす。



もちろん、マスクはNに察応したす。たずえば、N = 4の堎合、マスクが䜿甚されたす0b0011



; N = 8の堎合、マスクが䜿甚されたす0b0111



。



N番目のシステムティックごずに、カヌネルは「共通」リストのすべおのタむマヌを通過し、各タむマヌに察しお次のアクションを実行したす。





そしお、システムティックごずに、察応する1぀のティックリストを調べ、このリストからすべおのタむマヌを無条件にコヌルバックしたす。この゜リュヌションは、䞊蚘で説明した最も単玔なものよりも効率的です。タむマヌのリスト党䜓をNティックで1回だけ通過する必芁があり、残りの堎合は、リストが既に準備されおいるすべおのタむマヌを無条件で「シュヌト」したす。



気配りのある読者は疑問に思うかもしれたせん。実際にN個のリストがあるのに、なぜN-1ティックリストのみを䜿甚するのでしょうか。これは、タむマヌコヌルバックからタむマヌを倉曎できるようにしたいだけだからです。 N個のリストを䜿甚し、タむムアりトがNに等しいタむマヌをナヌザヌが远加するず、珟圚実行䞭のリストに新しいタむマヌが远加されたす。これはできたせん。



N-1リストを䜿甚する堎合、珟圚枡されおいるティックリストに新しいタむマヌを远加できないこずを保蚌したすちなみに、このリストからタむマヌを削陀できたすが、これにより問題は発生したせん 。



このタむマヌの実装は倚くのアプリケヌションで十分に受け入れられたすが、時には理想的ではありたせんデバむスがほずんど䜕もしないでほずんどの時間を費やしおいる堎合、MCUは定期的に起動しおシステムティックを凊理する必芁がありたすそしお、眠りに戻りたす。このために、動的ティックが実装されたした。



動的ティック



基本的な考え方は、無駄な呌び出しを取り陀くこずですtn_tick_int_processing()



。カヌネルが100システムティックを介しおタスクを起動する必芁がある堎合、システムティックのtn_tick_int_processing()



100呚期埌に正確に呌び出す必芁がありたすもちろん、倖郚非同期むベントが発生しおこれを倉曎する可胜性がありたす。



これを行うには、カヌネルがアプリケヌションずチャットできる必芁がありたす。





したがっお、動的ティックモヌドがアクティブTN_DYNAMIC_TICK



1に蚭定の堎合、アプリケヌションはシステム起動時にこれらのコヌルバックぞのポむンタヌを提䟛する必芁がありたす。もちろん、これらのコヌルバックの実際の実装は、MCUのタむプに完党に䟝存したす同じアヌキテクチャでも、異なる呚蟺機噚などを持぀膚倧な数のMCUがありたす。したがっお、これらのコヌルバックの正しい実装はアプリケヌションにありたす。



システムを起動



正垞に機胜するには、カヌネルに次のものが必芁です。





そのため、システムを起動する前に、アプリケヌションはIdleタスクスタックず割り蟌みに配列を割り圓お、Idleタスクコヌルバック空の堎合もあるを提䟛し、少なくずも1぀通垞は1぀のナヌザヌタスクを䜜成する必芁がある特別なコヌルバックを提䟛する必芁がありたす。これは起動される最初のタスクであり、私のアプリケヌションでは通垞、task_init



たたはを呌び出したすtask_conf



。明らかに、アプリケヌションを初期化しおいたす。アプリケヌションする必芁がありたす。



main()









カヌネルは次のこずを行いたす。





その埌、システムは通垞どおり動䜜したす。ナヌザヌタスクが制埡を取り、ナヌザヌタスクが実行できるすべおの操䜜を実行できたす。通垞、圌女は次のこずを行いたす。





実装の詳现



さお、私はある時点でやめるべきだず思いたす。率盎に蚀っお、この蚘事では実装のすべおの詳现を説明するこずはできたせん。



党䜓像を理解するために必芁な最も興味深いトピックに觊れようずしたした。他のトピックに぀いおは、読者は゜ヌスコヌドを簡単に調べるこずができたす。私はそれを本圓に良く理解できるようにしようずしたした。゜ヌスが利甚可胜ですそれらを䜿甚しおください



ご質問がある堎合は、コメントでお尋ねください。



単䜓テスト



通垞、ナニットテストに぀いお話すずきは、ホストマシンで実行されるテストを意味したす。しかし、残念ながら、組み蟌みシステムに䜿甚されるコンパむラには倚くの堎合゚ラヌが含たれおいるため、ハヌドりェアで盎接カヌネルをテストするこずにしたした。したがっお、カヌネルがデバむス䞊で100動䜜しおいるこずを確認できたす。



テストの実装の簡単な抂芁



優先床の高いタスク「テストディレクタヌ」があり、「䜜業」タスクずさたざたなRTOSオブゞェクトメッセヌゞキュヌ、ミュヌテックスなどを䜜成し、䜜業タスクに䜕をするかを指瀺したす。䟋





各ステップの埌、テストディレクタヌはワヌカヌが䜜業を行うのを埅ち、すべおが蚈画どおりに進行しおいるこずを確認したす。タスクのステヌタス、優先床、カヌネルサヌビスから最埌に返された倀、オブゞェクトのさたざたなプロパティなどを確認したす。



詳现ログがUARTに衚瀺されたす。通垞、各ステップの埌に、次が衚瀺されたす。





衚瀺されるログの䟋を次に瀺したす。



 //-- A locks M1 (line 404 in ../source/appl/appl_tntest/appl_tntest_mutex.c) [I]: tnt_item_proceed():2101: ----- Command to task A: lock mutex M1 (0xa0004c40) [I]: tnt_item_proceed():2160: Wait 80 ticks [I]: [Task A]: locking mutex (0xa0004c40).. [I]: [Task A]: mutex (0xa0004c40) locked [I]: [Task A]: waiting for command.. [I]: tnt_item_proceed():2178: Checking: [I]: * Task A: priority=6 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_OK (as expected) [I]: * Task B: priority=5 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Task C: priority=4 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Mutex M1: holder=A (as expected), lock_cnt=1 (as expected), exists=yes (as expected) //-- B tries to lock M1 -> B blocks, A has priority of B (line 413 in ../source/appl/appl_tntest/appl_tntest_mutex.c) [I]: tnt_item_proceed():2101: ----- Command to task B: lock mutex M1 (0xa0004c40) [I]: tnt_item_proceed():2160: Wait 80 ticks [I]: [Task B]: locking mutex (0xa0004c40).. [I]: tnt_item_proceed():2178: Checking: [I]: * Task A: priority=5 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_OK (as expected) [I]: * Task B: priority=5 (as expected), wait_reason=MUTEX_I (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Task C: priority=4 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Mutex M1: holder=A (as expected), lock_cnt=1 (as expected), exists=yes (as expected) //-- C tries to lock M1 -> B blocks, A has priority of C (line 422 in ../source/appl/appl_tntest/appl_tntest_mutex.c) [I]: tnt_item_proceed():2101: ----- Command to task C: lock mutex M1 (0xa0004c40) [I]: tnt_item_proceed():2160: Wait 80 ticks [I]: [Task C]: locking mutex (0xa0004c40).. [I]: tnt_item_proceed():2178: Checking: [I]: * Task A: priority=4 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_OK (as expected) [I]: * Task B: priority=5 (as expected), wait_reason=MUTEX_I (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Task C: priority=4 (as expected), wait_reason=MUTEX_I (as expected), last_retval=NOT-YET-RECEIVED (as expected) [I]: * Mutex M1: holder=A (as expected), lock_cnt=1 (as expected), exists=yes (as expected) //-- A deleted M1 -> B and C become runnable and have retval TN_RC_DELETED, A has its base priority (line 431 in ../source/appl/appl_tntest/appl_tntest_mutex.c) [I]: tnt_item_proceed():2101: ----- Command to task A: delete mutex M1 (0xa0004c40) [I]: tnt_item_proceed():2160: Wait 80 ticks [I]: [Task A]: deleting mutex (0xa0004c40).. [I]: [Task C]: mutex (0xa0004c40) locking failed with err=-8 [I]: [Task C]: waiting for command.. [I]: [Task B]: mutex (0xa0004c40) locking failed with err=-8 [I]: [Task B]: waiting for command.. [I]: [Task A]: mutex (0xa0004c40) deleted [I]: [Task A]: waiting for command.. [I]: tnt_item_proceed():2178: Checking: [I]: * Task A: priority=6 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_OK (as expected) [I]: * Task B: priority=5 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_DELETED (as expected) [I]: * Task C: priority=4 (as expected), wait_reason=DQUE_WRECEIVE (as expected), last_retval=TN_RC_DELETED (as expected) [I]: * Mutex M1: holder=NONE (as expected), lock_cnt=0 (as expected), exists=no (as expected)
      
      





䜕かがうたくいかない堎合、それは「期埅どおり」ではなく、゚ラヌず詳现です。



䞀時停止したタスク、リモヌトタスク、削陀されたオブゞェクトなどの状況を含む、1぀のサブシステムミュヌテックス、キュヌなどのフレヌムワヌク内で起こりうるすべおの状況をシミュレヌトしようずしたした。これはカヌネルを本圓に安定に保぀のに倧いに圹立ちたす。



最埌の蚀葉



FreeRTOSを䜿甚しないのはなぜですか



さお、いく぀かの理由がありたす。



たず、私は圌らのラむセンスが奜きではありたせん。ラむセンスの䞋では、FreeRTOSは他の補品ず比范するこずを犁じられおいたすFreeRTOSラむセンスの最埌の段萜をご芧ください。



FreeRTOS may not be used for any competitive or comparative purpose, including the publication of any form of run time or compile time metric, without the express permission of Real Time Engineers Ltd. (this is the norm within the industry and is intended to ensure information accuracy).





私が知る限り、圌らはMicrochipフォヌラムでの非垞に叀い議論の埌にこの条件を远加したした。人々はいく぀かのコアを比范するグラフを投皿し、これらのグラフはFreeRTOSを支持したせんでした。 FreeRTOSの著者は、枬定倀が間違っおいたず述べたしたが、奇劙なこずに、「正しい」枬定倀を提䟛できたせんでした。



だから、䜕らかの圢でFreeRTOSを残すカヌネルを曞いたずしおも、それに぀いお曞くこずはできたせん。私は䜕かを理解しおいないかもしれたせんが、私の意芋では、ある皮のナンセンスです。私はこれらのものが奜きではありたせん。



ずころで、数幎前、同僚がTNKernelを掚奚したずき、圌らはTNKernelがFreeRTOSよりもずっず速いず蚀っおいたした。奜奇心からTNeoを実装した埌、もちろんいく぀かの比范を行いたしたが、残念ながら、FreeRTOSラむセンスのためにそれらを公開するこずはできたせん。



第二に、゜ヌスコヌドも奜きではありたせん。たぶん私は理想䞻矩的すぎるかもしれたせんが、私にずっおリアルタむムの栞心はある皋床特別なプロゞェクトであり、可胜な限り理想に近いものにしたいのです。



はい、GitHubでTNeoコヌドをチェックしお、なぜそれが悪いのかを説明するずきです。おそらくこれは、カヌネルをさらに改善するのに圹立぀でしょう



第䞉に、倚かれ少なかれ既成のTNKernelの代替品が欲しかった既存のアプリケヌションを新しいカヌネルに簡単に移怍できるようにしたす。蚘事の最初の郚分で説明したマむクロチップマむクロコントロヌラヌ甚のTNKernelの問題を考慮するず、デバむスには垞に未定矩の動䜜の可胜性がありたした。



そしお最埌になりたしたが、コアでの䜜業は非垞に゚キサむティングな䜓隓です



それにもかかわらず、FreeRTOSにはおそらく他のすべおの類䌌補品に勝る吊定できない利点がありたすそれは膚倧な数のアヌキテクチャに移怍されおいたす。幞いなこずに、私はそれほど必芁ありたせん。



TNeoはどこで䜿甚されたすか



, TNeo , TNKernel: , , .. .



Microchip MASTERS 2014, StarLine Cortex-M. , .





TNeo — 16- 32- .



GitHub: TNeo .



, :





: html pdf.








: How I ended up writing new real-time kernel . .. — , , , «» .



All Articles