ソフトウェア開発で「生産性」を使うときに注意すること

このあたりの生産性の問題は、リモートワークと社内出社の双方に意見の食い違いがあり、佐藤氏のいう「誰かが顧客要件をキチンとまとめ~」に掛かってきているのかな、というのがある。

著作を出しているので、こっちも踏まえて議論を残しておこう。

主に議論になりそうなのは、第3章からの具体的な開発プロジェクトの話になる。ここの中でリモートとか設計とかコミュニケーションの話がでてくるので、それでよいだろう。

第三部では「現場で役に立つ周辺知識」としてマネジメントの話がでてくる。

  • 第9章 チームビルディング
  • 第10章 設計
  • 第11章 Gitによるリポジトリ管理

ということで前半の技術的な要素(ReactやDjango)とは違い、それらのWebアプリを具体的にどのように会社で開発しているのか?という紹介に近いものになる。

書籍の中には、アジャイル開発、イテレーション開発のような一般的なソフトウェア開発の用語はでてきるのだが「スクラム」や「PMBOK」のような用語はでてこない。これはあえて外したのか、既存のスクラムとはやり方が異なるために「スクラム」という用語を外したのかどうかはわからない。が、第9章や第10章を読む限り、

  • 開発自体はアジャイル開発でやっている模様
  • 顧客とのやり取りやプロジェクトメンバとの情報共有のため文書を積極的に作っている
  • 実際のタスク管理や進捗管理はチケット方式を使っている模様
  • プロジェクトメンバは5名程度で、単独ではない。あるいは大人数(100名とか)ではない。

ということがわかる。

一般的にアジャイル開発でのスクラムやXPなどがうまく回る人数として5~10名程度というものがある。それ以上になると、スクラムチームを分割して別途プロダクトオーナー(社内であればプロダクト・マネージャとか)が担うということなる。これは社内開発=製品開発なのか、受託開発を含めた請負であるのかにもよる。また、本格的な顧客参加型のスクラムチームであるのかにも依るだろう。

なので、「実装に学ぶフルスタック~」にあるソフトウェア開発が適当であるかどうかは、時と場合による。逆に言えば、株式会社オープントーンでは、この方式でうまくいっているという事例となる。

「誰かが顧客要件をキチンとまとめ~」の部分の生産性を考える

先のツイートから伺い知るに、既にチケット化されているものに対して自宅などを使ったリモート環境ではうまく生産性を上げることはできるが、それ依然のチケット化する工程において(設計や要件定義など?)ではリモートワークでは難しいのではないか?という話だろう。第9,10章を読むと、「他メンバーの課題・問題を感知しにくくなる」や「リモートでは仕様書の文書化を意識的に取り組む必要ある」という書き方があるので、プロジェクト内での情報共有や相互のサポートに難点が感じられたのであろう。また、「タスクマネジメントしづらい」という点を挙げていて、手が空いている人にタスクをうまく割り振りにくいという書き方もされている。つまりは、暇な人と忙しい人の見わけがリモートワークでは見つけづらいという難点をあげている。

ここの点からいえば、チケット駆動であれば本日こなすチケットを自らが取りに行くという方式がとられるし、スクラムであればスクラムミーティングというものが存在する。リーダーあるいはマネージャがタスクを割り振るのではなく、メンバー自らがタスクを取りに行くのだ。このあたり、スクラムの手法に沿っていないゆえの懸念点のような気もするのが、うまくいっているのであれば別に大丈夫なのだ。

論点は、それ以前のチケットを作るときの問題となるだろう。

顧客要件をまとめる、おそらく要件定義をまとめたり設計をまとめたりした段階で、WBSに落としていると思われる。この割り振りを誰がするのか?という話になると思うのだが、一般的なアジャイル開発(特にスクラム)の場合は、

  • 顧客の達成目標がプロダクトオーナー=顧客自身が立てる
  • スクラムマスターが、達成目標を適宜ピックアップして、スプリントする
  • スプリントの中で、チームのメンバーがタスクに落とす

という手順になる。なので、チケット(あるいはWBS)に落とすのはメンバー自身だし、つまりは顧客要件=達成目標をブレークダウンするのは、スクラムマスターとメンバーの共同作業となる。

が、先のようにマネージャが顧客の達成目標(つまりは要件定義など)をブレークダウンしようとすると、そのWBSを誰がやるのか?=マネージャがやる、WBSをどうやって計画通りに割り振るのか=マネージャがやる、というスタイルにならざるを得ず、そのために「誰かが顧客要件をキチンとまとめ~」という誰か=マネージャという主張となってしまう。

どうやら、このツイートを受け取った開発者(マネージャも含む?)の意見が分かれるのは、この部分で、

  • チケットはマネージャが作成し、メンバーに割り振る
  • 達成目標はメンバーがブレークダウンして、メンバー自身がチケットを受け取る

という開発スタイルの違いに他ならない。

ゆえに、そこの「生産性」も違ってしまうのだ。

では、プログラマ自身の生産性はどうなのか?

プログラマはプログラムを書くことだけが仕事ではないが、主な仕事はプログラムを書くことだ。昨今では、いろいろな仕事を平行で動かすことも必要なのだが、まあ、最終的にコードを書かないとソフトウェアというものは動かないのは確かだ。

まあ、例外的にノーコードっていう方法もあるけど、それはまた別の機会に。

で、私のツイートした「正直言うと~」の部分を解説しておこう。要するに、ソフトウェア開発のスタイルで、一番高速にモノができあがるのは「ウォーターフォール開発/計画駆動」であることを忘れてはいけない。これには異論があるだろうが、つまりは

  • 事前に完全な計画ができあがること
  • 計画通りのタスクを分割できること
  • タスクは計画通りに進み、手戻りや遅延がないこと

が、最高速にプロダクトができあがっていく計画駆動の条件となる。つまりは、自動車工場におけるオートメーションをソフトウェア開発に応用すればよい。ソフトウェア開発の大量生産工場のできあがりである!

のだが、そんなことは不可能だ。ということは皆わかっている。今では皆わかっているが、かつてはそうしていたのだ。丁寧な設計書や丁寧なフローチャートや綿密なレビューを繰り返していたのだ。

で、実際のところ、長期では難しいのが1週間程度あるいは2,3日程度の短期間であれば、この計画駆動は実に高速に動く。

これが「正直に言うと~」の前半の部分である。

後半の部分は、実際このようなキリキリに詰めた状態だと、うまくいくのいくのだが、非常に疲れるのである。疲労困憊だし、何度も続けてできるわけではない。最終的な納品直前に馬鹿力を発揮できるぐらいである。

なので、トップスピードを保つことはほとんどやらない。特にアクセルを踏むが、たいていの場合は、ブレーキのほうを踏んだままだ…と前に進まないので、オートマの自動走行位のスピードの気持ちでやることが多い。

それって、生産性が悪いんじゃないですかね?という意見もあるだろうが、長期的に見ればこのほうが生産性がよいのだ。体の故障は少ないし、他の人の気配り(手伝いやアイデアだしなど)の余裕ができる。つまり「ゆとりの法則」ができるわけだ。きちきちの作業をやるだけでは、ピースは全く動かなくなっていまう。ある程度の余裕がないとチームはうまく動かない。

わたしの場合は、フリーで仕事を受けることになるので、多数の仕事が平行で走る。シングルタスクの最速スピードよりもマルチタスクのスレッド操作のほうが遅くなるのは IT 屋では周知の事実だと思う。思うが、実際のところは

  • お客からの回答待ちなどで、こっちに待ち時間が発生する
  • 開発が先にできあがっても、お客の都合でリリースなどで待たされることが多い
  • 協力しているメンバーの進捗スピードなどで、待たされることも多い

ということで、結構な確率で「待ち」が挟まってくる。この場合、会社に居れば、待ちの状態でぼんやりと椅子に座っているのも可能(だとまずいんだけど)なのだが、リモートワークをやっていると、じゃあ気分を変えて別のに手をつけておくか、という形にできるわけだ。

ある程度、開発力があればシングルタスクで仕事をやるよりも、ある程度のマルチタスクのほうが作業効率がよいことがわかるだろう。新人レベルならばひとつの仕事べったりのほうがいいだろうが、ある程度年季が入ったプログラマならば、マルチでやらないとちょっと無駄が多いわけですよ。

アジャイル開発スクラムやチケット駆動、PMBOKの基本的なところは下記の本に書いたので、それを入口にして本格的なスクラムやPMBOKの書籍にあたってみてください。もちろん、いきなり「アジャイル開発スクラム」にあたるのもよいです。

カテゴリー: 開発 | ソフトウェア開発で「生産性」を使うときに注意すること はコメントを受け付けていません

GoFとMVCパターンと、ソフトウェア工学のデザインパターンのその後を私見で

せっかくなので、ソフトウェア工学における私がみつけたパターンをいくつか書いておきます。

原著の「Design Patterns」

いわゆる「GoF本」です。「デザパタ」と略されることもあるのですが、巷のデザインパターンの本と区別が付きづらいので「GoF本」で通しています。

日本語訳でもいいのですが、手元には原著のKindle版があります。Kindleってことは、だいぶん後に買ったので、当時(1994年)に読んだわけではありません。日本でのデザインパターンブームが2000年頃(だったっけ?)なので、原著(あるいは訳本)を読まないままデザインパターンを知った或いは覚えた人も多いと思います。私の入り口もそれですね。

