概要
uClibc
は組み込み Linux 開発に従事している方はご存知だと思いますが、リソース等制限されたハードウェアの Linux 環境に最適な標準 C ライブラリです。通常の PC Linux では glibc
等のライブラリが用いられますが、いろいろ気にする環境ではこういった軽量ライブラリが使用されていますね。
さて、マルチスレッドとマルチプロセスは混ぜると危険な匂いがするので警戒すると思います。しかしながら、油断して単純にマルチスレッドな実装に対してデーモンを作ろうとすると、罠にハマってしまいます。
以下でご説明するケースは uClibc
に見られるもので、前述した PC Linux 上ではおそらく見られませんのでご了承ください。また、古いバージョンだけかもしれません。
現象
以下のコードをご覧ください:
#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
して終了します。
出力結果は想像通り、以下のような感じになります:
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 してもらいます。
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;
}
するとどうでしょう出力は以下のようになって、終了しなくなりました:
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()
は libpthread
の fork()
ではなく、内部シンボルの fork()
を呼び出してしまうために pthread
のスレッドマネージャーが fork()
後のメインスレッドの PID を失ってしまうことによって起こるようです ( そんな風になっているんですね )。
- http://lists.uclibc.org/pipermail/uclibc/2007-December/039647.html
- http://lists.busybox.net/pipermail/uclibc/2009-July/042805.html
- http://toolchain-devel.blackfin.uclinux.narkive.com/jV2uDpR8/uclibc-nommu-daemon-does-not-work-with-pthreads
これの解決策として提示されているのは、pthread
の fork()
を使うように、自前で同様な daemon()
処理を実装することです:
https://dev.openwrt.org/browser/trunk/package/fuse/patches/300-workaround-uclibc-pthread-breakage.patch?rev=13312
解決策を取り入れた全体
#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;
}
どうやら、これで動きました。めでたしです。