🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
RTOS

RTOS(リアルタイムOS)は、リアルタイムシステムのためのOSです。実時間システムや実時間OSとも呼ばれ、時間的な制限のある処理を行うための機能・特性を備えています。組み込みシステムの制御に多く用いられています。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

セマフォ

セマフォは、並行プログラミングにおいて同期のサポートを行うための機構。また、それによりプロセス間で交換される信号を指します。複数のプロセスでファイルなどを共有している際の同時アクセスによる破壊や不整合を防ぐことが可能です。

組み込み開発

組み込み開発とは、スマートフォンや家電、自動車などに組み込まれているコンピューターシステムの開発のことです。特定の用途に特化しており、限られた機能のための開発を指します。組み込み開発で作られた機器を組み込み機器と呼び、近年ではPCのオペレーションシステム(OS)にも採用されています。

Q&A

解決済

2回答

3086閲覧

C言語でコピーが発生しない(スタックを消費しない)Getter, Setterの書き方

A-Hayashi

総合スコア14

RTOS

RTOS(リアルタイムOS)は、リアルタイムシステムのためのOSです。実時間システムや実時間OSとも呼ばれ、時間的な制限のある処理を行うための機能・特性を備えています。組み込みシステムの制御に多く用いられています。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

セマフォ

セマフォは、並行プログラミングにおいて同期のサポートを行うための機構。また、それによりプロセス間で交換される信号を指します。複数のプロセスでファイルなどを共有している際の同時アクセスによる破壊や不整合を防ぐことが可能です。

組み込み開発

組み込み開発とは、スマートフォンや家電、自動車などに組み込まれているコンピューターシステムの開発のことです。特定の用途に特化しており、限られた機能のための開発を指します。組み込み開発で作られた機器を組み込み機器と呼び、近年ではPCのオペレーションシステム(OS)にも採用されています。

0グッド

2クリップ

投稿2021/10/11 14:29

RTOSとC言語を使用して組込みソフトの製品を開発しています。

getter, setterを使って下記のように、
TaskA, TaskB間でデータの受け渡しをしたいのですが、

getter, setterを使用する際、
コピー処理が発生してCPUを無駄に使うのと
スタックを無駄に消費してしまいます。

サイズの非常に大きなデータを
getter, setterでやり取りをするため、
RAMが溢れ、スタックサイズを削減する必要性に迫られています。

getter, setterを使って、
dataへのポインタを返すことで、
コピー処理の削減、スタック使用の削減
をすることはできないでしょうか?

その場合、ポインタ経由で
dataを書き換えられてしまうため、
セマフォで排他制御ができなくなるので、
どうしていいか悩んでいます。

アドバイスいただけないでしょうか?

C