ただ、それだけだとコード寄りになってしまうので、いったん原著にあたっておくといいです。が、中身がC++なのでC++が読めないと辛いです。確か、Javaへの翻訳本もあるはずなのですが、GoF本の発端自体が、C++あるいはMVCパターンによるアプリケーション開発の現場からの抽出なので、当時のC++の状況がわからないと読み込めません。いまのJavaやC#だと、GoFの各種パターンが言語仕様に組み込まれた状態になってしまっているので、そのまま読んでも「何でこんなややこしいことになっているの?」としか見えません。

2016年の自分のブログ記事ですが ふと GoF のデザインパターンを再考しておく で GoF パターンを C# で検証しなおしたものがあります。

同時期の Smalltalk best practice patterns

先の GoF 本と同時期に出たケント・ベック氏の書いた本です。買ったのは随分後なので、当時がどういう状況だったのかはよくわかりません。結構高いので、参考までに持っておくというので良いです。

GoF 本と同じく「パターン」と書かれていますが、GoF とは異なり、アレクサンダー氏の「パタン・ランゲージ」を元にしている訳ではありません。UMLのクラス図もありません。コードがSmalltalkで書かれているので、これに慣れていないと読み取れません。

という難点がありますが、ケント・ベックが書いたということと、オブジェクト指向(クラス構造であれメッセージングであれ)を語る上で Smalltalk は外せないので持っています。持っていますが、私としては Smalltalk でオブジェクト指向を語る気はありません。どちらかというと、クラス構造としてのオブジェクト指向(C++やJavaなど)寄りのところで過ごして来たので、別の流れもあるということですね。

ちなみに、メッセージングのほうのオブジェクト指向は Web APIの呼び出しにてかなり実現されていると考えられます。HTTPプロトコルのステータス無し、Cookie等によるステータスの付与、物理的時間的に遠地にあるファンクションのコールなど、Smalltalk の語るオブジェクト指向が別系統でも実現されつつあるかなぁと。つーか、それで十分ではないか、その先に行きましょうよ、って感じですね。

ちなみにその先のパターンに関しては後述します。

追記しておくと、冒頭にある MVCパターンの論文はここにあります。

https://ics.uci.edu/~dfredmil/ics227-SQ04/papers/KrasnerPope88.pdf

大本のデザインパターンとしての「パタン・ランゲージ」

デザインパターンブームの元ネタの「パタン・ランゲージ」です。ご存じ、アレグザンダー氏の著書で高いので、ひと苦労です。しかも、コードじゃなくて建築の話です。

これも2000年のデザインパターンブームのときには買わなかったのですが、のちに購入しました。やっぱり原点をあたってみないとわからないということで。

この中で書かれていることから読み取ると、

パターンの原点であるアレクサンダー著「パタン・ランゲージ」では、現在ある構造物(都市とか商店街とか)を観察すると、部分的な構造物が組み合わさって現在の良い状態があることわかる。逆に、部品の良い組み合わせ、つまりパターンを作っておけば、良い結果を得られやすいのではないか?という発想です。
なので、当時のMVC パターンで作られた数々の完成物、更に成功している製品を眺めた時に、内部構造としてGoFで書かれたようなパターンを見出す事ができた、故にGoFパターンを使うと成功しやすい、という帰納的解法と思われます。

というのが私の結論です。

なので、建築のパターンやソフトウェア工学のパターンを、構造物(あるいはクラス)作成の効率化や、アプリオリ的に構造物とその組み合わせ(≒パターン)をしておけば良い結果が得られる、という思いこみは間違いです。

歴史的に良い結果(建造物の寿命や商店街の活性化、ソフトウェアの寿命など)を得られてきたものを分解してみると、ところどころに良いパターンが潜んでいるということです。この良いパターンや組み合わせを使うことによって、最終的に良い結果が得られる可能性が高いということですね。ここは帰納法的な思考になります。「帰納法」を出して来たのは、ちょうど統計学再入門を読んでいるところなので、当時は「経験上、よくわからないがうまくいくパターンがあるので、温故知新を用いて使ってみよう」という具合です。

私が、よく先人の知恵とか温故知新とか書くのはそれです。

GoF 本あるいはデザインパターンのその先へ

ここから本題です。いまさら GoF のパターンを学んでも仕方がないです。というか鵜呑みにしても、先に書いた通りプログラム言語仕様の中に組み込まれているものが多くて、原著を読んで C++ で読み込んでもたいして知識が得られるわけではありません。

個人的には、組み込みシステムや別途スマホアプリに応用が効くので、ひと通り知っておいて設計に活かすのがベターなのですが、C++ や Smalltalk を学び直して読み込む必要はないかなぁと思っています。

デザインパターンというと「アナリシスパターン」や「ドメイン駆動設計」あたりに走ってしまいがちになるのですが、実はもっとその先があります。

というかですね。アレクサンダーの著書である「パタン・ランゲージ」の派生を追っていくと、他の分野にも応用できると主張されている節がある。

実際、アレグサンダー自身が当時のソフトウェア業界(1994年)に講演をしているので、そこの別業界としての接点があります。

じゃあ、GoF の各種のコードあるいは周辺のデザインパターンのコードではなくて、もっと広い意味で都市計画とか商店街とか人の導線とかいう意味で、ソフトウェア工学に「パターン」は何を与えていたかというと、

「Garbage Collection」では、ガベージコレクションの世代交代が実装されている。今だとGC では当たり前なんだけど、単純な GC だと頻繁に配置側が発生していまうので、利用している世代を分ける。いわゆる、利用する頻度によって分類するという分け方です。これは、キャッシュとか予約発券システムとかに使われているパターン。 単純にキャッシュするのではなく、頻度に対して分布を作るところにパターンがある。

画像の深層学習で一掃されてしまったが「Toward Category-Lravel Object Recognition」というアイデアがあった。特徴量を導き出して、相互の距離を計測して画像認識に使う技で Google とかもやっていました。結果的に、隠れ層を使った7層程度の深層学習(Deep Learning)のほうが効率が良いことになったので、このあたりの実装はなくなってしまったのだが、特徴量的な考え方は統計学とか主要因分析にも応用されているし(おそらく、この当時の画像認識の研究が統計学の応用だろうし)、その後の報酬関数(フィードバックシステム)のパターンがこの当時からあります。生成AIもこのあたりの延長と言える。

こちらは工業デザインのパターンということで、ノーマン著「誰のためのデザイン」。現在は認知科学として捉えるべきなのか?単なるエモーショナルな流行デザインに過ぎないのか不明なんところではあるけど(いわゆるグローバルデザインの話になる)、それでも一種のパターンに従えば、利用者の落とし穴を防ぐことができる。これこそパターン・ランゲージの応用例と言える。
ソフトウェア工学として、一時期UIデザインにも取り入られた筈なのだが、iOS7からのフラットデザインから以降、それが本質的に誰のためのデザインなのかわからなくなっているところがある。個人的には、高齢者カスタムのかんたんスマホのUIとかはいいと思うんですけどね。設定がごちゃごちゃなのはいただけないが、ガラケーっぽいUIに揃えてあるのは、ガラケー主流だった高齢者にはよいデザインだと思う。誰が?という意味では、「高齢者」が使うわけですから。

「コンピュータの中の人口社会」に書かれているエージェントシステムは、ソフトウェア工学のなかで MVC パターンと同じぐらい重要なデザインパターンと思われる。全体を統括的に扱うのではなく、個々のオブジェクトを独立してプロセスやスレッドで動かすというパターン。まさしく、オブジェクト指向による「オブジェクト」の実現なわけだが、それぞれを独立して動かすことにより、相互の影響を環境値として与えることができるのがミソ。いわゆる、品質工学などでいう外乱を内部特性を区別できるシステムである。

エージェントシステムの発想は遺伝的プログラミングとか並列処理とかいろいろ発想が効くので知っておくとよいパターンです。

まとめとしてのパターン

そんな訳で、ソフトウェアにおけるパターンはなにもコーディングに限らないので、システム全体の構築やユーザーの利用動向、UIの作成、PMBOKによるプロジェクト管理、DevOpsやテスト駆動による価値の制御(スループットや計測)などいろいろな場面でパターンがあります。細かい単位のプラクティスではなくて、大局としてのパターン、つまりは軍事ドクトリンに近いのです。みなさま、応用しては如何でしょうか?

といういうブログパターンで〆てみるテスト。

カテゴリー: 開発 | GoFとMVCパターンと、ソフトウェア工学のデザインパターンのその後を私見で はコメントを受け付けていません

GPT4o に技術マニュアルから実装コードを模索する話(C2340R5でデバイス名を変更編)

生成AIを使ってコード生成をし始めて半年ぐらい経つわけですが、「いろいろなコード生成が自動的にできるよ!」という話よりも、実際に目の前のコードをどうやって生成していくのか?うまく動くようにAIが生成したコードをどうやって人(=自分)が修正していくのか?がプログラマには当面の課題になります。

まあ、どうやってChatGPTあるいはCopilotを使って、コード書きを促進させるか?という実例ですね。WEB アプリケーションのように巷に情報が溢れているならばまだしも、組み込みのような情報が閉鎖的(でもないのだけど)で少ない場合にはどうするのか?という例でもあります。実際のところ、ブロック崩しとか顔認識AIのような既にコードがある場合にはそれをコピペすれば言い訳で、ほどよく WEB サイトに散らばっているならば大丈夫なんですが、完全に未知なコード(少なくとも自分にとっては「未知」の状態)の場合には、ちょっと困るわけです。

C2340R5でBLEのデバイス名を変更したい

未知の人には完全に未知だと思います。まずは、「C2340R5」ってのが何なのかを調べないといけません。BLEを知っていればいいのだけど、デバイス名は何なのか、ぐらいは知っておいて欲しいものですが、まあ、そこからスタートしましょう。

