Armadilloフォーラム

電源投入後のプログラム実行エラーについて

o-m

2019年11月18日 14時13分

お世話になっております。

Armadillo440液晶タイプを使用しております。
lftpコマンドでATDEからArmadilloへプログラムを転送して実行する手段では上手く実行されたのですが、/etc/config/rc.localに記述して電源投入後にプログラムを実行するよう設定すると、親プロセスは上手く実行されるのですが子プロセスは上手く動かず、LCD画面が更新されません。

問題が発生しているプログラムは以下の通りです。
親プロセスがUARTで数値を取得、子プロセスが取得したデータをGDK+を使用してLCD画面に表示を行っております。
子プロセス作成後、どちらのプロセスでも無限ループを実行しています。
プロセス間の数値のやり取りは4本のパイプを作成して通信を行っています。(やり取りするデータが4つあるため)
また子プロセスは50msごとに親プロセスからデータを受信しています。
以前UARTが取得したデータと変更があったらLCDのラベルや画像を変更し、変更がなければそのままの状態にするというプログラムです。

どうやらrc.localでの実行だとパイプ通信部分が上手く実行されていないようで、親プロセスはデータの送信を行っているようなのですが、子プロセスは1度データを受信するとそれ以降はデータを受信しないようです。read関数の返り値が常に0となります。

rc.localからの実行で子プロセスが受信出来ない理由と子プロセスが親プロセスからデータを受信出来るようにする方法を教えて頂けると幸いです。
お手数をおかけしますがよろしくお願いいたします。

コメント

at_makoto.sato

2019年11月18日 16時42分

佐藤です。

ご質問の内容からだと分かりかねる部分がありますので、
可能であれば、現象が再現する最小限のサンプルプログラムをアップしていただくことは可能でしょうか。
そうしていただくとより正確なアドバイスができるかと思います。

o-m

2019年11月19日 10時20分

佐藤様

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

以下に問題が発生している部分をアップします。
作成しているプログラムは機能ごとに分割しており、mainソースファイル、UARTでデータを取得するソースファイル、データを保持やプロセス間通信を行うソースファイル(data_mgr.c)、LCD画面を変更するソースファイル(lcd_mgr.c)のような形です。

lcd_mgr.c
/* 50ms経過するたびに実行する関数(g_timeout_add関数で登録) */
static gboolean refreshData( gpointer data )
{
 
    /* 親プロセスからデータを取得 */
    DataMgr_get_fromMain();
 
    ====親プロセスから取得したデータに基づいてLabel(printStatus1)を変更する====
 
 
 
    refreshScreenFlag = DataMgr_Get_changeStatus2();    /* 親プロセスから送られてきたデータに基づいて画像(printStatus2)を変更するか判断。変更するならば1以上を返す */
    if (refreshScreenFlag >= 1) {
         gdk_window_invalidate_rect( printStatus2->window, NULL, FALSE);
    }
 
    return TRUE;
}
 
/* g_signal_connect関数でexpose-eventが発生するたびに実行するよう登録 */
static gboolean cb_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer user_data )
{
    GtkWindow *drawable = widget->window;
    cairo_t *cr;
 
    cr = gdk_cairo_create( drawable );
 
    LcdMgr_showImage( cr );    /* mainプロセスから送られてきたデータにより表示する画像を変更する関数 */
 
    cairo_destroy( cr );
 
    return FALSE;
}

DataMgr_get_fromMain関数では更新するか確認したい変数ごとに以下の関数を行っています。
mainプロセスではUARTから取得した値に変化があった場合のみLCDプロセスに送信します。
rc.localで記述して実行するとDataMgr_selectReadFromMain関数の返り値が常に0であるのでパイプ間通信が上手く実行されていないと予想しております。

data_mgr.c
/* 引数のfdはpipe関数で作成したファイル記述子 */
/* 返り値が1以上ならばLCD画面をreadした値で更新する */
static char DataMgr_selectReadFromMain( int fd, char str[] )
{
    ssize_t    checkRet = 0;
    fd_set    temp_read;
    int    checkRead;
    struct    timeval    selectTimer;
 
    selectTimer.tv_sec = 0;
    selectTimer.tv_usec = 0;
 
    memcpy( &temp_read, &read_fdset, sizeof( temp_read ));    /* read_fdsetは大域変数として宣言されている */
 
    /* mainプロセスから情報が送信されたときだけread */
    checkRead = select( (pipe関数で作成した記述子の中で一番大きい記述子) + 1, &temp_read, NULL, NULL, &selectTimer);
    if (checkRead == -1) {
        perror( "sending:select()" );
    } else if (checkRead >= 1) {
        if (FD_ISSET( fd, &temp_read )) {
            /* LCDプロセスがデータ取得 */
            checkRet = read( fd, str, strlen( str ) );
            if (checkRet <= -1) {
                perror( "data_read:error" );
            }
        }
    } else {
        /* 処理なし */
    }
 
    return checkRet;
}

