Armadilloフォーラム

/dev/memでの任意のメモリ空間にアクセスについて

tokita.shinichi

2023年12月5日 14時24分

お世話になります。
/dev/memとmmapを使用して任意のメモリ空間にアクセスするプログラムを使用していますが、socket通信のrecv()でバッファをmmapでマップしたアドレスを指定した場合に、Errno:14 Bad addressのエラーが発生します。
エラーとしては、受信バッファポインタが、プロセスに割り当てられたアドレス空間の範囲外を指しているとのことですが、mmapでマップした範囲外ではないと思っています。
別のバッファでrecv()を行い、後でマップしたアドレスにコピーした場合はエラーが発生しません。
また、受信するサイズによってはエラーが発生しないこともあり、例えば、4byteの受信では発生せず、16byteの受信では発生します。
同動作をsend()で試した場合はエラーが発生しませんでした。

Armadillo特有の問題ではないかもしれませんが、現象についてご存じであればお力添えを頂きたく存じます。

コードの一部を記載します。

#define MEM_TOP 0xA0000000
#define MEM_SIZE 0x20000000
 
int fd;
char *adr;
char temp[0x100000];
 
fd = open("/dev/mem", O_RDWR | O_SYNC);
adr = mmap(NULL, MEM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, MEM_TOP);
 
size = 16;
// mmapで取得したアドレスでrecv これはエラー(errno14)となる
cnt = recv(sckt, adr, size, 0);
 
// 別のバッファに入れてからコピーするとエラーとならない
cnt = recv(sckt, temp, size, 0);
memcpy(adr, temp, cnt);

以上、宜しくお願いいたします。

コメント

アットマークテクノの古賀です。

tokita.shinichiさん:
>/dev/memとmmapを使用して任意のメモリ空間にアクセスするプログラムを使用していますが、socket通信のrecv()でバッファをmmapでマップしたアドレスを指定した場合に、Errno:14 Bad addressのエラーが発生します。

/dev/mem に対する mmap() に MAP_SHARED を指定してマップした領域を、socket 通信の受信バッファに使おうとしていらっしゃる理由は、何でしょうか?
ソケットからのデータ受信と、受信したデータの処理を異なるプロセスで行う設計で考えていらっしゃり、受信バッファ領域をプロセス間で共有したいということでしょうか。もしそうであれば、/dev/mem ではなく、shm_open() と mmap() を組み合わせる方が良いんじゃないかと思います:
 https://ja.manpages.org/shm_open/3

>コードの一部を記載します。

#define MEM_TOP 0xA0000000
#define MEM_SIZE 0x20000000
...

提示して頂いたコードの、上記定数定義を見ると、Armadillo-IoT G4 の RAM 2GB の末尾 512MB 部分を共有メモリ領域として使おうとしていらっしゃるのだろうかと想像しています。
しかし、RAM 領域はカーネルの管理対象ですから、別途設定して、カーネルに RAM の当該部分を使わせないようにしなければ、正しく動作しないでしょう:
 https://unix.stackexchange.com/questions/516416/allow-partial-memory-ma…

ということで、プロセス間共有メモリを使用するのであれば、shm_open() と mmap() を組み合わせるのが良いかと思いますが、いかがでしょうか?
以下の、ご報告の振る舞いについての質問への直接の回答ではなく恐縮ですが、もし参考になりましたら幸いです。

>エラーとしては、受信バッファポインタが、プロセスに割り当てられたアドレス空間の範囲外を指しているとのことですが、mmapでマップした範囲外ではないと思っています。
>別のバッファでrecv()を行い、後でマップしたアドレスにコピーした場合はエラーが発生しません。
>また、受信するサイズによってはエラーが発生しないこともあり、例えば、4byteの受信では発生せず、16byteの受信では発生します。
>同動作をsend()で試した場合はエラーが発生しませんでした。
>
>Armadillo特有の問題ではないかもしれませんが、現象についてご存じであればお力添えを頂きたく存じます。