ちなみに、CC2340R5 ってのは Bluetooth が入っている組み込み用のボードですね。ESP32 が入っている M5Stack 系で Bluetooth/BLE を扱えば結構情報が多いのですが、Texas Insturuments の CC23xx 系のボードだと情報が少ないのです。まあ、フォーラムがあるだけマシなのと、マニュアルが揃っているのでなんとかなりそうではあります。

https://www.ti.com/product/CC2340R5

実は SimpleLink というライブラリが TI から提供されていて、これに結構書いてあります。書いてはあるのですが、じゃあ「BLEのデバイス名を変更する」方法は書いていないのが難点ではあります(ほんとうのところ、書いていないのではなくて、デバイス名を変更する方法がない、ということなのですが)。

SWCU193 User guide | TI.com https://www.ti.com/document-viewer/lit/html/swcu193

初手は素直に聞いてみる

AIに質問をするときに「目的」と「手段」をうまく切り分けないと変な回答が出てくることが多いのですが、ひとまず初手としては素直に聞いてみるのがいちばんです。ぴったりとした答えがでてくればそれでよいし、まあ違ったとしたらそれを踏み台にして探索を続ければいいのです。

実はですね、この模索を再現しようと思って、初手に「C2340R5でBLEのデバイス名を変更したい」を再び入力したら、正解が出てくるんですよ。実は、SimpleLink のライブラリには動的にデバイス名を変更する API がなくて、BLE のアドバタイズ部分を再起動するしかありません。しかも配信するデバイス名は上記のように直書きになっているので、自前でバイト単位で書き出さなければ(memcpy使うけど)いけないのです。

まあ、このコードを見て「デバイス名が変更できる」と分かるには、その模索がってこそなのですが。

以下は、以前出した間違ったChatGTPの回答を上げておきます。

ChatGPT にマニュアルの PDF を入れる

ChatGPT にはファイルアップロードして、その中を優先的に探索してくれる機能があるので、それを使います。

初期値は英語になっているようですが、次のプロンプトで「日本語で。」というと日本語が主になります。

いろいろと探索する

前回の場合は「simplelink でデバイス名を変更するには?」で質問しています。この手の質問は、初手は漠然とした「目的」を示したほうがよいです。最終的には実装するための「手段」を探すことになるのですが、いきなり手段を示してしまうと、それ以外の手段を探すのが難しくなりがちです(人間の頭的に)。なので、最初は何もわからない振りをして、ChatGPTに尋ね、少し手間がかかりますが掘り込み方式で進めます。

  • TGAP_DEVICE_NAME という定義はどこにもない

  • GGS_DEVICE_NAME_ATT はあるが、デバイス名は変更されない。

  • GAPRole_SetParameter は存在しない。

  • BLEAppUtil_setAdvData は存在していない。

そんな訳で、なかなか正解に辿り着けません。実は、最初の質問ででてきたscanRespDataの書き換えが正解で、なんとか API 経由で書き換えようとしていたのですが、そんな API は存在しなくて、直接データを書き換えて BLEAppUtil_advStart で BLE のアドバタイズを再起動しないといけないのです。

仕方がないので、初期設定されているアドバタイズデータを書き換える

実は TI のプログラムは Code Composer Studio の *.syscfg というファイルを使っています。これが設定ファイルになっていて、各種設定をコード出力しているのです。Code Composer Studio でデバイス名(ble.deviceName)を書き換えると、うまく書き換わるわけで、そのあたりから更にコードを見ていきます。

最終的には BLEAppUtil_AdvInit_t 構造体があって、ここで初期設定されているのが肝です。コメントを見ると Sysconfig から変換されていることがわかるし、const struct になっているので、実にそれっぽいです。

//! Advertise param, needed for each advertise set, Generate by Sysconfig
const BLEAppUtil_AdvInit_t advSetInitParamsSet_1 =
{
    /* Advertise data and length */
    .advDataLen        = sizeof(advData1),
    .advData           = advData1,

    /* Scan respond data and length */
    .scanRespDataLen   = sizeof(scanResData1),
    .scanRespData      = scanResData1,

    .advParam          = &advParams1
};

まずは、const のままでは書き換えられないのでコードを修正します。

//! Advertise param, needed for each advertise set, Generate by Sysconfig
BLEAppUtil_AdvInit_t advSetInitParamsSet_1 =
{
    /* Advertise data and length */
    .advDataLen        = sizeof(advData1),
    .advData           = advData1,

    /* Scan respond data and length */
    .scanRespDataLen   = sizeof(scanResData1),
    .scanRespData      = scanResData1,

    .advParam          = &advParams1
};

その後に、appMain 関数の先頭で advDataLen と advData を書き換えます。

    // デバイス名の変更
    static uint8_t deviceName[] = "BLE-TEST";

    uint8_t deviceNameLen = strlen((const char*)deviceName);
    static uint8_t scanResData[32];
    uint8_t scanResDataLen = 0;
    scanResData[0] = strlen((const char*)deviceName) + 1;
    scanResData[1] = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
    memcpy(&scanResData[2], deviceName, deviceNameLen);
    scanResData[2+deviceNameLen] = 0x02;
    scanResData[3+deviceNameLen] = GAP_ADTYPE_POWER_LEVEL;
    scanResData[4+deviceNameLen] = 0x00;
    scanResDataLen = 5 + deviceNameLen;

    memcpy( attDeviceName, deviceName, deviceNameLen+1 );
    // memcpy( scanResData1 + 2, deviceName, deviceNameLen );
    extern BLEAppUtil_AdvInit_t advSetInitParamsSet_1;
    advSetInitParamsSet_1.scanRespData = scanResData;
    advSetInitParamsSet_1.scanRespDataLen = scanResDataLen;

動的に変更したい場合は、BLEAppUtil_advStop でいったん止めてから BLEAppUtil_advStart すれば OK です。

ChatGPT の回答では、起動時の BLEAppUtil_advStart しか出て来ないのでなかなか正解に辿り着けないのですが、実は「動的に BLE のデバイス名を変えるにはどうすればいいですか?」と質問すると、かなりイイ線までいきます。

void updateDeviceName(const char* newDeviceName)
{
    // 新しいデバイス名を格納(長さチェックを含む)
    size_t nameLen = strlen(newDeviceName);
    if (nameLen > MAX_DEVICE_NAME_LEN)
    {
        nameLen = MAX_DEVICE_NAME_LEN;
    }
    strncpy(deviceName, newDeviceName, nameLen);

    // スキャンレスポンスデータを更新
    uint8_t scanRespData[] =
    {
        nameLen + 1,  // データ長
        GAP_ADTYPE_LOCAL_NAME_COMPLETE,  // デバイス名のタイプ
        // デバイス名データ
    };

    // デバイス名をコピー(新しい名前で上書き)
    memcpy(&scanRespData[2], deviceName, nameLen);

    // BLEアドバタイズ設定を更新
    BLEAppUtil_AdvInit_t advParams = {
        .advParamLegacy = { ... },  // 他のパラメータを設定
        .scanResponseData = scanRespData,  // 更新されたスキャンレスポンスデータをセット
    };

    // 既存のアドバタイズを停止
    BLEAppUtil_advStop();

    // 新しいアドバタイズを開始
    BLEAppUtil_advStart();
}

BLEAppUtil_AdvInit_t advParams が何処で使われているのか?(実際には BLEAppUtil_advStart の中身なんですが)、というのが不明なのでこのコードではうまくいかないのですが、先に説明した const struct BLEAppUtil_AdvInit_t までたどり着いています。

AI でコード出力をする場合、マニュアルにないコードは探せません。逆に言えば、マニュアルにあるコードならば結構な確率で探し出してくれます。数々の回答のコード(間違ってはいるけれども)もかつてのコードかもしれないし、うまく類推しているコードでしょう。

AI でコード出力と言うと「5分でなんとか」方式が多いので、あっという間に作れる雰囲気がありますが、実際のところはかなり違います。まあ、それでも何も分からないところがから、最初の下調べをしてくれてマニュアルからなんとなく導き出してくれるところは便利ですね。当然のことながら、コンパイルも含めて動作確認は人の手でやらないと無理なので、そのあたりで動作確認は必須なところです。

カテゴリー: 開発 | GPT4o に技術マニュアルから実装コードを模索する話(C2340R5でデバイス名を変更編) はコメントを受け付けていません

M5Stack で iBeacon を飛ばす(PlatformIO環境)

iBeacon をスマホで受信するテストしているときに、iBeacon 自体がないので困ることはありませんか?いや、困ってなくても作ってみるのがお勧めです。

たまに作ろうと思うと、ググってもうまく引っ掛からない(iBeaconを受信するツールはあるのですが、発信するほうはあまりないので)、自分のブログに残しておきます。

platformio.ini

初代の m5stack の platformio,ini は次の通り。m5stick cplus とかを使う場合は適宜かえておきます。

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
monitor_speed = 115200

lib_deps = 
    m5stack/M5Stack@^0.3.9

RTOS を使わない場合

RTOS を使わずに framework の android で setup/loop 関数を使って作ります。iBeacon を発信するツールとして使いたいときはこれで十分です。

#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <M5Stack.h>

static BLEServer *pServer ;
static BLEAdvertising *advertising ;

#define BEACON_UUID "12345678-1111-2222-3333-56789abcdef0"
#define BEACON_MAJOR 0x0001
#define BEACON_MINOR 0x0001
#define BEACON_POWER 0xC5  // Measured power (at 1 meter distance)