at_mizo

2019年11月19日 11時07分

溝渕です。

> 以下に問題が発生している部分をアップします。

これだと、こちらで現象の再現確認をすることが不可能なので、アドバイスす
ることが困難です。

現象が再現する最小限のサンプルプログラムを作成していただくことは可能で
しょうか。

先のご質問にあった、

> lftpコマンドでATDEからArmadilloへプログラムを転送して実行する手段では
> 上手く実行されたのですが、/etc/config/rc.localに記述して電源投入後にプ
> ログラムを実行するよう設定すると、親プロセスは上手く実行されるのですが
> 子プロセスは上手く動かず、

という内容から、それぞれの差異について調べることも解決に繋るかもしれません。具体的には、

- 実行ユーザー(rootかそれ以外か)
- 環境変数(mainの第3引数から確認可能です)
- 実行タイミング(本プログラムに依存するデーモンが起動しているか)

等を確認してみてください。

o-m

2019年11月20日 9時27分

溝渕様

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

> これだと、こちらで現象の再現確認をすることが不可能なので、アドバイスす
> ることが困難です。
>
> 現象が再現する最小限のサンプルプログラムを作成していただくことは可能で
> しょうか。

現象が再現する最小限のサンプルプログラムを作成する前に伊澤様のご指摘を修正したところ、問題が解決いたしました。
未操作の配列に対してstrlen関数を使用してreadするバイト数を指定していたことが原因だった模様です。
select関数の条件分岐のelseの処理によってDataMgr_selectReadFromMain関数の返り値が常に0になっていると思い、パイプ間通信が上手くいっていないと思っておりました。
修正前に確認したところ「if (FD_ISSET( fd, &temp_read )) 」を通っておりましたのでパイプ間通信に不具合がなく、単純にread出来ていなかっただけのようです。

ご対応ありがとうございました。

izawa

2019年11月19日 11時12分

毎度お世話様、伊澤です。
直接の原因の指摘ではありませんが、ソースコードを見て幾つか。

・DataMgr_selectReadFromMain()のstrの扱い
引き数strは内部で更新しない状態でstrlen()に引き渡されています。
この為、関数呼び出し時点で何をポイントしているかによって振る舞いが変わります。
例えばstrのポイント先がナル文字であれば、strlen()の戻り値は0であり、当然にread()は行なわれません。
また、strのポイント先が未初期化状態であれば、read()によってバッファオーバーフローが引き起こされる可能性があります。

・DataMgr_selectReadFromMain()の戻り値の扱い
ssize_tであるcheckRetをcharに暗黙的castしていますが、charがsignedであるかunsignedであるかは環境依存です。

後者は兎も角、前者は読み込みバッファ長を明示的に引き渡すように実装するべきではないでしょうか。

o-m

2019年11月20日 9時12分

伊澤様

ご指摘ありがとうございます。

> ・DataMgr_selectReadFromMain()のstrの扱い
> 引き数strは内部で更新しない状態でstrlen()に引き渡されています。
> この為、関数呼び出し時点で何をポイントしているかによって振る舞いが変わります。
> 例えばstrのポイント先がナル文字であれば、strlen()の戻り値は0であり、当然にread()は行なわれません。
> また、strのポイント先が未初期化状態であれば、read()によってバッファオーバーフローが引き起こされる可能性があります。

バイト数を指定せず、バッファに溜まっているデータをまとめて取得したいと思い、strlen関数を使用しておりました。
(sizeof関数で指定すると配列の大きさ分を取得するまでread関数で待機状態になってしまうと勘違いしておりました。)
伊澤様のご指摘通りstrlenの使用を止め、sizeof関数でのバイト数指定に変更したところ、rc.localの記述によるプログラム実行を行うことができました。

ご返信ありがとうございました。