1 2int data[1000]; 3 4 5void getter(int *pData) 6{ 7 セマフォ取得 8 memcpy(pData, data, sizeof(data)); 9 セマフォ開放 10} 11 12 13void setter(int *pData) 14{ 15 セマフォ取得 16 memcpy(data, pDate, sizeof(data)) 17 セマフォ開放 18} 19 20 21 22int TaskA(void) 23{ 24 while(1){ 25 int tmp[1000]; 26 getter(tmp); 27 28 tmp[45] = 10; //一部のデータを更新 29 30 setter(tmp): //データを反映 31 } 32} 33 34 35int TaskB(void) 36{ 37 while(1){ 38 int tmp2[1000]; 39 getter(tmp2); 40 41 //tmp2を使う制御 42 } 43} 44

~~

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/10/11 19:39

getter/setter の代わりに、「セマフォ取得してポインタを返す関数」と「セマフォ解放する関数」を用意するとか? (セマフォ解放する関数を呼び忘れるとやばいですが…。)
A-Hayashi

2021/10/11 22:12

セマフォ解放する関数を呼び忘れないために、何かいい方法があればいいのですが。 マクロ関数で、何か工夫できるか考えてみます
dodox86

2021/10/11 23:02 編集

> セマフォ解放する関数を呼び忘れないために、 たぶん、先のご質問からの延長のお話かと思いますが、 アプリ・ドライバ間のセマフォでの排他制御による処理遅延回避方法 https://teratail.com/questions/359898 排他の対象資源を指すポインタだから排他制御できない、と捉えるのではなく、そのアクセスする操作"期間"を排他する、と考えれば良いのではないでしょうか。他のOSなどでもありますが、Windowsではクリティカルセクション(Critical Section)や言語によってはロックの期間を設けることができます。 安全を期してのこととはいえ、コーディング上"忘れてしまう"心配とかとかそういう気楽な状況でも無いと思うのですが、、、
dodox86

2021/10/12 00:00

思ったのですが、RTOSということでタスクがスイッチするとしたら、今のgetter/setter方式ではTaskAがgetterで取得してあとでsetterで上書きするとしたら、他のタスクで同じ頃にget/setしたら古い値で上書きされてしまう心配はないですか? 1. TaskA get 2. TaskB get 3. TaskA getしたもので値を更新 4. TaskB getしたもので値を更新 5. TaskA set 共有領域にTaskAが更新したものを書き戻す 6. TaskB set 共有領域にTaskBが更新したものを書き戻す 結果として5.の内容が破棄される。 それとも上記は、質問用に省略して説明しただけのものでしたでしょうか。
A-Hayashi

2021/10/12 00:09

すみません、これもかけてませんでしたが 書き込むタスクは1つで、読み出すタスクは1つ以上の場合を想定しています。
dodox86

2021/10/12 00:13

書き込むタスクが1つだとしても、書き込むタスクが読み出してから書き込むまでに僅かでも時間差があります。その間に別のタスクが読み出した場合、やはり値の一貫性に問題が生じるような気がしますが、そんなことはないですか。
A-Hayashi

2021/10/12 00:23

1. TaskA get 2. TaskB get 3. TaskA getしたもので値を更新 4. TaskB get 5. TaskA set 共有領域にTaskAが更新したものを書き戻す 6. TaskB get 4においてTaskBで古いデータを使用してしまう期間は発生しますが、 5においてデータを書き込んでいる最中は、セマフォで排他しているため 更新途中のdataがTaskBから読まれてしまうことはないと思います。
A-Hayashi

2021/10/12 00:24

一般的には、2から5まで排他するべきなんでしょうか
A-Hayashi

2021/10/12 00:35

1. TaskA setterでdataへのポインタを返す・セマフォを取得 2. TaskA dataを更新 3. TaskA セマフォを解放 4. TaskB get スタック消費を抑えつつ、dodox86様の懸念事項を解決するならこうでしょうか。 setterをコールする側でセマフォを解放させるとすると、 ①セマフォの解放し忘れによるデッドロック ②複数のセマフォを同時に取得する場合、取得・解放の順がまずくてデッドロック くらい懸念点があるでしょうか。 y_waiwai様がおっしゃるように、①は簡単に気づけますが ②に気付くのが難しくなりますね。 セマフォを続けて取得できなくするようにマクロで工夫する等、考えてみます。
dodox86

2021/10/12 01:20

> 一般的には、2から5まで排他するべきなんでしょうか データによるのでは。読み出す値が他の値に依存していなくて、読み出した時点で処理をして構わないものであれば、他のタスクでの更新を待つ必要は無いはずです。他のタスクで読み出されたが最後、その値に依存して更新処理が続くのであれば、その後の処理に影響が出ると思えます。何らかのテーブル全体をただ排他するのではなく、排他が必要なものとそうでないものをちゃんと分別すれば良いのではないでしょうか。
dodox86

2021/10/12 01:30 編集

ソフト(OS)、ハード、更に特別に用意されたCPU命令の連携でセマフォなどの排他制御を実現している場合、それ自体CPUパワー、というかコストが必要かもしれませんので、排他制御はできるだけ無くす方が良いはずです。ほぼCPU命令だけで実現するロックの類もCPUによっては存在したと思いますが。
A-Hayashi

2021/10/12 09:02

dodox86様 排他の考えは理解しました。 排他制御を極力なくしたいのですが、なかなかなくせずにいます。 ダブルバッファにして、排他制御の期間を少しでも短くできないか等 検討中です。
guest

回答2

0

実装イメージですが、こんなかんじで行こうと思います。

  • Getter, Setterではポインタを返す
  • セマフォの解放忘れがないようにマクロ化

みなさん、すごく為になる議論をさせていただき
誠にありがとうございました。

C

1typedef struct{ 2 int data; 3 int data2; 4 float data3; 5}testData_t; 6 7testData_t data; 8 9int semaphore = 0; 10 11void modelGet(testData_t **p){ 12 *p = &data; 13} 14 15void modelSet(testData_t **p){ 16 *p = &data; 17} 18 19#define MODEL_GET(a, b) semaphore++; modelGet(a); b; semaphore--; 20#define MODEL_SET(a, b) semaphore++; modelSet(a); b; semaphore--; 21 22 23int main(void){ 24 testData_t *p; 25 int tmp = 0; 26 27 MODEL_SET(&p, 28 p->data = 123; 29 p->data2 = 1; 30 p->data3 = 3.14; 31 ); 32 33 MODEL_GET(&p, 34 tmp = p->data; 35 ); 36} 37 38

投稿2021/10/12 05:13

A-Hayashi

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2021/10/12 05:28

あくまで実装イメージで、本物ではないのでしょうけれど、セマフォの現在値に応じて排他している様子もないし、ソフトだけでやっているし、ポインタを返した後でセマフォの排他制御が離れたところで同じアドレス、領域への書き込みをしているかんじがするし、読んだ者にかえって誤解を与えるのでは、と思いました。(少なくとも私は、本当にこれでイメージを反映できているのか? と思いました)
A-Hayashi

2021/10/12 06:10

セマフォは使用されるRTOSによりI/Fが異なるため、 あくまでイメージで実装しています。 各RTOSに従って、修正していただく必要があります
fana

2021/10/12 06:30

> testData_t *p; これが main 内に存続している時点で, MODEL_SET/GET 以降に,このpを介して好き放題できるような気がします. 下記ではsemaphoreに関する部分は省略しますが, こんな感じにして p を閉じ込めてはどうでしょう. ( MODEL_SET/GET 内部ではポインタの名前は p なのだ,という約束事にする) --- const testData_t *Ptr_for_Get() { return &data; } testData_t *Ptr_for_Set(){ return &data; } #define MODEL_GET( codes ) { const testData_t *p=Ptr_for_Get(); codes; } #define MODEL_SET( codes ) { testData_t *p=Ptr_for_Set(); codes; } ---
A-Hayashi

2021/10/12 09:00

おっしゃる通り、排他制御中でなくても ポインタ経由でdataを書き換えられるのでまずいですね。。 fana様のアイデアを使わせていただきます。 dataの型は他にもいろいろあるので、 void *型で返して、codes部分でキャストして使ってもらうことにします。 constをつけて、ポインタとポインタが指す先を書き換えることを禁止しようと考えたのですが、 キャストされてしまうと、constが無意味になるので、 fana様のようにマクロに閉じ込める方法が良いと思います。
fana

2021/10/12 09:27

まぁ,このようなマクロを頑張って用意したところで, * マクロを介さずに関数 Ptr_for_Get() を単独で呼ばれてしまったら死亡 * codes部分に return とか break みたいなのを書かれてしまえば死亡 とか,しくじりパターンが完全になくなるわけでもないように思われますし, さらに void* を適切にキャストしなきゃならんとか言い始めるくらいなら 「セマフォ解放する関数を呼び忘れないように気を付けよう」で普通にやってた方がシンプル,とかいうことにもなりそうな気がしないでもない…?
A-Hayashi

2021/10/12 10:02

おっしゃる通りだとは思います。 悪意のある実装者は考えないとして、 普通に実装していてミスをするような場合を いかにふせぐという観点で、マクロは有効なのかなと思います。 * マクロを介さずに関数 Ptr_for_Get() を単独で呼ばれてしまったら死亡 ⇒防げないですね。。 * codes部分に return とか break みたいなのを書かれてしまえば死亡 ⇒静的解析で実行されないコードがある旨で検出できると思います。 * void* を適切にキャストしなきゃならん ⇒その通りです
dodox86

2021/10/12 10:15

不特定多数の人が使うOSやフレームワークではなく、ある特殊な用途の製品の組み込みソフトを造っていて、開発に関わる方々も素人ではないのですから、コーディングの仕方(含むマクロの使い方)も設計の一部として、制限事項も既知のこととして進めればよいのだと思います。使い方を間違うのは極端な話、間違った人が仕込むバグです。その辺りのバランスでしょうね。
TaroToyotomi

2022/01/22 05:50

この実装だと、MODEL_SET/MODEL_GETのコード部分で、さらにMODEL_SET/MODEL_GETを呼び出すようなサブルーチンが入っているとデッドロックが発生すると思います。 これを防ぐには、同一タスクでの2重ロックはしないようにするなどの工夫が必要になります。 個人的には、ポインタを使用している期間を明確化するという意味で、fanaさんの言う通り「セマフォ解放する関数を呼び忘れないように気を付けよう」の方が不具合は少なくなる気がします。
A-Hayashi

2022/02/11 04:53

ご回答ありがとうございます。 「セマフォ解放する関数を呼び忘れないように気を付けよう」 のほうが、いいような気はしてきました。 ある程度長い時間、セマフォが解放されなければ セマフォ解放する関数の呼び忘れに気づけますし
guest

0

ベストアンサー

Task側のtmp確保をローカル変数としてしまうためにスタックを食いつぶしてしまいますね。
ここはmalloc(みたいな関数を作って)でヒープから確保するようにすればどうでしょう

#getter、setterではスタック消費してないですよね

投稿2021/10/11 22:05

編集2021/10/11 22:08
y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

A-Hayashi

2021/10/11 22:14

回答ありがとうございます。 すみません、書けてなかったのですが 動的にメモリを確保することは基本的にできない開発でして、 できればmallocのようなことは避けたいです。 あと、mallocにしたとしても 無駄にコピー処理が発生してしまうので、 それを無くせないかを考えています。
y_waiwai

2021/10/11 22:20

動的に、じゃなく、ヒープ上にそのタスク分だけそのエリアを確保すればいいだけです まあ、グローバル変数でタスク数分だけ定義すればいい、ってはなしですね 組み込み用途ではスタックは数KByte程度しか確保しないんで、ローカルで確保するってのはヤバいです
y_waiwai

2021/10/11 22:42

んで、コピーを発生させずに、といっても、そのシステムの要件がわからないと答えようないです たんにdataのナカミを変更できればそんでいい、ってことなら、ポインタ渡す関数作ればいいだけですが、そんでいいのん?ってことになります
A-Hayashi

2021/10/11 22:47

それぞれのタスクでヒープから確保しておいたメモリを、そのタスクの中で使い回すということですね。 タスクの中で動的にメモリ確保するなら、メモリ解放忘れても不具合解析しやすそうです
A-Hayashi

2021/10/11 22:51

コピーを発生させずにというのは、CPU負荷を下げるためです。ブラシレスモータ制御からステッピングモーター制御、各種アプリの処理も乗るので処理不可が重いのが課題です。 ポインタを渡す関数を作ると、排他制御できなくなるので、 上でhoshi-takanoriさまが 仰っている方法になるのかなと考えているのですが、セマフォ開放忘れをどう防ぐかですね
y_waiwai

2021/10/11 22:52

組み込みであればタスクの数ってのは決め打ちになってると思います なら、そのメモリ領域も決め打ちの数だけ確保しとけばそんでいいってことです
y_waiwai

2021/10/11 22:55

排他制御するってことなら別にコピーの必要はないんでは。 ロックして、変数操作して、アンロックするってだけでいいでしょう 開放忘れすれば必ずデッドロックするので、バグとしては超簡単なレベルですよ
A-Hayashi

2021/10/12 00:29

コピーしている間は排他制御が必要だと考えたのですが、 getterをコールする側で、セマフォの解放をさせるなら、 コピーは不要でポインタを返せばいいですね。 セマフォの解放をgetterをコールする側で行わせると、 セマフォの解放を忘れなかったとしても、 複数のセマフォの取得・解放順によって、デッドロックが起こってしまう可能性があるので これを懸念して、getter, setter内でセマフォの取得・解放を行っていました。 それでコピーが必要になったということです。 経緯の説明までできておらず、すみません
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問