LoginSignup
8
3

More than 5 years have passed since last update.

ライブラリで使うスタックサイズなんて決められないよ!って話

Last updated at Posted at 2018-07-14

ライブラリで使用するスタックサイズを求めた結果、常にこれ!ってのは決められないなってなったので記事にしました。

スタックサイズを絞りたい

スタック領域とは?

メモリ領域にはヒープ領域とスタック領域という2種類の領域があり、スタック領域の方は例えば以下の情報が詰められます。(全てかは未確認。)

  • 実行される関数の実体・引数
  • 関数・メインで使用されるローカル変数

これらは確保できないとプログラムが動作しなくなるため、しっかりサイズを確保しないといけない大事なものになります。

たまにメモリ破壊系の不具合で見かけるスタックオーバーフローはこのスタックサイズ以上のスタックを利用としているからですね。

以下サイトがわかりやすいです。
https://www.uquest.co.jp/embedded/learning/lecture07-2.html

なぜサイズを絞りたいのか?

上記のように大事な大事なスタックサイズです。さてLinuxでthreadを立ち上げた場合はどのくらい必要なんでしょう。
以前にスタックサイズとか気にせずにメモリの変化を確認したところ、1 threadで8MByte消費していました。
環境はUbuntu18.04 64bitです。man page曰く32bitでもデフォルトは2MByte。スレッド立ち上げでガリガリメモリが削られるのが目に見えます。

今回サイズを求めたいのは私が作ってるthreadpoolのライブラリなんですが、デフォルトのままでは組み込み分野では使えない。Linux Cなのに組み込みでは使えないなんて面白くない(というかメインターゲットが組み込み系です)。
なのでいい感じに設定してあげないと。
というわけでサイズを絞ろうというのが目的でした。

使用スタックサイズを求める

その1: gccの機能を活用する。

記事作成後にいただいた@tenmyo さんのコメントを試してみました。やり方は簡単。gccのオプションに-fstack-usageを付けるだけ。
するとプログラムを実行すれば、各コードの配下に.suというファイルが出力され、関数の使用スタックサイズが表示されます。
こんな感じに。関数と使用スタックが表示されます。

$ cat threadpool/lib/event_thread.su
event_thread.c:430:13:event_tpool_thread_call_msgs      208     dynamic,bounded
event_thread.c:445:13:event_tpool_thread_cb     48      dynamic,bounded
event_thread.c:424:13:event_tpool_thread_msg_cb_stop    8       static
event_thread.c:289:13:event_subscriber_data_free.isra.2 16      static
event_thread.c:411:13:event_tpool_thread_msg_cb_del     32      static
event_thread.c:397:13:event_tpool_thread_msg_cb_update  64      dynamic,bounded
event_thread.c:383:13:event_tpool_thread_msg_cb_add     80      dynamic,bounded
event_thread.c:164:13:event_thread_msg_send.isra.7      224     dynamic,bounded
event_thread.c:190:13:event_thread_msg_send_subscribe   64      static
event_thread.c:360:15:event_tpool_thread_main   48      dynamic,bounded
event_thread.c:461:18:event_tpool_thread_new    80      dynamic,bounded
event_thread.c:493:6:event_tpool_thread_start   96      static
event_thread.c:509:6:event_tpool_thread_stop    48      dynamic,bounded
event_thread.c:525:6:event_tpool_thread_add     48      dynamic,bounded
event_thread.c:528:6:event_tpool_thread_update  48      dynamic,bounded
event_thread.c:533:6:event_tpool_thread_del     48      dynamic,bounded
event_thread.c:536:6:event_thread_atfork_child  16      static
event_thread.c:543:6:event_thread_set_stack_size        8       static

トータルがサイズがわかるような簡易スクリプトも作ってみました。

LIST=`find $1 -name *.su | xargs cat | awk -F" " '{print $2}'`
data=0
for value in $LIST; do data=`expr $data + $value`; done
echo "Stack size under $1: $data byte"

出力はこんな感じ。

$ ./threadpool/test.sh threadpool
Stack size under threadpool: 141328 byte