void init_beacon() {
  BLEAdvertisementData ibeaconData;
  // 12345678-1111-2222-3333-56789abcdef0
  char UUID[] = { 
    0x12, 0x34, 0x56, 0x78, 
    0x11, 0x11, 
    0x22, 0x22, 
    0x33, 0x33, 
    0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 
    0x00};
    int major = BEACON_MAJOR ;
    int minor = BEACON_MINOR ;
    int txPower = BEACON_POWER;

    ibeaconData.setFlags(0x1A);
    std::string strServiceData = "";
    strServiceData += (char)0x4c;  // apple 
    strServiceData += (char)0x00;  // apple 
    strServiceData += (char)0x02;  // apple 
    strServiceData += (char)0x15;  // apple
    strServiceData += UUID ;
    strServiceData += (char)(major >> 8);    // major
    strServiceData += (char)(major & 0xFF);  // major
    strServiceData += (char)(minor >> 8);    // minor
    strServiceData += (char)(minor & 0xFF);  // minor
    strServiceData += (char)txPower;  // 
    ibeaconData.setManufacturerData(strServiceData);

    // アドバタイズの設定
    advertising = BLEDevice::getAdvertising();
    advertising->setAdvertisementData(ibeaconData);
    advertising->setScanResponse(false);

}

// Arduinoのsetup関数
void setup() {
    // M5Stack の初期化
    M5.begin();
    Serial.begin(115200);

    BLEDevice::init("M5Stack");
    BLEServer *pServer = BLEDevice::createServer();
    init_beacon();

    // taskBeacon 内で start している
    // 実は start 1回だけではうまくいかないことが多く、
    // start/stop を繰り返す必要がある
    // advertising->start();
}

// Arduinoのloop関数
void loop() {
    // メインタスクのループ
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.print("Hello, M5Stack with FreeRTOS! ");
    M5.Lcd.print(BEACON_UUID);
    Serial.println("Hello, M5Stack with FreeRTOS! ");
    // vTaskDelay(pdMS_TO_TICKS(3000));  // 3秒待機
    advertising->start();
    delay(3000);
    advertising->stop();
    delay(3000);
}

iBeacon で送信するサービスUUIDは、Apple の 0x004c となっていて、内部データとして iBeacon として送信したい UUID(12345678-1111-2222-3333-56789abcdef0)を設定しています。本来ならば、BEACON_UUID から 16 bytes のデータを作るところでが、面倒なので UUID 配列に詰め込んでいます。ちなみに 0x1502 は特性ID(キャラクタリスティック)のようなもので固定値です。

このコードでは、init_beacon 関数内でビーコンの発信設定をしているので、advertising->start(); を 1回だけ呼び出せばビーコンが送信されるような気もするのですが、うまくいきません。

これが BLEAdvertising クラスの仕様なのか、M5Stack の制限なのか(あるいは手元の M5Stack が不調なのか)わかりませんが、loop 関数にあるように、ある程度の間隔で start/stop を繰り返してやらないと iBeacon のデータが飛びません。

iBeacon が無事飛んでいると、次のように別のツールで動作確認ができます。

これは dotnet で作ったツールで、こんなコードで動かしています。

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Storage.Streams;

// BLEのスキャナ
BluetoothLEAdvertisementWatcher watcher;
// MACアドレスの保持(ランダムなので意味はない)
List<ulong> maclist = new List<ulong>();

Main(args);

void Main(string[] args)
{
    Console.WriteLine("Folkbears iBeaconCheck");

    watcher = new BluetoothLEAdvertisementWatcher()
    {
        ScanningMode = BluetoothLEScanningMode.Passive
    };
    // スキャンしたときのコールバックを設定
    watcher.Received += Watcher_Received;
    // スキャン開始
    watcher.Start();
    // キーが押されるまで待つ
    Console.WriteLine("Press any key to continue");
    Console.ReadLine();
}

void Watcher_Received(
    BluetoothLEAdvertisementWatcher sender,
    BluetoothLEAdvertisementReceivedEventArgs args)
{

    var uuids = args.Advertisement.ServiceUuids;
    var mac = string.Join(":",
                BitConverter.GetBytes(args.BluetoothAddress).Reverse()
                .Select(b => b.ToString("X2"))).Substring(6);
    var name = args.Advertisement.LocalName;
    var rssi = args.RawSignalStrengthInDBm;
    var time = args.Timestamp.ToString("yyyy/MM/dd HH:mm:ss.fff");
    

    if ( args.Advertisement.ManufacturerData.Count > 0)
    {
        var data = args.Advertisement.ManufacturerData[0];
        if ( data.CompanyId == 0x004c && data.Data.Length >= 23)
        {
            byte[] ibeacon = new byte[data.Data.Length];
            DataReader.FromBuffer(data.Data).ReadBytes(ibeacon);
            if (ibeacon[0] == 0x02 && ibeacon[1] == 0x15)
            {
                byte[] uuid = ibeacon[2..18];
                byte[] major = ibeacon[18..20];
                byte[] minor = ibeacon[20..22];
                byte txpower = ibeacon[22];

                int majorvalue = major[0] * 256 + major[1];
                int minorvalue = minor[0] * 256 + minor[1];
                Console.WriteLine($"{time} [{tohex(uuid)}] {rssi} dBm {mac} "
                    + string.Format("{0:x04}", majorvalue) + " "
                    + string.Format("{0:x04}", minorvalue) + " "
                    );
            }
        }
    }
    string tohex( byte[] data )
    {
        return BitConverter.ToString(data).Replace("-", "").ToLower();
    }
}

*.csproj ファイルはこんな感じです。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

RTOS を使う場合

先のコードでは iBeacon の発信と停止を loop 内で制御しましたが、RTOS を使えばタスク内で記述ができます。

#include <M5Stack.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <M5Stack.h>

static BLEServer *pServer ;
static BLEAdvertising *advertising ;

// タスクの定義
void task1(void *pvParameters) {
    while (1) {
        Serial.println("Hello from Task 1");
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒待機
    }
}

void task2(void *pvParameters) {
    while (1) {
        Serial.println("Hello from Task 2");
        vTaskDelay(pdMS_TO_TICKS(2000));  // 2秒待機
    }
}

void taskBeacon(void *pvParameters) {
    while (1) {
        Serial.println("task beacon start");
        advertising->start();
        vTaskDelay(pdMS_TO_TICKS(3000));  // 3秒待機
        Serial.println("task beacon stop");
        advertising->stop();
        vTaskDelay(pdMS_TO_TICKS(10000));  // 10秒待機
    }
}

#define BEACON_UUID "12345678-1111-2222-3333-56789abcdef0"
#define BEACON_MAJOR 0x0001
#define BEACON_MINOR 0x0001
#define BEACON_POWER 0xC5  // Measured power (at 1 meter distance)

void init_beacon() {
  BLEAdvertisementData ibeaconData;
  // 12345678-1111-2222-3333-56789abcdef0
  char UUID[] = { 
    0x12, 0x34, 0x56, 0x78, 
    0x11, 0x11, 
    0x22, 0x22, 
    0x33, 0x33, 
    0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 
    0x00};
    int major = BEACON_MAJOR ;
    int minor = BEACON_MINOR ;
    int txPower = BEACON_POWER;

    ibeaconData.setFlags(0x1A);
    std::string strServiceData = "";
    strServiceData += (char)0x4c;  // apple 
    strServiceData += (char)0x00;  // apple 
    strServiceData += (char)0x02;  // apple 
    strServiceData += (char)0x15;  // apple
    strServiceData += UUID ;
    strServiceData += (char)(major >> 8);    // major
    strServiceData += (char)(major & 0xFF);  // major
    strServiceData += (char)(minor >> 8);    // minor
    strServiceData += (char)(minor & 0xFF);  // minor
    strServiceData += (char)txPower;  // 
    ibeaconData.setManufacturerData(strServiceData);

    // アドバタイズの設定
    advertising = BLEDevice::getAdvertising();
    advertising->setAdvertisementData(ibeaconData);
    advertising->setScanResponse(false);
}

// Arduinoのsetup関数
void setup() {
    // M5Stack の初期化
    M5.begin();
    Serial.begin(115200);

    BLEDevice::init("M5Stack");
    BLEServer *pServer = BLEDevice::createServer();
    init_beacon();

    // taskBeacon 内で start している
    // 実は start 1回だけではうまくいかないことが多く、
    // start/stop を繰り返す必要がある
    // advertising->start();

    // タスクの作成
    xTaskCreate(&task1, "task1", 2048, NULL, 5, NULL);
    xTaskCreate(&task2, "task2", 2048, NULL, 5, NULL);
    xTaskCreate(&taskBeacon, "taskBeacon", 2048, NULL, 5, NULL);
}

// Arduinoのloop関数
void loop() {
    // メインタスクのループ
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.print("Hello, M5Stack with FreeRTOS! ");
    M5.Lcd.print(BEACON_UUID);
    Serial.println("Hello, M5Stack with FreeRTOS! ");
    vTaskDelay(pdMS_TO_TICKS(3000));  // 3秒待機
}

ここでは、task1, task2, taskBeacon という3つのタスクが同時に動いています。優先度が同じなので、並列で動いているのですが、優先度を変えれば iBeacon 発信のタスクだけ優先して動かすことも可能でしょう(実験までしていませんが)。

先の iBeacon の発信/停止の切り替えは loop 関数から taskBeacon 内に移動しています。

m5stackの画面はこんな感じです。

カテゴリー: 開発 | M5Stack で iBeacon を飛ばす(PlatformIO環境) はコメントを受け付けていません

M5Stack で RTOS を試すときに main.cpp で動作させること

M5Stackシリーズを使うと、内部で ESP32のチップが使わているのでWi-FiやBluetooth周りのテストをするのに非常に便利です。手元の BLE 関係の通信も、いったん M5Stack で試し実装をしてみてスマホで動作確認をしてから、専用ボードに書き込むと動作がわかりやすいですよね。