tokita.shinichi

2023年12月6日 11時07分

古賀様
返信ありがとうございます。

> /dev/mem に対する mmap() に MAP_SHARED を指定してマップした領域を、socket 通信の受信バッファに使おうとしていらっしゃる理由は、何でしょうか?

当方ではM7コアを使用していまして、こちらとの共有メモリとしてDDR領域の一部を使用しようと考えています。Linuxで受信したデータをM7コアで使用する設計です。
プロセス間のメモリ共有ではないため、残念ながらshm_open()は使用できないと考えています。

> しかし、RAM 領域はカーネルの管理対象ですから、別途設定して、カーネルに RAM の当該部分を使わせないようにしなければ、正しく動作しないでしょう:

記載して頂いたリンクのようにブートパラメータを設定してはいませんが、デバイスツリーを変更して領域をreserved-memoryに設定していました。

armadillo:~# cat /proc/iomem
...
a0000000-bfffffff : reserved

ただ、CONFIG_STRICT_DEVMEMによるアクセス制限のことは知らなかったため、こちらを試してみたいと思います。

以上、宜しくお願いいたします。

アットマークテクノの古賀です。

tokita.shinichiさん:
>>/dev/mem に対する mmap() に MAP_SHARED を指定してマップした領域を、socket 通信の受信バッファに使おうと
>>していらっしゃる理由は、何でしょうか?
>
>当方ではM7コアを使用していまして、こちらとの共有メモリとしてDDR領域の一部を使用しようと考えています。Linuxで受信したデータをM7コアで使用する設計です。

もしかして、と思っていたのですが、そうでしたか。了解しました。
M7 コアの使用については、Armadillo-IoT G4 としてはサポートしていませんので、マニュアルに記載していません。
が、後述するように、コア間通信を含む M7 コア使用のデモを公開していますので、そちらが参考になりましたら幸いです。

>プロセス間のメモリ共有ではないため、残念ながらshm_open()は使用できないと考えています。

そうですね。
ということであれば、以下のアプリケーションノートなどで紹介されている、i.MX 8M Plus の MU (Message Unit) を利用した、RPMsg というコア間通信機能を使うのがよいと思います:
 https://www.nxp.com/docs/en/application-note/AN13400.pdf

上述したデモは、こちらです:

 Armadillo RTOS demo
 https://github.com/atmark-techno/armadillo-x2-rtos-demo

tokita.shinichi

2023年12月6日 16時55分

古賀様

> M7 コアの使用については、Armadillo-IoT G4 としてはサポートしていませんので、マニュアルに記載していません。
> が、後述するように、コア間通信を含む M7 コア使用のデモを公開していますので、そちらが参考になりましたら幸いです。

こちらのデモの存在は知りませんでした。参考にさせていただきます。

> ということであれば、以下のアプリケーションノートなどで紹介されている、i.MX 8M Plus の MU (Message Unit) を利用した、RPMsg というコア間通信機能を使うのがよいと思います:

RPMsgの使用も検討したのですが、なるべくM7コアの処理に通信用のリソースを割きたくなかったため、当方では不採用としていました。
 
また、CONFIG_STRICT_DEVMEMを無効したカーネルで試してみましたが、結果は変わりませんでした。

以上、宜しくお願いいたします。

アットマークテクノの古賀です。

tokita.shinichiさん:
>>M7 コアの使用については、Armadillo-IoT G4 としてはサポートしていませんので、マニュアルに記載していません。
>>が、後述するように、コア間通信を含む M7 コア使用のデモを公開していますので、そちらが参考になりましたら幸いです。
>
>こちらのデモの存在は知りませんでした。参考にさせていただきます。

もしお役に立つようであれば、幸いです。
共有メモリだけでコア間通信する場合、コア間のデータ受け渡しのプロトコルをきちんと決めないと、後々ハマる可能性が高いですから、RPMsg を使うのが確実じゃないかなあ、と思います。

