小さいながらも非常に有益な戦術レッスンを実施する機会がありました
先日、FreeRTOSを既に書いたCortex-M1コアを備えたマイクロコントローラーに移植する過程で、Googleの全能を使用してそれに対する答えを見つけようとするすべての試みにひどく抵抗するという小さな疑問が生じました。 さらに、検索プロセスでは、この質問は私だけの関心ではないことが判明しましたが、したがって、質問者の固有の(または後天的な)愚かさの結果ではない可能性があり、極端な場合、これはそれほどまれではないことを示しています。 答えを検索する通常の方法を適用することが不可能であることに戸惑い、私はよりエキゾチックで少し忘れられたものに頼ることに決めました-自分で答えを考えて見つけること。 残念ながら、それもうまくいかず、他の賢い人たちと相談しようと試みても助けにはなりませんでした(あなたは自分を賞賛することはありません-あなたは唾を吐くように一日中歩き回ります)。 Habréにはそのようなものがたくさんあるはずなので、このプロセスにさらに多くの専門家を巻き込むことで、広範な解決方法を試みます。 したがって、私は勝利のポストの代わりに、平凡なポストを書いています-助け、善良な人々、何ができるか。 したがって、問題の本質に移ります。
タスク切り替えのプロセスでは、プロセスコンテキストを保存し、後で復元する必要があります。 明らかに、このプロセスはハードウェアに依存しており、移植のプロセスでは特別な注意を払う必要があります。 M0のソリューションが、上記のサブセットであるアーキテクチャM1の基礎として採用されたため、すべてが問題なく落ちました。 それでも、少し経験を積むために、このサイトのコードを調べることにしました。 そして、ここでいくつかの予期せぬことが待っていました。つまり、予想されるPUSHコマンドの代わりに次の図があったため、コードは複雑に思えました。
xPortPendSVHandler: ; - mrs r0, psp ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */ ldr r2, [r3] subs r0, r0, #32 /* Make space for the remaining low registers. */ str r0, [r2] /* Save the new top of stack. */ stmia r0!, {r4-r7} /* Store the low registers that are not saved automatically. */ mov r4, r8 /* Store the high registers. */ mov r5, r9 mov r6, r10 mov r7, r11 stmia r0!, {r4-r7} ; , push {r3, r14} cpsid i bl vTaskSwitchContext cpsie i pop {r2, r3} /* lr goes in r3. r2 now holds tcb pointer. */ ; ldr r1, [r2] ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */ adds r0, r0, #16 /* Move to the high registers. */ ldmia r0!, {r4-r7} /* Pop the high registers. */ mov r8, r4 mov r9, r5 mov r10, r6 mov r11, r7 msr psp, r0 /* Remember the new top of stack for the task. */ subs r0, r0, #32 /* Go back for the low registers that are not automatically restored. */ ldmia r0!, {r4-r7} /* Pop low registers. */ bx r3 vPortSVCHandler; ... vPortStartFirstTask ...
ちなみに、この機会を利用して、質問自体を解析する前であっても、このコードの作者を呪いたいと思います。 3つのラベルは異なる形式で記述されていることに注意してください-末尾にコロンがあり、末尾にコロンがなく(言語の説明で許可されます)、コロンはありませんが、欠落しているコメントを開くセミコロンがあります。 後者の場合、ラベルもプリプロセッサディレクティブによって再定義されたため、これが行われた理由を理解しようとするのに時間がかかりました。 「because」という答えはすぐに見つかり、喜びはもたらされませんでした。 次に、コードの1行目と4行目で値が計算され、5行目で2行目と3行目で計算されたアドレスに送信されます。 さて、なぜアドレスを計算して値の計算を中断するのですか? 一方で、スタイルの怠慢は国際的な性格を持ち、私たちの国家的特徴ではなく、一方で楽観主義を加えないことは喜ばしいことです。 私は古典的な「普通の愚かさで説明できることで悪意を探してはいけない」を思い出します。 しかし、それは太陽の明るさと草の緑の余談です。 タスク自体に戻りましょう。
プロセスコンテキストの一部、つまりレジスタr4-r11が7行目から12行目に保存され、インデックス多重転送を使用して(コンテキストの残り、レジスタr0-r3およびr12-r15は例外処理中に保存された)使用されるのはPUSHコマンドではなく、レジスタ間転送での長い転送コマンドである理由(長い転送コマンドはr7レジスタ以上に機能しません)まあ、まず、MアーキテクチャのPUSHコマンドも近くで動作するので、転送は避けられないが、誰もが平等 それは何が起こっているかを理解することがはるかに容易になるために。犬とはしくじったところです。
実際、Mアーキテクチャには、threa_d_モード(カスタムと呼びます)とハンドラー(システムと呼びます)の2つの動作モードがあります。 このような名前は、システムレベルに固有の割り込みを処理するためにハンドラモードがオンになっているため、その精神と非常に一致しています。 特権モードと非特権モードもありますが、M1にはまだありません(それらは区別できません)。 さらに、アーキテクチャMには、MAIN(システムと呼びます)とProcess(ユーザー定義と呼びます)の2つのスタックポインターがあります。 リセット後、MAINポインターが使用され、これが明らかにシステムレベルであるため、この命名も非常に正当化されます。 この場合、両方のポインターは、コードの最初の行で使用される特殊レジスター、MSPおよびPSPのスペースにそれぞれ一意の名前を持っています。 一意の名前に加えて、スタックポインターにアクセスするために、スタックポインターの(突然の)レジスタもあります。これは、特別なレジスタのビットの制御下で上記2つのうちの1つを示します(詳細については、ARMドキュメントを参照)。 すべてが論理的に見えますが、さらに調べます。
ユーザーモードMKでは、このビットを切り替えて、両方のスタックポインターにアクセスできます。 まあ、個人的には、私はこの政権に避けるべき権利を与えませんでしたが、私たちはARMと議論する人を駆り立てました。 しかし、システムモードでは、MKはスタックのシステムポインターにのみアクセスでき、このビットの値を切り替えることはできません。 したがって、スタックアクセスコマンドを介してユーザースタックに直接書き込むことはできません。 もちろんこの場合、サブルーチンで行われるレジスタのインデックス付けによって対応するメモリ領域にアクセスする可能性は残りますが、「なぜこれが行われるのか」という質問がありました。..なぜユーザーモードでポインターを切り替えたり、おそらくシステムスタックの崩壊、および特別に訓練された人々がより慎重に設計する必要があるシステムモードは、この機会を否定されますか? そのような許可が両方のモードに与えられる場合、質問はありません-開発者は保護を行う必要があるとは考えませんでした、これは彼らの権利です。 ただし、システムモードの場合、この機能は意図的に禁止されています。つまり、この禁止の原因となる機器の一部があります。 もちろん、この部分はそれほど複雑ではなく、私自身もいくつかの簡単なオプションを提供できますが、それだけでは表示されません。 だから、そうする理由があります、私だけがそれらを理解していません。 私は頭の中で入れ子になった割り込みに関連するオプションをひねりましたが、何も思いつきませんでした。 残念ながら、ARMのWebサイトで答えが見つかりませんでした。彼らはMKのこの部分がどのように機能するかについて書いています。なぜ言われていないのでしょうか(これは神聖な知識かもしれません。 これがまさに私がこの質問をHabraコミュニティの裁判所に提出する方法であるという秘密の希望とともに、私はあなたの答えの選択肢を待っています。