手元でTIのボードを使ってBLE通信をしているのですが、そこでRTOSを使うことになっています。なぜRTOSを使うことになったのかはさておき、最近では AWS から FreeRTOSが配布されているので、これの準じたサンプルコードがあちこちで配布されています。当然ながらTIでも配布されているので、これを参考に作ります。

で、各ボードでのRTOS対応のコードは結構な職人プログラマが整備しているので、うまいことRTOSの説明や動きを解説することができあません。せっかくRTOSという統一されたOSのAPIを使っているわけですが、それぞれのボードに対応した専用関数で置き換えられているので(これはこれで仕事上は便利)コーディングがしやすいのですが、まあ、内部でどう動いているのか不可解なところがあるので、できることならば生のRTOSのAPIを叩いて試しておきたい。

ちなみに、私の場合 μiTronで仕事をしたことがあるのでリアルタイムOSまわりの動きはわかるのですが、RTOSのほうは初見ですね。

VSCode で PlatformIO を使う。

以前はESP32の開発環境を整えるのが一苦労だったのですが、最近は VSCode に「PlatformIO」という拡張をいれるだけで十分です。コンパイラ諸々をインストールしてくれます。

VSCode-PlatformIO IDEを使って、ESP32の開発環境を構築およびLチカ https://zenn.dev/kotaproj/articles/esp32_vscode_pio

デバッガ機能等便利なものが多いのですが、私の場合は Arduino IDE の上位互換機能があれば十分なので、build と upload だけあれば十分なところです。

Arduino IDE の場合はコード補完機能(インテリセンス機能)が効かないのでコーディングに苦労することが多いのですが、VSCode だと補完が効くし、GitHub Copilot を入れた現状だとかなりの部分をコード補完してくるので便利ですね。

RTOS対応のプロジェクトを作る

RTOSを使ってプログラミングをするのに、何かライブラリを入れないといけないのでは?と思いますが、実は ESP32の「espidf」というフレームワークには既に、RTOSのAPIが含まれています。他のボードだと結構苦労しそうな感じなのですが、少なくともM5StackシリーズでRTOSをやるときは特に追加のライブラリは必要ありません。

手元にあるのが、初期のm5stackなのでBoardには「M5Stack Core ESP32」を選択します。

ここでは、Framework に「Espidf」を選択していますが、実は「Arduino」でも構いません。どういうライブラリの構造かわかりませんが、ここで指定する「Arduino」のほうにもRTOSのライブラリが入っています。

最初、m5stackでRTOSプログラミングをするときのサンプルコードが、「Espidf」が多かったので、これにしたのが以下の躓きの始まりです。結論から言えば、(おそらくライブラリの容量の関係)「Arduino」で問題がないと思われます。

platformio.ini を比較する

プロジェクトを作成したら platformio.ini を確認しておきます。ここでフレームワークの指定やライブラリの追加ができます。

Framework が 「Espidf」の場合

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = espidf

Framework が Arduino の場合

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino

platformio.ini の中身は framework のところ以外は変わりません。

ただし、このままでは M5.Lcd のような、モニタを使ったコードが書けないので、lib_deps を追加しておきます。これで M5.* 系のライブラリが自動でインストールされます。

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino

lib_deps = 
    m5stack/M5Stack@^0.3.9

ChatGPT に RTOS を使ったサンプルコードを書いて貰う。

実は GPT4o を使うと、ほどよく M5Satck を使って RTOS のサンプルコードを書いてくれます。

以下は、提案してもらったコードなのですが、実はビルドができません。

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <esp_log.h>
#include <M5Stack.h>

// ログタグの定義
static const char *TAG = "main";

// タスクの定義
void task1(void *pvParameters) {
    while (1) {
        ESP_LOGI(TAG, "Hello from Task 1");
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒待機
    }
}

void task2(void *pvParameters) {
    while (1) {
        ESP_LOGI(TAG, "Hello from Task 2");
        vTaskDelay(pdMS_TO_TICKS(2000));  // 2秒待機
    }
}

// メイン関数
void app_main(void) {
    // M5Stack の初期化
    M5.begin();

    // タスクの作成
    xTaskCreate(&task1, "task1", 2048, NULL, 5, NULL);
    xTaskCreate(&task2, "task2", 2048, NULL, 5, NULL);

    // メインタスクのループ
    while (1) {
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.setCursor(10, 10);
        M5.Lcd.setTextColor(WHITE);
        M5.Lcd.setTextSize(2);
        M5.Lcd.print("Hello, M5Stack with FreeRTOS!");
        vTaskDelay(pdMS_TO_TICKS(3000));  // 3秒待機
    }
}

error “This library only supports boards with ESP32 processor.”

なエラーが M5Stack.h 内で出ています。

よくわからないので、framework が「Arduino」のほうに、main.c をコピーすると以下のようなエラーがでます。

“class” がないというエラーなのですが、実は main.c をそのままコピーしたので C言語で扱われているからです。なので、main.cpp にして C++ でコンパイルされるようにします。

実は SD.h がないとか諸々エラーになることもあるのですが、結論としては main.c のままなのが原因で、main.cpp にすれば ok です。

コンパイルは順調に進むのですが、最後のリンクで失敗します。

どうやら、

  • framework が espidf のときはC言語で、app_main がエントリー関数(俗にいうmain関数)
  • framework が ArduinoのときはC++で、setup と loop が呼び出される

という違いがあるようですね。TI のサンプルコードが C言語のほうの *.c に限られていたので、Arduinoのほうが C++ のほうの *.cpp に統一されていたのを忘れていました、というオチです。

で、最終的には以下のコードで動くようになります。

#include <M5Stack.h>

// タスクの定義
void task1(void *pvParameters) {
    while (1) {
        Serial.println("Hello from Task 1");
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒待機
    }
}

void task2(void *pvParameters) {
    while (1) {
        Serial.println("Hello from Task 2");
        vTaskDelay(pdMS_TO_TICKS(2000));  // 2秒待機
    }
}

// Arduinoのsetup関数
void setup() {
    // M5Stack の初期化
    M5.begin();
    Serial.begin(115200);

    // タスクの作成
    xTaskCreate(&task1, "task1", 2048, NULL, 5, NULL);
    xTaskCreate(&task2, "task2", 2048, NULL, 5, NULL);
}

// Arduinoのloop関数
void loop() {
    // メインタスクのループ
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.print("Hello, M5Stack with FreeRTOS!");
    vTaskDelay(pdMS_TO_TICKS(3000));  // 3秒待機
}


先頭部分の「freertos/FreeRTOS.h」あたりは、M5Stack.h から自動的にインクルードされているらしく必要ありません。RTOS 特有の関数としては、タスク生成の xTaskCreate の部分で、これが呼び出せていれば RTOS でビルドできています。

This page describes the RTOS xTaskCreate() FreeRTOS API function which is part of the RTOS task control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers. https://www.freertos.org/a00125.html

アップロードして動作させる

アップロードでして動作させると m5stack のモニタに「Hello, M5Stack with FreeRTOS!」の表示がでて、シリアルポートには、以下のような2つのタスクが交互っぽい形で動いていることがわかります。

ここまでで半日費やしてしまったので力尽きて、何をやろうとしていたのか忘れてしまいました。まあ、いいんですが。

後日、BLE通信のコードをちょっと入れ込んで試しおくつもりです。

余談

ちなみに、これらのサンプルコードは ChatGPT の GPT4o を使って尋ねているのですが、最終的に main.c と main.cpp に気付いた部分はこれになります。

いやいやいや、そもそも ESP-IDF フレームワークと Arduinoフレームワークを混在させてきたのは君なんですが!をぐっとこらえてw

まあ、それでも GPT4o を使って、何度も「このコードだと動かないのですが、どうしたらいいですか?」を繰り返している訳で。なかなか一発で解決にはなりませんね。

それでも最初のスタートダッシュは生成AIを使うと便利です。

カテゴリー: 開発 | M5Stack で RTOS を試すときに main.cpp で動作させること はコメントを受け付けていません

CrawdStrike 関係で Windows のカーネルドライバーの動きを少し解説

NHK の解説

【最新】システム障害 マイクロソフトは「サービスは回復」と発表 影響は一部で継続も収束にむかう | NHK | 航空 https://www3.nhk.or.jp/news/html/20240720/k10014517151000.html

Microsoft からの復旧手順

KB5042421: CrowdStrike issue impacting Windows endpoints causing an 0x50 or 0x7E error message on a blue screen – Microsoft Support https://support.microsoft.com/en-us/topic/kb5042421-crowdstrike-issue-impacting-windows-endpoints-causing-an-0x50-or-0x7e-error-message-on-a-blue-screen-b1c700e0-7317-4e95-aeee-5d67dd35b92f

Recovery options for Azure Virtual Machines (VM) affected by CrowdStrike Falcon agent – Microsoft Community Hub https://techcommunity.microsoft.com/t5/azure-compute-blog/recovery-options-for-azure-virtual-machines-vm-affected-by/ba-p/4196798

どのようにNULLポインタアクセスなのか?

画面キャプチャだけ抜き出し。

このツイートでは、00000000 0000009c ゆえに NULL ポインタアクセスという指摘になっているが、(確か)実際は Windows 起動時はリアルモードで実行されるので DS レジスタが有効になっていて DS:009c がアクセスされる。つまり 002b:009c から DWORD の 4バイトの読み込みでアクセスエラーとなっているので、NULL ポインタアクセスとは限らない(まあ、この DS が間違っている可能性もあるので)。ここは私も見逃したところのなので、特に言及はしない(私が間違っている可能性あるし)以下はひとつの考察である。