>>ということであれば、以下のアプリケーションノートなどで紹介されている、i.MX 8M Plus の MU (Message Unit) を利用した、RPMsg というコア間通信機能を使うのがよいと思います:
>
>RPMsgの使用も検討したのですが、なるべくM7コアの処理に通信用のリソースを割きたくなかったため、当方では不採用としていました。

ちなみに、A53 コアで動く Linux から、M7 コア側にデータを渡した場合、M7 コアにデータ到着を通知するのは、どうされるんでしょうか?
M7 コアが定周期で共有メモリ領域をポーリングする仕組みにして、タイムスタンプ付きのダブルバッファやリングバッファを使うなどするとしても、それよりは、MU 経由の割り込み通知を使う方が効率的な場合が少なくないんじゃないかと思います。

>また、CONFIG_STRICT_DEVMEMを無効したカーネルで試してみましたが、結果は変わりませんでした。

最初のコメントで紹介した StackExchange の質問スレッドですが、質問そのものではなく、こちらのコメントを指したつもりでした。ごめんなさい:
 https://unix.stackexchange.com/a/516430

ちなみにですが、mmap() の flags 引数の値を、MAP_SHARED ではなく、MAP_FIXED_NOREPLACE や MAP_POPULATE にした場合は、どうなるでしょうか?

tokita.shinichi

2023年12月7日 14時48分

古賀様

> 共有メモリだけでコア間通信する場合、コア間のデータ受け渡しのプロトコルをきちんと決めないと、後々ハマる可能性が高いですから、RPMsg を使うのが確実じゃないかなあ、と思います。
> ちなみに、A53 コアで動く Linux から、M7 コア側にデータを渡した場合、M7 コアにデータ到着を通知するのは、どうされるんでしょうか?
> M7 コアが定周期で共有メモリ領域をポーリングする仕組みにして、タイムスタンプ付きのダブルバッファやリングバッファを使うなどするとしても、それよりは、MU 経由の割り込み通知を使う方が効率的な場合が少なくないんじゃないかと思います。

共有メモリという書き方をしていましたが、実際はM7コアで動作するプログラムのパラメータ(メモリ)に直接A53コアがアクセスする動作になります。
そのためM7コア側は指定のパラメータを使用するだけで、通知を受信したりの通信処理は一切行いません。
また、以前にこの設計で開発していた経緯があるため、流用したという都合もあります。

> ちなみにですが、mmap() の flags 引数の値を、MAP_SHARED ではなく、MAP_FIXED_NOREPLACE や MAP_POPULATE にした場合は、どうなるでしょうか?

MAP_SHAREDではなく、とありますが、flagはMAP_SHAREDかMAP_PRIVATEは必須という認識でしたが、間違っているでしょうか。
MAP_POPULATEとMAP_SHAREDを指定した場合は、動作は変わりませんでした。
MAP_FIXED_NOREPLACEとMAP_SHAREDを指定した場合は、mmapでエラーとなり、エラーはErrno17:File existsでした。(既存のマッピング範囲と衝突している?)
  
共有メモリ領域をreserved-memoryにしたと書きましたが、実際の領域はM7コアを制御するためのremoteprocで使用されているはずのため、このあたりもエラーに関係しているかもしれません。

以上、宜しくお願いいたします。

アットマークテクノの古賀です。

tokita.shinichiさん:
>>共有メモリだけでコア間通信する場合、コア間のデータ受け渡しのプロトコルをきちんと決めないと、後々ハマる可能性が高いですから、RPMsg を使うのが確実じゃないかなあ、と思います。
>>ちなみに、A53 コアで動く Linux から、M7 コア側にデータを渡した場合、M7 コアにデータ到着を通知するのは、どうされるんでしょうか?
>>M7 コアが定周期で共有メモリ領域をポーリングする仕組みにして、タイムスタンプ付きのダブルバッファやリングバッファを使うなどするとしても、それよりは、MU 経由の割り込み通知を使う方が効率的な場合が少なくないんじゃないかと思います。
>
>共有メモリという書き方をしていましたが、実際はM7コアで動作するプログラムのパラメータ(メモリ)に直接A53コアがアクセスする動作になります。
>そのためM7コア側は指定のパラメータを使用するだけで、通知を受信したりの通信処理は一切行いません。