オプションで1発とは凄いですねgcc!

その2: threadのスタックアドレスを設定する

こちらのサイトを参考に、最大スタックサイズを確保したアドレスを利用して、joinする時に結果を表示するようにしました。
http://oswald.hatenablog.com/entry/20101205/1291518843

私の場合は求めたいのがthreadpoolライブラリのスレッドで使用するスタックサイズなので、threadを管理するEventTPoolThreadクラスstack_adrを追加

event_thread.c
/*! thread information */
struct event_tpool_thread_t {
        EventSubscriberData head;/*!<list of subscriber*/
        EventSubscriberData tail;/*!<list of subscriber*/

        event_thread_msg_info_t msgdata;/*<! message queue*/
        int eventfd;/*!<eventfd to use add message*/
        EventInstance event_base;/*<! base event*/
        EventHandler msg_evinfo;/*<! msg event info*/
        pthread_t tid;/*<! thread id*/
        int is_stop;/*<! stop flag*/
#ifdef CHECK_STACKSIZE
        char * stack_adr;
#endif
};

pthread_create時に初期化マジックナンバーで初期化した上でpthread_attr_setstackで設定

event_thread.c
/** start thread */
void event_tpool_thread_start(EventTPoolThread this) {
        this->tid=0;
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setstacksize(&attr, EVENT_THREAD_STACKSIZE);

#ifdef CHECK_STACKSIZE
        this->stack_adr = (char *) malloc(EVENT_THREAD_STACKSIZE);
        memset(this->stack_adr, MAGIC_NUMBER, EVENT_THREAD_STACKSIZE);
        pthread_attr_setstack(&attr, (void *) this->stack_adr, EVENT_THREAD_STACKSIZE);
#endif
        pthread_create(&this->tid, &attr, event_tpool_thread_main, this);
        pthread_attr_destroy(&attr);
}

Join後に結果を表示。スタックで利用されたアドレスがわかるという寸法です。

event_thread.c
/** stop thread */
void event_tpool_thread_stop(EventTPoolThread this) {
        pthread_t tid = this->tid;
        int ret = event_thread_msg_send_stop(this);
        if(0<ret) {
                pthread_join(tid, NULL);
        }
#ifdef CHECK_STACKSIZE
        int i=0;
        for(i=0;i<EVENT_THREAD_STACKSIZE;i++) {
                if (this->stack_adr[i] != MAGIC_NUMBER) break;
        }
        fprintf(stderr, "Used %d byte\n", EVENT_THREAD_STACKSIZE - i);
#endif
}

結果

テストコードを実行してみたところ、テストケース1では16264 byte、テストケース2では21384byteと、動作ルート毎にサイズが変わっています。

なんでかなと思いつつサイズを絞ってlighttpdにぶち込んだところスタックオーバーフロー発生。原因は私がいじったスタックを気にしないcgiの実装のせいでした。

ここでふと考える。なんでライブラリ内の関数と関係ない所のスタックサイズが影響するのか?
答えは簡単です。関数ポインタを使ってライブラリに関数登録・実行してるから

自分でも実行される関数でスタックを使うとかいてるじゃないか、気付きなさいよ!
スタックは関数実行時に積まれるので、関数ポインタだろうが実体だろうが同じことですよね。

結論・関数ポインタを利用するライブラリ、スタックサイズなんて決められない!

あくまでそのライブラリ使用ケースも合わせてスタックサイズ計算ならできますが、このケースではライブラリ単品でのスタックサイズ決定なんてできません。だって関数実体はライブラリ使用者に用意してもらうんですから。

というわけで、サイズが決められないなら設定できるようにしよう。pthread仕様のように。という結論に落ち着きました。
計測してよかった(笑)

参考

スタックについてわかりやすい解説をしてくれているサイト。その他下回りにも近い知識をわかりやすく説明してくれています。
スタックってなあに?(2)

関数ポインタだろうがスタック使うことに気付かせてくれたサイト
「関数ポインタ」と「関数テーブル」

スタックサイズを求める方法の参考
関数のスタック使用量を計算する方法

8
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3