アセンブリコードを見ると、

mov r9d,dword [r8] ds:002b:00000000`0000009c=????????

r8 レジスタが示すメモリつまり [002b:009c] なんだけど、きちんとデータセグメントにあるっぽいのに、read access error を起こしているのはかなり変な状況になっている感じがする。

が!!!、再考すると、

  • カーネルドライバは「プロテクトモード」で動いている
  • セグメントレジスタの値(特にcs=0010, ss=0018, ds=002bなど)は、プロテクトモードのGDT内のエントリを指している可能性が高い。

とのことなので、これは「プロテクトモード」っぽい。

となると、先のツイートにあるように、なんらかの変数A(配列の先頭と思われる)に対して「オフセットで、0x9cの場所を参照しようとしている。つまり

DWORD x = A[ 0x9c ] ;

みたいなコードで、DWORD(4バイト)読み込みをしようとしているのだが、最初の変数 A が NULL ポインタなので(おそらく初期化漏れ)、アクセスエラーを発生している。大抵のメモリでは、先頭 0x1000 位が NULL ポインタチェック用に用意されていることが多く、それに引っ掛かったのだろう。

DWORD *A = 0x00 ;
// ここで変数 A の初期化漏れ
DWORD x = A[ 0x9c ] ;

この部分、変数 A の初期化漏れを Rust で回避できるかどうかなんだけど、Rust の場合は初期化していない変数を利用しようとするとコンパイルエラーになるので、これは回避できるかも。

「インサイド Windows」下巻の第12章より

ここの「ドライバー実行環境(DXE)」のときに、既にプロテクトモードになっているそうだ。なので、リアルモードのセグメント化でつかう DS レジスタは、既に GDT を示しているのだけなので、00000000 0000009c がフラットなアドレスと指定される。メモリを示すときはこんな小さなアドレスを示すことはないので、配列の先頭を示すアドレスの初期化漏れ(しかもわざわざ 0x00 が入っていた)と考えるのが正しい。

参考先

Windows カーネルドライバーって Visual Studio のプロジェクトテンプレートにあるらしく、結構便利になっている。

Write a Hello World Windows Driver (KMDF) – Windows drivers | Microsoft Learn https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/writing-a-very-small-kmdf–driver

追記 2024/07/25

Technical Details: Falcon Update for Windows Hosts | CrowdStrike https://www.crowdstrike.com/blog/falcon-update-for-windows-hosts-technical-details/

名前付きパイプの読み取りで失敗して、C-00000291-*.sys の読み込みあたりでエラーが発声しているので、Although Channel Files end with the SYS extension, they are not kernel drivers. カーネルドライバーではないよ、という理由らしい。C-00000291-*.sys ファイルを消すと正常動作するので、読み取り先のデータ領域っぽい(多分、ファイルをオープンして、メモリマップドしていると思う)ので間違いではないが、NULLポインタエラーの否定にはなっていないし、おそらく

  • C-00000291-*.sys ファイルが新規に追加される
  • C-00000291-*.sys ファイルをオープンする。
  • 名前付きパイプでアクセスする(大抵はメモリマップドである)
  • このときに NULL チェックを怠ったか、C-00000291-*.sys の先が NULL 書き込みされていたかでアクセスエラー発生

という手順だろう。

ほぼ100%再現性があるっぽいので(何万台というレベルで発生いるため)、これ最初に動作確認したのか?が危ぶまれるのだが CrawdStrike社としてはどうなのだろうか?

単体テストというか、リリース前の運用テストをスキップしてしまった感じがする。

カテゴリー: 開発 | CrawdStrike 関係で Windows のカーネルドライバーの動きを少し解説 はコメントを受け付けていません

詳細設計書を「効果的に」活用するためのパターン

発端はこのツイート

スナップショットはこちら(ロードするたびに割合がかわってしまうので)

前提条件

いろいろ議論が発散してしまうので、前提条件を確認しておきます。

「詳細設計」というのは、PMBOKの「Detail Design」にあたる、とします。

・要件定義
・概要設計、外部設計、基本設計、画面設計
・詳細設計、内部設計
・実装
・各種のテスト

PMBOKで云うと概要設計と基本設計が同じになります。最近は「基本設計」の方が言われることが多かった(多分、新聞などで使われることになったからだと思う)のですが、いわゆる顧客からの要件定義を受けて、システムを構築するための設計のことです。

実は、大手の会社でもここの分類が分かれるところで「自社だと違う」というパターンが多いでしょう。実際に画面設計(ユーザーインターフェース)やネットワーク設計などの仕様書が必要になることが多く、PMBOKが規定する分類にあてはまるとは限りません。

この分類は、漠然としたものではなく「誰が誰のために作るのか?」という視点で分けるとわかりやすいです。

「要件定義」は、顧客の要望を文書化したものです。顧客自身が書いてもいいし、請け負った会社が書いても構いません。

概要設計(基本設計や画面設計やネットワーク設計など)は、要望をどのように実現するのか?を記述していきます。これは、要件定義と対になるもので、いわゆるプロジェクト予算を作るときに利用します。ただし、実際のITプロジェクトの契約では、要件定義段階で契約することが多く、概要設計を作らずに(あるいは概要の概要みたいなものは作る)契約してしまうことも多いのですが、これは別の問題なのでここでは議論しません。

詳細設計は、概要に従ったものを具体的にクラスやフレームワーク、関数や、シーケンスなどを考慮して記述していきます。近年「詳細設計」を飛ばしてしまうことが多いのは、概要設計から実装(WEBの各種フレームワークやプロトタイプ、ノンコーディングできる実装環境など)が繋ぎやすいところにあります。いわゆる、スクラッチビルドが試せる環境が整っていたことがあります。逆に言えば、プロトタイプビルディングができない環境である場合は、詳細設計が必須です。

実装は、実際にコーディングをして動かす部分です。これに対しても以前はモジュール設計、クラス設計などを記述して分離させていた時期もあるのですが、最近では詳細設計に入れてしまう場合が多いです。これは会社のプロジェクトによって異なるところです。

UMLは、かつては概要設計と詳細設計の両方で使われる可能性があったのですが、現在はあまりUMLで書いてある仕様書を見かけません。これは有償のケースツールの衰退もあるのと、UML自体が細分化されてしまって、忌諱されてしまったためでしょう。

UMLを記述する場合は、最低限

・クラス図
・シーケンス図
・オブジェクト図(これはなくてもよい)
・状態遷移図
・ユースケース記述

だけ覚えればOKでしょう。これは時間と具象/抽象の4象限でわけたものです。

ただし、これ自体はここでは議論しません。

詳細設計は誰のためにつくるのか?

さて、詳細設計は誰のために作るのか?をパターン分けしておきましょう。先のアンケートを分類すると、

  1. 詳細設計書と実装が別の人
  2. 詳細設計書と実装が同じ人
  3. 詳細設計を書かず、実装のみ

のパターンに分けられています。それぞれ、意味があるので何がよいという訳ではありません。ITプロジェクトの状態に沿った形で、詳細設計を書くかあるいは省略するかすればよいのです。

詳細設計と実装が別の会社の場合

契約上、詳細設計と実装が異なる会社の場合があります。かつての中国オフショアなどがそれで、日本で詳細設計書を書いて中国のプログラマに発注していた時期があります。オフショアに限らず、子会社は外注に出すパターンはこの組み合わせが多いです。ただし、WEBサイト開発のような場合は、概要設計や画面設計から実装へ進む場合も多いので、これは情報システムや銀行系の場合に多いですね。

それぞれの会社が別なので「瑕疵」という契約が発生します。どのような場合に「瑕疵」となるのか、不具合をどのように直すのか?が問われるところなので、契約のために「詳細設計書」が必要になります。

別の会社であっても、派遣社員や準派遣の場合は、発注会社が責任を持つことが多いので、詳細設計書を省略しても契約上問題ありません。

詳細設計と実装が同じ会社(人)の場合

同じ会社であり同じ人であるならば詳細設計を作らずに、画面設計からいきなり実装に移ることも多いでしょう。保守運用的に詳細設計を残す或いは残さないの判断もあるのですが、実装する人の経験が浅かったり、ロジックが複雑怪奇であったりする場合には、詳細設計書を書くのがベターです。

詳細設計といっても、コードと逐一同じものを書くのではなく、UMLなどを使いながら簡略化していきます。

私の新人教育では、

・箇条書きで詳細設計を行う
・シーケンス図を記述する

ことにしています。シーケンス図はプログラムコードが入り組んでいた時、タイミングが微妙なときを考察するのに便利です。基本的に詳細設計は使い捨てです。そのままコードのドキュメントとして残すか(文芸的プログラミングのように)、単なるメモ書きとして記録しておくだけでよいでしょう。

コードレベルの詳細設計は書かない

先のアンケートの結果を見ればわかる通り、私は「コードレベルの詳細設計」は書きません。しかし、場合によってはシーケンス図のような詳細設計を書きます。

このアンケートで答えた人がどの位の「詳細設計書」を考えているかわかりませんが、分布としては形式的な詳細設計書はなくなっていく傾向にあります。

ここで注意しておきたいのは、

・アジャイル開発だからといって、詳細設計が存在しない訳ではない
・ウォーターフォール開発だからといって、詳細設計が必須な理由はない

ということです。ITプロジェクトのスタイル(アジャイル開発、計画駆動、イテレーション開発などなど)に問われるものではありません。アジャイル開発においても「設計書」は情報共有のために必要となるので、そこに囚われる必要はないでしょう。

設計書からコードを出力するツールはあるのか?

あると言えばあるんですよ。

記事を見ると解る通り、みずほ銀行の件は失敗しています。失敗していますが、詳細設計書からコードを出力するツールはあるんです。あるんですが、失敗しています、ということを覚えておくことが重要です。

個人的には MDA の復活になると思うのですが、それがいつ頃になるかわかりません。Scratch とか Node-RED のツールとかがそれに近いです。各種のノンコードツールもそれに近いものがあります。ノンコードツールの延長線上にあるとは思えないのですが、一種ではあると思われます。

参考先

図解即戦力 PMBOK第6版の知識と手法がこれ1冊でしっかりわかる教科書

図解即戦力 アジャイル開発の基礎知識と導入方法がこれ1冊でしっかりわかる教科書 Kindle版

図解入門 よくわかる最新 システム開発者のための仕様書の基本と仕組み

カテゴリー: 開発 | 詳細設計書を「効果的に」活用するためのパターン はコメントを受け付けていません

MS-MVP の再受賞(14回目)と学習効果の話

今年も無事、Microsoft MVP を受賞することができました。ぱちぱちぱち。

ひとまず、Microsoft系の書籍を書いている限りは、申請を出すつもりなので、引き続きよろしくお願い致します。

「Azure OpenAI Service 入門」のほうも合わせて。

さて学習効果の話

ツイートを失念してしまったのですが、学習効果が複利的に逓増するか?というのが再び流行りました。もともとの図自体はかなり前に流行ったものなので、目新しいことではないのですが、賛同だけではなく、疑問符な方たちが結構いたようなので、学習効果についてちょっと記述しておきます。

誰が云ったか忘れてしまったのですが、「1.01の法則と0.99の法則」というものです。

1.01を365日続けると37倍になり、0.99で努力が減ると0.03になるという話で、最初でてきた当時も「1年間続けたとしても、さすがに37倍にはならんやろ」という結論がでています。まあ、1.01の努力ってのもたとえ話なので、実際に37倍になるかどうかは不明です。

さて、これを数式で表すと、次のように複利的にΠで計算するか、加算ということでΣで表すかという違いになります。

果たして、微々たる努力(1.00のところを1%だけ上げるので、勤務時間8時間であれば、8x60x0.01 = 4.8分/日ということになる)で、じゃあ、1年後に37倍になるか?という話になり、まあそうなんらんやろという結論で、Bの加算程度じゃないだろうか?というツイートが今回散見しました。努力が加算されるという意味ですね。

なにかの作業的なものであれば、たかだか1日のうち5分間というのは大した効果ではありません。単純に自給換算にで言えば、8時間勤務が8時間5分勤務になったというだけなのです。

が、Aのような複利効果を考えるとき、実はベースとなる実力そのものが増大するため、その効果はその時々において確かに複利的になる(常に元金が増えるような状態なので)ものです。たとえ話のように、356日連続で向上するとなるとなにやらうさん臭くなってしまいますが、これが「1か月単位で、ある程度実力が向上する」となると少し話が違ってきます。

たとえば、プログラムのコードを書く作業を生産性とみなし(詳細設計や単体試験まどを含めた、バグのないコードをどのくらいのペースで書けるか?ということを考えてみましょう)たときに、1か月のうちで、1.00の時間ところを0.99の時間で書けるようになったとしましょう。たかだか1か月のうちで1%しかスピードアップはしませんが、これが12か月になると、0.99^12 = 0.886 となり、おおよそ 90%程度になります。つまり、1割位の短縮ができるわけです。

プログラムを書くスピードはそのプロログラム言語の習得率や設計のうまさ、テスト環境の整え方など各人の経験で向上できるところの多い分野です。この「経験」は、Σのように加算ではなく、Πのように累積されることは、いわゆるプログラマ力の個人差が大きいところを見ると明らかでしょう。スタート時点での経験の差(あるいは習得率の差)は、その差のまま続くのではなく、1年間のうちに「経験」として向上するものです。さらに、1か月ごとの累積で考えるならば、1か月単位のスタートで「経験」に対して累積していくと言えるでしょう。スタートとなる土台は、次のスタートの時には土台が上がっていると考えられます。

このあたりは町工場などの職人も同じですね。学習前の土台を常々上げることによって、次のステップが楽になります(段差が小さくなる状態にしておきます)。

ここでは仮に1か月につき1%の向上にしましたが、5%と仮定したときは、当初1.0だった見積もりが0.54程度に住むことになります(0.95^12 = 0.54)。つまり1年たてば、最初の見積もり期間よりも半分の工数でコードができあがる、という予定になります。

実際、プログラミング言語の習得だけではこの向上は見込めませんが、

  • フレームワークの使い方の習得
  • サンプルコードやテストコードを作ることで、落とし穴を回避する
  • あるいは、あらかじめ落とし穴に落ちておく(踏み抜いておく)
  • テストコードや不具合対処の勘がさえてくる(経験上)
  • プログラミング言語自体の習熟度が上がっている

などの要因もあり、単純な加算ではなく複利的に実力が向上することが見込まれます。

実は、似たところはプロジェクトのスケジューリングにも言えるのです。プロジェクトの当初では未知の部分も多く、プロジェクトで利用するライブラリや環境にも不慣れな状態からスタートします。しかし、プロジェクトが終盤になれば、プロジェクトのメンバーはライブラリや環境にも習熟して、数々の不具合を素早く対処できるようになってきています。

終盤で下手に炎上しているプロジェクトではなければ(実は炎上しているプロジェクトであったとしても、プロジェクトメンバー自身は成長しているのですが)、プロジェクトの最初の力量よりもプロジェクト終了時の力量のほうが上であると考えられます。

つまり、この習熟度の高い状態を疑似的に作り出せば、プロジェクトはより安全に安定期(予測可能という意味で)に入ることができます。

  • プロジェクトで使うフレームワークを事前にサンプル等を作って試しておく
  • 運用直前でトラブルになりそうなリスク要件をあらかじめ確認しておく
  • もともと、習熟度の高いメンバーを入れる(可能であれば)
  • プロジェクト途中で「学習」を重視する

という様々な方法が考えられます。

そんな訳で、私的には学習による複利的効果(基礎力をアップするという意味で)を期待して、基礎力のアップに努めたほうがプロジェクトも安定しやすいだろう、ということです。

加筆

これは正しいwww

カテゴリー: 開発 | MS-MVP の再受賞(14回目)と学習効果の話 はコメントを受け付けていません

2024年 都知事選を予測&検証する

都知事選は結果を見る通り、小池氏が全得票の半数弱を取る結果となった。

いろいろな後付けの感想がツイッター(X)に流れている訳だが、実はひとつの実験として、行動経済学の視点から実地の数値から都知事選を予測することをやっていた。

結果の資料

東京都知事選挙2024 立候補者紹介・選挙速報(7月7日投票) https://www3.nhk.or.jp/senkyo2/shutoken/20336/skh54664.html

あわせて開票所別も

東京都議会議員補欠選挙2024 立候補者紹介・選挙速報(7月7日投票) https://www3.nhk.or.jp/senkyo2/shutoken/20337/

東京都知事選挙 NHK出口調査結果|NHK 首都圏のニュース https://www3.nhk.or.jp/shutoken-news/20240707/1000106259.html

支持層割合、とくに無党派層の割合を調べる

都知事選 【結果】2024 現職の小池百合子氏が3回目の当選 石丸伸二氏 蓮舫氏らを抑える | NHK | 選挙 https://www3.nhk.or.jp/news/html/20240707/k10014502181000.html

年代別用

私の予想

投票日の1週間前位に投票数を予想している。

私としては、

  • 組織長(首長)の三選は禁止したい派なので、非小池知事を希望
  • 板橋区というのもあり地元当選の蓮舫氏を支持

ということで、蓮舫陣営寄りの観察になりがちなので、これを補正するためにきちんと事前のデータだけを利用することにした。感情的にはツイッター(X)での様相に左右されがちなのだけど、これは安野氏や暇空氏支持でも同じことだろう。どうしてもエコーチェンバーになりがちになってします。このために、行動経済学における「見たものからしかデータを得られない」というバイアスがかかってしまう。また、どうしても期待を込めてしまうために、ちょっとでも希望が持てそうな事実を重く取り上げてしまう。いわゆる、損失バイアスをかけた状態になってしまうのだ。

投票率は 55% 程度、全体で600万票と予想する。各党の支持率は、東京都の比例代表からとってきている(ここは、前回の出口調査から類するするべきであった。無支持層が少なすぎる)。

上段のものが、比例代表などから想定したもの。各党のまとめ方は、組織票(公明と共産)が集まりやすいものと、分散しやすい層(自民、立憲)とわかれている。実は、都内なので都民ファーストの率が高いのであるが、国政に都民ファーストが入っていないので票が読めなかった。また、2020年の選挙でのれいわの票の行先が不明であった。

得票予想としてはとしては、

  • 小池氏 250万票 41% 程度
  • 蓮舫氏 125万票 20%程度

となっている。

実際のところは、

  • 小池氏 290万票 43%程度
  • 蓮舫氏 128万票 19%程度

となるので、ほぼこれで合ってる。石丸氏が得票数を伸ばした(1/4の支持を得ている)ことは予想できなかったし、田母神氏がこれほど低い得票率であったこと、泡沫候補がトータルでも5%未満であることは予想し得なかったので、ちょっと偶然の具合も強いのだが、最初の予想はあっている。

予測時点で、蓮舫氏を当選させるシミュレーションとしては、

  • 自民票が、半分以下にとどまること
  • 立憲、共産が票を強くまとめること
  • 蓮舫氏が無党派層の割合をもう少しあげること

であったが、それでも160万票ぐらいにしか届かない。このためボーダーラインとしては、150万票から200万票ぐらいと考えていたわけだが、黄色のセルをどう工夫しても蓮舫氏の勝ち目は薄かったところである。

それでも期待バイアス(損失バイアスの反対)を掛けて、蓮舫氏の当選を工夫しようとするのだが…結果的には、当初の予測値に近い形になっている。

無慈悲な統計のほうがあたり率が高いというのは、やっぱり行動経済学ですね、という具合になってしまう。

支持政党別を補正して予測し直す

先の予測では、比例代表から支持政党の割合を決めたのだが、無支持層の割合が低すぎる(9.4%)になっている。今回の出口調査を見ると 48% である。

これを使って予測し直してみよう。

得票率は61%となるので、前回より得票率が高い。有効投票数は690万票と100万票ぐらい増えている。

これをベースにして計算し直すと、

  • 小池氏 290万票 42.1%
  • 蓮舫氏 106万票 15.4%

ということで、これは概ね合っている。維新、れいわは資料がないので按分し、都民ファーストはほとんどを小池氏にいれる計算になる。

実際の割合はこれになるが、統計上2桁位で計算すればよいので、1%,2%である、れいわは誤差として無視してよいだろう(前回の山本太郎候補の66万票 10% 程度、が、れいわ新選組以外からの票が多かったことを示している)。

行動経済学の損失/期待バイアスはシステム1を無視すれば、バイアスを避けられる

この実験は、予測値をきちんとした実測値(実測値自体の精度もあるのだが)を使えば、心理学的にシステム1(損失バイアス)を回避して、うまくシステム2の予測ができるだろうか?という実証実験である。

結論から言えば、実測値を忠実に扱えば、システム1に引っ張られることを避けることができる。さらに言えば、実測値をシステム2の思考で忠実に扱えば、かなり近い形で未来の予測が可能であろう、ということになる。まあ、N=1の実験結果でしかないので、都知事選については過去のデータに遡って予測を繰り返してみるとよいだろう。

応用としては、ITプロジェクトの進捗予測や不具合予測に利用可能である。

プロジェクトマネジメントにおける直感はシステム1に引っ張られる可能性もあるが、同時に専門職による経験と勘は内部的にはシステム2の思考を利用している可能性も高く重要である。傍目からみると(自分でさえ)その思考手順がシステム1なのかシステム2なのかを知ることはできない。このため、バイアスが存在するかどうかが判断し辛いのだが、直感と実測からの予測を見比べることで、ずれが生じていれば、それは「システム1に引っ張られているかもしれない」という懸念を持つことが重要だろう。こうすると、経験と勘を捨てずに、精度の高い予測を行うことが可能と考えられる。

ただし、この予測値においては石丸氏の170万票(24%)は予測できていない。無党派層の2割(日経新聞より)からだけでは、24%という高い得票率は想定し得ない。私の予想では、石丸氏、田母神氏がギリギリ供託金の60万票をまぬがれるかどうか、と思っていたのだが、実際のところでは田母神氏は保守票をまとめきれず 27万票しか得られていない。

個人的には、安野氏や暇空氏に注目するところ(賛同ではないけれど)ではあったのだが、2%前後という低い割合になったのは意外なところだった。2%というと、学校の学年で2,3人ぐらいの支持なわけで、クラスで徒党を組むことも難しい。その位のマイノリティということになる。マイノリティにはマイノリティによる戦い方があるので、ここではゲリラ戦ですよね :)

参考資料

都知事選2024予想(Excel)

https://1drv.ms/x/s!AmXmBbuizQkXg4Ag0jTmpCT4KbShow?e=4TavxZ

カテゴリー: 開発 | 2024年 都知事選を予測&検証する はコメントを受け付けていません

組み込みシステムとWEBアプリのグローバル変数の扱いについて

元ネタは、以下からなんですが、もともと新人が C++ で書いたプロトコルがばしばしグローバル変数を叩いていてなんともならん!が発端らしいので、ちょっと筋が違うかもしれないけど、ネタ的に興味深いところがあるので、ちょっと書き連ねておきます。

結論から先に言えば、組み込みシステムの割り込みで使うグローバル変数と、WEBアプリ(ブラウザで作る方)の割り込みはかなり違うので比較ができません。加えて、サーバーサイドのWEBアプリというかWEBシステムも「割り込み」や「非同期」の扱いが、組み込みの非同期とはかなり違います。

組み込みの非同期

先のツイートについては、組み込みの非同期については釈迦に説法のような気もするのですが、このブログの対象としてはあまり知らない分野だと思うので、いちおう。

組み込みにしてもOSあり/なしがあるわけですが、OSを使うにせよ使わないにせよ、「割り込み処理」(インターラプト)内の処理は気を付ける必要があります。いわゆる、ハードウェアの割り込み処理(I/Oポートからのインターラプト)とOSのシグナル、ソフトウェアからの割り込み処理に分けられるわけで、この割り込み処理の中では、

  • スタックが異なる
  • ランタイム(C言語ランタイムなど)の初期化有無がある
  • スレッド(メモリ空間)が異なる場合がある
  • CPUレベルでスイッチングしている

という面がある。

組み込みのC言語プログラミングの場合、C言語ランタイムが初期化されていない場合が多いので通常の関数(printfとかputsとか)が使えないときがある。スタックの切り替えやメモリ空間の切り替えが起こっている可能性もある(ハードウェアのインターラプトの場合はそう)のだけど、これは組み込み用のCPUの場合は、仮想メモリを使わない場合が多いので、プロセスごとに気を付ける必要があるという面もある。

そんなわけで、IntelのようにCPUレベルで仮想化されているわけではないので、プロセス(iTronプロセスみたいに)ごとに相手のプロセス空間を汚さないように注意する必要がある。と同時に、グローバル変数にデータを置くと、自動的に共有メモリとして扱えるので、インターラプト関係では結構便利なので、ついつい使ってしまう、という弱点がある。

先のツイートの元ネタのプロトコル通信でグローバル変数を使っていてあかん、というのはそれで、通信自体はハードウェア的に非同期で走っていてかつ、受信側も非同期で動いているとグローバル変数のメモリ空間は取り合いになってしまうという面がある。そのあたりは、iTron型のリアルタイムOSなのか、OSなしなのかが不明なのでなんとも言えないけど、

  • 全体のメモリが少ないので、プロセス間通信はグローバル変数で受け渡ししてしまうことが多い。
  • ただし、ハード割り込みの関係で、ポーリング形式になりがちな解析部分での非同期化が必要になり、単純な1個のグローバル変数では難しく、リングバッファにするかー、とか考えないといけない。場合によるが。

そんなわけで、組み込みプログラミングに置いて、グローバル変数と割り込みは絶妙な感じにしないといけない。あと、メモリ周りの制限がきついので C++ で大量に new/delete するのは困る。別途 allocater を作るわけだが…という話が待っている。

WEBアプリ(クライアントサイド)の非同期

React.js や Vue.js のように一見、async/await や Promise クラスを使って非同期処理をしているように見えるが、ベースは JavaScript なのでシングルスレッドで動いている。このあたりは見かけ上、非同期処理(特に GUI部分と)をしているように見える、あるいはプログラミングできるというだけになる。

特にシングルページアプリケーション(SPA)の場合は、非同期処理とはいえWeb APIを呼び出している間「待たない」という処理だけなので、組み込みプログラミングでいうところのハードウェア割り込みというものが存在しない。ボタン操作やキーボード操作などは、イベント処理として扱われているので、低レベルな話で言えば OS のポーリング処理に属している。

なので、非同期処理内(ラムダ式とか)であっても、他のクラスを使うこともできるし、グローバル変数(のようなもの)に対しての処理もプログラム言語での仕様でしかなく、ハードウェア上の制限ではない。

特にブラウザアプリケーションの場合は、グローバル変数というものは存在しない。コード内に var 変数として書くこともできるが、これはソースコードを跨げない。ちょうど、C言語のファイル内のスコープと同じになる。唯一、ブラウザ自身を示す window オブジェクトというのがあって、これを媒介してグローバル変数として扱うこともできる。乱暴だが window.a とすれば、a というグローバル変数が作れて別のソースコードから参照可能になる。

まあ、何が言いたいかというと、react.js や vue.js に flux, vuex というストアがあってですね。実質グローバル変数化されるわけですが、これ、ルールを決めて window.* にアクセスすればそれで充分では?と思うのです。実際 vuex が廃れてしまって mvvm パターンの pinia 標準化されつつあるわけで、「グローバル変数」を嫌うあまりに、プロセス/アプリケーションが共通でもつべきメモリ空間、の置き所に混乱が生じてしまっているような気がするのです。まあ、結局のところグローバル変数に落ち着きそうですが。

で、WEBアプリの場合は非同期でラムダ式を使っていたとしても、実質的にはシングルスレッドで動いているのでグローバル変数(windows.* へのアクセス)は競合しません。将来的にネイティブな typescript が wasm 上で実装さえたりすると競合するようになるかもしれませんが、いまのところ大丈夫です。

余談ですが、react, vue ともにビルドをするとひとつの javascript に圧縮されるので、中身で書いた var 変数はグローバル変数化します。ですが、ビルドする前に別のソースコードの変数を参照する手段がない(export/importすれば別だけど)ので、実質グローバル変数がないのです。これは、そういう文法を作ればクリアできる問題かもしれません。

そんなわけで、組み込みプログラミングとWEBアプリでの「グローバル変数」の立ち位置が異なるのと、それにかかわる「非同期処理」のレベルが異なるので、一概にどっちがどうという訳ではありませんね。という話です。

カテゴリー: 開発 | 組み込みシステムとWEBアプリのグローバル変数の扱いについて はコメントを受け付けていません