A53 コアから M7 コアのプログラムにパラメータを渡すタイミングは、動作中の任意のタイミングでしょうか?
ソケット通信を使っていらっしゃいますので、次のような感じだろうかと想像しています:

・Armadillo とは別の端末から、ユーザー操作などにより、新たに設定するパラメータの内容を Armadillo にソケット通信で送信
・Armadillo の A53 コアが、ソケット通信で新たなパラメータを受信して、M7 コアのプログラムが参照するパラメータ領域に書き込む

この場合、パラメータが単一の word ではなく、複数のフィールドで構成されたデータ構造である場合、A53 コアが新しいパラメータ内容を書き込んでいる途中に M7 コアが読んでしまうと、M7 コアのプログラムの動作がおかしくなってしまう場合がありますよね。
複数のフィールドの値が、一そろいで更新されなければ整合しない場合は、途中まで書き換わった状態で M7 コアが読み出すと、不整合なパラメータセットを参照してしまうはずです。
なので、少なくともダブルバッファで受け渡しを行い、パラメータセットを全て書き終えた方のバッファを M7 コアが読むようにする必要があるんじゃないかと思います。

>>ちなみにですが、mmap() の flags 引数の値を、MAP_SHARED ではなく、MAP_FIXED_NOREPLACE や MAP_POPULATE にした場合は、どうなるでしょうか?
>
>MAP_SHAREDではなく、とありますが、flagはMAP_SHAREDかMAP_PRIVATEは必須という認識でしたが、間違っているでしょうか。
>MAP_POPULATEとMAP_SHAREDを指定した場合は、動作は変わりませんでした。
>MAP_FIXED_NOREPLACEとMAP_SHAREDを指定した場合は、mmapでエラーとなり、エラーはErrno17:File existsでした。(既存のマッピング範囲と衝突している?)
>  
>共有メモリ領域をreserved-memoryにしたと書きましたが、実際の領域はM7コアを制御するためのremoteprocで使用されているはずのため、このあたりもエラーに関係して

最初の質問で、以下のように書いていらっしゃいましたよね。

tokita.shinichiさん(2023年12月5日 14時24分):
>別のバッファでrecv()を行い、後でマップしたアドレスにコピーした場合はエラーが発生しません。

であれば、共有メモリ領域のアドレスを socket API に渡すのではなく、通常のメモリ領域を socket API に渡して受信したのち、その内容を共有メモリ領域へコピーする、というのが安直な解のように思います。

tokita.shinichi

2023年12月7日 19時25分

古賀様

> ソケット通信を使っていらっしゃいますので、次のような感じだろうかと想像しています:
> ・Armadillo とは別の端末から、ユーザー操作などにより、新たに設定するパラメータの内容を Armadillo にソケット通信で送信
> ・Armadillo の A53 コアが、ソケット通信で新たなパラメータを受信して、M7 コアのプログラムが参照するパラメータ領域に書き込む

はい、その通りになります。

> この場合、パラメータが単一の word ではなく、複数のフィールドで構成されたデータ構造である場合、A53 コアが新しいパラメータ内容を書き込んでいる途中に M7 コアが読んでしまうと、M7 コアのプログラムの動作がおかしくなってしまう場合がありますよね。
> 複数のフィールドの値が、一そろいで更新されなければ整合しない場合は、途中まで書き換わった状態で M7 コアが読み出すと、不整合なパラメータセットを参照してしまうはずです。

そのリスクはあると思います。アプリケーションとしては、そのようなケースの場合は、M7コアの制御を停止する機構を設け、停止中にパラメータセットを変更するような使い方を想定しています。

