LoginSignup
8
9

More than 5 years have passed since last update.

組み込みLinuxではまるuClibcのdaemonとpthread_create

Last updated at Posted at 2015-06-26

概要

uClibc は組み込み Linux 開発に従事している方はご存知だと思いますが、リソース等制限されたハードウェアの Linux 環境に最適な標準 C ライブラリです。通常の PC Linux では glibc 等のライブラリが用いられますが、いろいろ気にする環境ではこういった軽量ライブラリが使用されていますね。

さて、マルチスレッドとマルチプロセスは混ぜると危険な匂いがするので警戒すると思います。しかしながら、油断して単純にマルチスレッドな実装に対してデーモンを作ろうとすると、罠にハマってしまいます。

以下でご説明するケースは uClibc に見られるもので、前述した PC Linux 上ではおそらく見られませんのでご了承ください。また、古いバージョンだけかもしれません。

現象

以下のコードをご覧ください:

thread_test.c
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>


void* do_something( void *object )
{
    size_t i;

    for ( i = 0; i < 5; ++i )
    {
        printf(
            "Hello, this is another thread! "
            "( times = %zd, tid = %p )\n",

            i,
            ( void * ) pthread_self()
        );
        sleep( 2 );
    }

    return NULL;
}


int main()
{
    size_t    i;
    pthread_t thread;

    printf( "New thread will be created. ( tid = %p )\n", ( void * ) pthread_self() );
    pthread_create( &thread, NULL, do_something, NULL );

    for ( i = 0; i < 10; ++i )
    {
        printf( "The old thread keeps going on... ( tid = %p )\n", ( void * ) pthread_self() );
        sleep( 1 );
    }

    pthread_join( thread, NULL );
    printf( "threads are joined.\n" );

    return 0;
}

このコードは、メインスレッドで 10 回 メッセージを表示しながら、do_something 関数を別スレッドで実行し、5 回メッセージを表示する、マルチスレッドのよくある例です。最終的に 2 つのスレッドは pthread_join して終了します。

出力結果は想像通り、以下のような感じになります:

stdout
New thread will be created. ( tid = 0x1111 )
The old thread keeps going on... ( tid = 0x1111 )          
Hello, this is another thread! ( times = 0, tid = 0x1234 )
The old thread keeps going on... ( tid = 0x1111 )
The old thread keeps going on... ( tid = 0x1111 )
Hello, this is another thread! ( times = 1, tid = 0x1234 )
The old thread keeps going on... ( tid = 0x1111 )
Hello, this is another thread! ( times = 2, tid = 0x1234 )
The old thread keeps going on... ( tid = 0x1111 )
The old thread keeps going on... ( tid = 0x1111 )
Hello, this is another thread! ( times = 3, tid = 0x1234 )
The old thread keeps going on... ( tid = 0x1111 )
The old thread keeps going on... ( tid = 0x1111 )
Hello, this is another thread! ( times = 4, tid = 0x1234 )
The old thread keeps going on... ( tid = 0x1111 )
The old thread keeps going on... ( tid = 0x1111 )
threads are joined.

(プロセスが終了している)

ここで、このプロセスをバックグラウンドでデーモンとして実行したくなったので、しょっぱなで便利な daemon() ( http://linuxjm.osdn.jp/html/LDP_man-pages/man3/daemon.3.html ) を呼んでよしなに deamonize してもらいます。

daemonized_thread.c
int main()
{
    size_t    i;
    pthread_t thread;

    // process should be daemonized here!
    if ( daemon( 1, 1 ) )
    {
        fprintf( stderr, "daemonization failed." );
        exit( 1 );
    }

    printf( "New thread will be created. ( tid = %p )\n", ( void * ) pthread_self() );
    pthread_create( &thread, NULL, do_something, NULL );

    for ( i = 0; i < 10; ++i )
    {
        printf( "The old thread keeps going on... ( tid = %p )\n", ( void * ) pthread_self() );
        sleep( 1 );
    }

    pthread_join( thread, NULL );
    printf( "threads are joined.\n" );

    return 0;
}

するとどうでしょう出力は以下のようになって、終了しなくなりました:

stdout
New thread will be created. ( tid = 0x1111 )
Hello, this is another thread! ( times = 0, tid = 0x1234 )
Hello, this is another thread! ( times = 1, tid = 0x1234 )
Hello, this is another thread! ( times = 2, tid = 0x1234 )
Hello, this is another thread! ( times = 3, tid = 0x1234 )
Hello, this is another thread! ( times = 4, tid = 0x1234 )
(プロセスは終了しない)

どうやらメインスレッドが pthread_create() あたりでロックしてしまって動かなくなりました。

何が起こっているの

"uClibc pthread daemon" で検索すると、結構問題になっているようでフォーラムやメーリングリストの情報が出てきます:

以下のメーリングリストの情報によると、問題の原因は daemon() 内で呼ばれる fork()libpthreadfork() ではなく、内部シンボルの fork() を呼び出してしまうために pthread のスレッドマネージャーが fork() 後のメインスレッドの PID を失ってしまうことによって起こるようです ( そんな風になっているんですね )。

これの解決策として提示されているのは、pthreadfork() を使うように、自前で同様な daemon() 処理を実装することです:
https://dev.openwrt.org/browser/trunk/package/fuse/patches/300-workaround-uclibc-pthread-breakage.patch?rev=13312

解決策を取り入れた全体

daemonized_thread_fixed.c
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>


int my_daemon( int nochdir, int noclose )
{
    int res;
    int fd;

    switch ( res = fork() )
    {
        case -1:
            return 1;

        case 0:
            break;

        default:
            exit( 0 );
    }

    if ( ( res = setsid() ) == -1 )
    {
        return 1;
    }

    if ( !nochdir )
    {
        chdir( "/" );
    }

    if ( !noclose && ( fd = open( "/dev/null", O_RDWR, 0 ) ) != -1 )
    {
        dup2( fd, STDIN_FILENO );
        dup2( fd, STDOUT_FILENO );
        dup2( fd, STDERR_FILENO );

        if ( fd > 2 )
        {
            close( fd );
        }
    }

    return 0;
}


void* do_something( void *object )
{
    size_t i;

    for ( i = 0; i < 5; ++i )
    {
        printf(
            "Hello, this is another thread! "
            "( times = %zd, tid = %p )\n",

            i,
            ( void * ) pthread_self()
        );
        sleep( 2 );
    }

    return NULL;
}


int main()
{
    size_t    i;
    pthread_t thread;

    if ( my_daemon( 1, 1 ) )
    {
        fprintf( stderr, "daemonization failed." );
        exit( 1 );
    }

    printf( "New thread will be created. ( tid = %p )\n", ( void * ) pthread_self() );
    pthread_create( &thread, NULL, do_something, NULL );

    for ( i = 0; i < 10; ++i )
    {
        printf( "The old thread keeps going on... ( tid = %p )\n", ( void * ) pthread_self() );
        sleep( 1 );
    }

    pthread_join( thread, NULL );
    printf( "threads are joined.\n" );

    return 0;
}

どうやら、これで動きました。めでたしです。

8
9
0

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
9