> であれば、共有メモリ領域のアドレスを socket API に渡すのではなく、通常のメモリ領域を socket API に渡して受信したのち、その内容を共有メモリ領域へコピーする、というのが安直な解のように思います。

私の方もエラーが解決しなければそうするしかないとは考えてはいましたが、エラーの内容に疑問を感じ、もし解決できればと思いフォーラムに投稿させていただいた次第でした。
いろいろアドバイスも頂けたため、参考にして解決法を検討したいと思います。

以上、宜しくお願いいたします。

at_dominique.m…

2023年12月7日 11時06分

tokita.shinichiさん、

お世話になっています、
マルティネです。

> /dev/memとmmapを使用して任意のメモリ空間にアクセスするプログラムを使用していますが、socket通信のrecv()でバッファをmmapでマップしたアドレスを指定した場合に、Errno:14 Bad addressのエラーが発生します。
> エラーとしては、受信バッファポインタが、プロセスに割り当てられたアドレス空間の範囲外を指しているとのことですが、mmapでマップした範囲外ではないと思っています。

よこからすみません、古賀さんの言う通りにはこういうユースケースをあまりサポートできませんが興味半分で少しみてみたら、linux カーネル net/core/datagram.c__skb_datagram_iter() で simple_copy_to_iter が短い理由で -EFAULT としてエラーします。

成功の場合(15bytes)

FUNCTION CALL TRACE                         RESULT    DURATION
-----------------------------------------   ------  ----------
→ __arm64_sys_recvfrom                                        
    → __sys_recvfrom                                          
        → tcp_recvmsg                                         
            ↔ tcp_cleanup_rbuf              [void]     6.875us
            → skb_copy_datagram_iter                          
                → __skb_datagram_iter                         
                    ↔ simple_copy_to_iter   [15]      14.125us
                ← __skb_datagram_iter       [0]       30.375us
            ← skb_copy_datagram_iter        [0]       47.250us
            ↔ tcp_cleanup_rbuf              [void]     8.125us
        ← tcp_recvmsg                       [15]    1017.009us
    ← __sys_recvfrom                        [15]    1030.384us
← __arm64_sys_recvfrom                      [15]    1062.509us

失敗の場合(16 bytes)

→ __arm64_sys_recvfrom                                           
    → __sys_recvfrom                                             
        → tcp_recvmsg                                            
            ↔ tcp_cleanup_rbuf              [void]        6.750us
            → skb_copy_datagram_iter                             
                → __skb_datagram_iter                            
                    ↔ simple_copy_to_iter   [4]          18.500us
                ← __skb_datagram_iter       [-EFAULT]    34.625us
            ← skb_copy_datagram_iter        [-EFAULT]    59.250us
            ↔ tcp_cleanup_rbuf              [void]        6.500us
            ↔ tcp_set_state                 [void]        9.375us
        ← tcp_recvmsg                       [-EFAULT]  1094.510us
    ← __sys_recvfrom                        [-EFAULT]  1114.885us
← __arm64_sys_recvfrom                      [-EFAULT]  1147.135us

(頭の 4 byte だけがメモリに受信されたことも確認しました)

これ以上調べるのにはちょっと時間がないので推測ですが、ドライバー側でパッケットのサイズによって違うところに保存されて、このメモリ領域がカーネル管理外になっていることでそこでその最適化で使用できないことになっていないかと考えています。

満足できる返事ではないと思いますが、reserved ではなく別の方法でメモリを取得すれば動作できるようになるかもしれません。
rpmsg の性能を一度デモで確認してからまだネットワークからのデーターを直接に受信が必要と判断した場合はその方向で調べてみてください。

よろしくお願いします。

tokita.shinichi

2023年12月7日 15時51分

マルティネ様

ご確認いただきありがとうございます。
カーネルの処理までは調査していなかったため参考になります。
__skb_datagram_iter()のソースを少し確認したところ、カーネルのバグの可能性もあるとのコメントもあったため、カーネルソースの確認も視野にしれて調査したいと思います。

以上、宜しくお願いいたします。