[powerpc]oprofileの実装を追う

2012/07/25Linux::powerpcimport
oprofileがなぜ存在するのか。
ftraceとは何が異なるのかを確認するため、実装を追いかけた。
アーキテクチャ依存する部位も含むため、大枠はこれで理解するといいかもしれない。
QorIQをターゲットとして捜査したので、ほかのpower architectureは各自で追いかけていただきたい。

参考:
http://ssvb.github.com/2011/08/23/yet-another-oprofile-tutorial.html

参照したファイル類

linux-2.6/drivers/oprofile/oprof.c
linux-2.6/drivers/oprofile/oprofile_stats.c
linux-2.6/drivers/oprofile/oprofile_files.c
linux-2.6/drivers/oprofile/oprofile_stats.c
linux-2.6/drivers/oprofile/event_buffer.c
linux-2.6/drivers/oprofile/buffer_sync.c
linux-2.6/drivers/oprofile/cpu_buffer.c
linux-2.6/include/linux/ring_buffer.h
linux-2.6/kernel/trace/ring_buffer_benchmark.c
linux-2.6/kernel/trace/ring_buffer.c
linux-2.6/arch/powerpc/oprofile/common.c
linux-2.6/arch/powerpc/kernel/head_booke.h
linux-2.6/arch/powerpc/kernel/head_fsl_booke.S
linux-2.6/arch/powerpc/kernel/cpu_setup_fsl_booke.S
linux-2.6/arch/powerpc/kernel/traps.c
linux-2.6/arch/powerpc/kernel/pmc.c

platform initialization

linux-2.6/arch/powerpc/kernel/cpu_setup_fsl_booke.S

_GLOBAL(__setup_cpu_e500v1)
_GLOBAL(__setup_cpu_e500v2)
	mflr	r4
	bl	__e500_icache_setup
	bl	__e500_dcache_setup
	bl	__setup_e500_ivors
	mtlr	r4
	blr

FILE: linux-2.6/arch/powerpc/kernel/cpu_setup_fsl_booke.S

"PerformanceMonitor"へ飛ぶようにセットしてる。
/* Adjust or setup IVORs for e500v1/v2 */
_GLOBAL(__setup_e500_ivors)
	li	r3,DebugCrit@l
	mtspr	SPRN_IVOR15,r3
	li	r3,SPEUnavailable@l
	mtspr	SPRN_IVOR32,r3
	li	r3,SPEFloatingPointData@l
	mtspr	SPRN_IVOR33,r3
	li	r3,SPEFloatingPointRound@l
	mtspr	SPRN_IVOR34,r3
	li	r3,PerformanceMonitor@l
	mtspr	SPRN_IVOR35,r3
	sync
	blr
	/* Performance Monitor */
	EXCEPTION(0x2060, PerformanceMonitor, performance_monitor_exception, EXC_XFER_STD)

linux-2.6/arch/powerpc/kernel/traps.c

PerformanceMonitor()を用意、
void performance_monitor_exception(struct pt_regs *regs)
{
	perf_irq(regs);
}

linux-2.6/arch/powerpc/kernel/pmc.c

perf_irq() が定義されている。以下の関数で、ユーザ定義?の関数に差し替えることができる。
oprofile driverで差し替えとる。
int reserve_pmc_hardware(perf_irq_t new_perf_irq)

linux-2.6/arch/powerpc/oprofile/common.c

static void op_handle_interrupt(struct pt_regs *regs)
{
	model->handle_interrupt(regs, ctr);
}
初期化処理で、ハンドルを奪う。
static int op_powerpc_setup(void)
{
	/* Grab the hardware */
	err = reserve_pmc_hardware(op_handle_interrupt);
driver登録時点で、以下の初期化が行われている。
int __init oprofile_arch_init(struct oprofile_operations *ops)

		case PPC_OPROFILE_FSL_EMB:
			model = &op_model_fsl_emb;


linux-2.6/arch/powerpc/oprofile/op_model_fsl_emb.c

static void fsl_emb_handle_interrupt(struct pt_regs *regs,
				    struct op_counter_config *ctr)
oprofile_add_ext_sample()でイベント登録。
イベントがなければぬける。
うっひょ-

linux-2.6/drivers/oprofile/cpu_buffer.c

カウントはこちらで行う。
static inline void
__oprofile_add_ext_sample(unsigned long pc, struct pt_regs * const regs,
			  unsigned long event, int is_kernel)
{
	struct oprofile_cpu_buffer *cpu_buf = &__get_cpu_var(op_cpu_buffer);
	unsigned long backtrace = oprofile_backtrace_depth;

	/*
	 * if log_sample() fail we can't backtrace since we lost the
	 * source of this event
	 */
	if (!log_sample(cpu_buf, pc, backtrace, is_kernel, event))
		/* failed */
		return;

	if (!backtrace)
		return;

	oprofile_begin_trace(cpu_buf);
	oprofile_ops.backtrace(regs, backtrace);
	oprofile_end_trace(cpu_buf);
}

void oprofile_add_ext_sample(unsigned long pc, struct pt_regs * const regs,
			     unsigned long event, int is_kernel)
{
	__oprofile_add_ext_sample(pc, regs, event, is_kernel);
}

集計は?

ユーザ空間でoprofiledとかctrlが間接的に呼び出す。
上記kernel空間で取得したPC/backtraceデータを用いて、どの区間を何ステップ走行したか、で
実行時間を推定している。
集計結果だけを見ているようでは、厳密な測定結果とはいえないだろう。
ざっくりとみる分には、それでもいいかもしれないですね。
profile取得のオーバヘッドと、サンプリング周期と測定対象の解像度とを十分に考慮してくださいね。

CodeSourcery G++ LITEのsource compile

2012/07/24build_systemimport

cross compile環境の構築

昔は h8のcross環境をへろへろ~っと作っていたけれど、久しぶり。
野良よりも、toolchainを公開されているものを流用してくることにします。
CodeSourcery G++ LITEですが、mentorの組み込みソフト部門として買収されたようです。

Host OSは Virtual machine上のCentOS5.7だったと思う.. 小数点部分が怪しい.
$ uname -a
Linux localhost.localdomain 2.6.18-274.el5 #1 SMP Fri Jul 22 04:49:12 EDT 2011 i686 i686 i386 GNU/Linux

$ gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-52)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

targetとか準備

ざっと作業メモを貼っておく。将来の自分用メモ的な意味で...
$ mkdir -p /tmp/csl_scratch/local_tools/bin
 ln -sf /usr/bin/gcc i686-pc-linux-gnu-gcc
 ln -sf /usr/bin/g++ i686-pc-linux-gnu-g++
 ln -sf /usr/bin/ar i686-pc-linux-gnu-ar
 ln -sf /usr/bin/ranlib i686-pc-linux-gnu-ranlib
 ln -sf /usr/bin/strip i686-pc-linux-gnu-strip

$ export OSS_DIR=/path/to/OSS
 ※ここは各自sourceを拾ってきたpathを設定。環境変わっても流用できるように.. 少しくらいは.

$ mkdir -p /tmp/csl_scratch/
$ cd /tmp/csl_scratch/
$ tar xf $OSS_DIR/CodeSourcery/freescale-2010.09-55-powerpc-linux-gnu.src.tar.bz2 

$ mkdir -p /tmp/csl_scratch/gnu-lite-release/src
$ cd /tmp/csl_scratch/gnu-lite-release/src
$ for f in $(ls /tmp/csl_scratch/freescale-2010.09-55-powerpc-linux-gnu/*.bz2); do tar xf $f; done

HOST環境を用意.

付属のshell scriptを見ると、GCC 4.3.3が無難そう.
置換で済む変更は sedでざっくり書き換え. テキトウに手動補正.
$ cp -a freescale-2010.09-55-powerpc-linux-gnu/freescale-2010.09-55-powerpc-linux-gnu.sh build.sh

$ sed -e 's,/scratch/froydnj,/tmp/csl_scratch,g' \
   -e 's,pushenvvar PATH /usr/local/tools/gcc-4.3.3/bin,pushenvvar PATH /tmp/csl_scratch/local_tools/bin:\$PATH,' \
   -e 's,pushenvvar LD_LIBRARY_PATH,#pushenvvar LD_LIBRARY_PATH,' \
   -e 's,rmdir ./lib/rh73,# rmdir ./lib/rh73,' \
   -e 's,/rh73/,/,' \
   -e 's/ -j4/ -j8/g' \
   -e 's,-mrh73,,' \
   -e 's,/usr/local/tools/gcc-4.3.3/bin/,/tmp/csl_scratch/local_tools/bin/,' \
   -i build.sh

CSFLを見ながら構築していこうと思ったけれど頓挫した.
ので, そのタイミングで拾っておいた m4,texinfoをHostに入れてしまう.
既存環境を壊さないように、これも/tmpに放り込んでみた.
永続して使うなら, 適当なuser directoryにでも入れておくと良いだろう.
ここに書いてないけれど, hostのgcc, binutils, kernel headers, flex, bisonくらいは必要.
厳密にどれだけ必要なのかは... 調べきる元気が無かったので, 既存環境に無いものをだましだまし追加.
tar xf $OSS_DIR/CLFS_PKG/m4-1.4.16.tar.bz2 

./configure --prefix=/tmp/csl_scratch/local_tools
make
make install
tar xf $OSS_DIR//CLFS_PKG/texinfo-4.13a.tar.gz 

./configure --prefix=/tmp/csl_scratch/local_tools
make
make install


カットアンドトライ

以下は残す. 2~35はコメントアウト*1
# task [001/258] 
# task [036/258] /i686-pc-linux-gnu/host_cleanup

ここでこける.
task [068/258] /i686-pc-linux-gnu/toolchain/binutils/postinstall
gnu-lte-release/obj/binutil... powerpc-linux-gnu-i686-pc-linux-gnu/libiberty/functions.texi
500行目あたり, 関数定義のマクロ部分に余計な改行が入っているので、改行をとってリトライ.
[148/258] /i686-pc-linux-gnu/toolchain/gdb/0/build
 /tmp/csl_scratch/gnu-lite-release/obj/gdb-src-2010.09-55-powerpc-linux-gnu-i686-pc-linux-gnu/libiberty/functions.texi

task [147/258] /i686-pc-linux-gnu/toolchain/gdb/0/configure
ここからやり直すとよい.

以下のtaskで参照している, getting started guideのソースが無い..?
task [158/258] /i686-pc-linux-gnu/getting_started/copy
task [159/258] /i686-pc-linux-gnu/getting_started/configure
task [160/258] /i686-pc-linux-gnu/getting_started/build
task [161/258] /i686-pc-linux-gnu/getting_started/install
task [162/258] /i686-pc-linux-gnu/csl_docbook
以下は 置換漏れ. まさかフルパスで書いていたとはな...
task [172/258] /i686-pc-linux-gnu/strip_host_objects
./build.sh: line 57432: /usr/local/tools/gcc-4.3.3/bin/i686-pc-linux-gnu-strip: No such file or directory

targetのlibcにデバッグシンボルを残したいときは, 次のタスクをスキップすると良い.
 task [173/258] /i686-pc-linux-gnu/strip_target_objects

installer?これも無かったと思う.
このディレクトリ"gnu-lite-release/src/scripts-trunk/"が無い.
パッケージを作るためのスクリプトが入っているようだけれど、source archiveには入ってなさそう.
task [175/258] /i686-pc-linux-gnu/package_installanywhere/unpack
task [176/258] /i686-pc-linux-gnu/package_installanywhere/merge_modules
task [177/258] /i686-pc-linux-gnu/package_installanywhere/installer
task [178/258] /i686-pc-linux-gnu/package_rpm

*1 : if false; then .... fi で切ってしまう

まとめ

たぶん host側も CSLのtoolchainを持っていれば良かったのだろう.
これに限らず host環境の依存性も重要であると再認識できた.

sirq/hoghogeってなに?

2012/07/05Linux::taskimport

"sirq-XXX"とは?

コレ参考になる。筑波大学の講義か。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2011/2012-02-14/

要約すると??

優先度の高いtaskで、driverのbottom halfやprotocol stackの処理を行う。
kernel threadで、SMPならcoreごとに名前が異なる。("ksoftirqd/0","ksoftirqd/1"とか)

ソースコードを追う

FILE: kernel/softirq.c

static const char *softirq_names [] =
{
  [HI_SOFTIRQ]		= "high",
  [SCHED_SOFTIRQ]	= "sched",
  [TIMER_SOFTIRQ]	= "timer",
  [NET_TX_SOFTIRQ]	= "net-tx",
  [NET_RX_SOFTIRQ]	= "net-rx",
  [BLOCK_SOFTIRQ]	= "block",
  [BLOCK_IOPOLL_SOFTIRQ]= "block-iopoll",
  [TASKLET_SOFTIRQ]	= "tasklet",
  [HRTIMER_SOFTIRQ]	= "hrtimer",
  [RCU_SOFTIRQ]		= "rcu",
};


static struct notifier_block __cpuinitdata cpu_nfb = {
	.notifier_call = cpu_callback
};

static __init int spawn_ksoftirqd(void)
{
	void *cpu = (void *)(long)smp_processor_id();
	int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);

	BUG_ON(err == NOTIFY_BAD);
	cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
	register_cpu_notifier(&cpu_nfb);
	return 0;
}
early_initcall(spawn_ksoftirqd);
展開すると以下イメージで処理される。
cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);
		for (i = 0; i < NR_SOFTIRQS; i++) {
			per_cpu(ksoftirqd, hotcpu)[i].nr = i;
			per_cpu(ksoftirqd, hotcpu)[i].cpu = hotcpu;
			per_cpu(ksoftirqd, hotcpu)[i].tsk = NULL;
		}
		for (i = 0; i < NR_SOFTIRQS; i++) {
			p = kthread_create(run_ksoftirqd,
					   &per_cpu(ksoftirqd, hotcpu)[i],
					   "sirq-%s/%d", softirq_names[i],
					   hotcpu);
			if (IS_ERR(p)) {
				printk("ksoftirqd %d for %i failed\n", i,
				       hotcpu);
				return NOTIFY_BAD;
			}
			kthread_bind(p, hotcpu);
			per_cpu(ksoftirqd, hotcpu)[i].tsk = p;
		}
cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
		for (i = 0; i < NR_SOFTIRQS; i++) {
			param.sched_priority = MAX_RT_PRIO-1;
			p = per_cpu(ksoftirqd, hotcpu)[i].tsk;
			sched_setscheduler(p, SCHED_FIFO, &param);
			per_cpu(ksoftirqd, hotcpu)[i].tsk = NULL;
			kthread_stop(p);
		}
		takeover_tasklets(hotcpu);

[C]forkで子供を作る時の注意

2012/06/19Programming::Cimport

forkの動作

fork()を実行するプロセスのコピーを作る、とあります。
man pageの版によっては、glibcの特定バージョンから、clone()による実装に変わった*1とある。
そこには、ファイルデスクリプタもコピーされます。とあるではありませんか!。
ただし、0,1,2は一端クローズしてから開き直します、とありました。
これちょっとハマった気がするので、実際に検証してみましょう。
platformが異なりますが、とりあえずPC-Linux(VMですが)で検証です。

glibc 2.3.3 以降では、 NPTL スレッド実装の一部として提供されている glibc のfork() ラッパー関数は、 カーネルの fork() システムコールを起動するのではなく、clone(2) を起動する。 clone(2) に渡すフラグとして、伝統的な fork() システムコールと同じ効果が得られるようなフラグが指定される (fork() の呼び出しは、 flags に SIGCHLD だけを指定して clone(2) を呼び出すのと等価である)。 glibc のラッパー関数は pthread_atfork(3) を使って設定されている任意の fork ハンドラを起動する。
via http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/fork.2.html

*1 : 残念ながら、Cent-OS 5.7 patch非適用状態では、その文言がない。別途確認が必要→man page

確認コード例

少々長いですが、コードを貼り付けます。
エイヤっと作っているので、エラー処理とバッファ量などもテキトウです。

検証GCC version

gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-51)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

fork_exec.c

#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>


#define DEBUG_PRINT(fmt, ...)  \
        printf("%s(): "fmt, __func__, ## __VA_ARGS__)



const char* pExec_PG = "./infty.sh" ;

int proc_exec(char* argv[])
{
char* arg_nul[] = {NULL,} ;
int rc;

		DEBUG_PRINT( "%s\n", pExec_PG );
	if (argv == NULL) {
		rc = execvp( pExec_PG, arg_nul );
	}
	else {
		rc = execvp( pExec_PG, argv );
	}
return rc ;
}


int proc_dup(int* pid_child, char* argv[])
{
pid_t id;

	id = fork();
	if (id==-1) {	/* parent - error */
		DEBUG_PRINT( "%s", strerror(errno) );
	}
	else if( id==0 ) {	/* child - ok */
		DEBUG_PRINT( "created pid=%d, let's execv!", (int)getpid() );
		proc_exec( argv );
		// ここにはこないはず.
		DEBUG_PRINT( "Oops... Where is here ?" );
	}
	else {		/* parent - get child process */
		DEBUG_PRINT( "created success.\n");
	}
	if(pid_child)
		*pid_child = id ;
return id;
}

int main ( int argc, char*argv[])
{
int id1, id2 ;
int fd;
char buf[512];

	DEBUG_PRINT( "I am %d.\n", (int)getpid() );
	proc_dup(&id1,NULL);
	printf("create %d\n", id1);
	system("ps");
	sleep(5);

	fd = open("test", O_CREAT);
	if(fd<0) {
		printf("file open error");
	}

	proc_dup(&id2,NULL);
	system("ps");
	sleep(5);
	printf("create %d\n", id2);


	sprintf(buf, "lsof -c %d", (int)getpid());
	system(buf);

	sprintf(buf, "lsof -c %d", id1);
	system(buf);

	sprintf(buf, "lsof -c %d", id2);
	system(buf);

	close(fd);
	printf(" **** After close(fd) ****\n");
	sprintf(buf, "lsof -c %d", (int)getpid());
	system(buf);

	sprintf(buf, "lsof -c %d", id1);
	system(buf);

	sprintf(buf, "lsof -c %d", id2);
	system(buf);

	printf("run  kill %d %d  \n", id1, id2);
	sleep(60);
return 0;
}

infty.sh

#!/bin/sh
while true;
do
  lsof -p $$
  sleep 10;
done

実行結果

lsofコマンドを使っているので、特権ユーザで実行しましょう。
一般ユーザが使えるようなら、それでも構いません(自プロセスだけだから大丈夫かな?)
# ./fork_exe 
main(): I am 7630.
proc_dup(): created pid=7631, let's execv!proc_exec(): ./infty.sh
proc_dup(): created success.
create 7631
  PID TTY          TIME CMD
 7371 pts/0    00:00:00 su
 7374 pts/0    00:00:00 bash
 7630 pts/0    00:00:00 fork_exe
 7631 pts/0    00:00:00 infty.sh
 7632 pts/0    00:00:00 ps
 7633 pts/0    00:00:00 lsof
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7631 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7631 root  rtd    DIR    8,3     4096        2 /
infty.sh 7631 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7631 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7631 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7631 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7631 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7631 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7631 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7631 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
proc_dup(): created pid=7636, let's execv!proc_exec(): ./infty.sh
proc_dup(): created success.
  PID TTY          TIME CMD
 7371 pts/0    00:00:00 su
 7374 pts/0    00:00:00 bash
 7630 pts/0    00:00:00 fork_exe
 7631 pts/0    00:00:00 infty.sh
 7635 pts/0    00:00:00 sleep
 7636 pts/0    00:00:00 infty.sh
 7637 pts/0    00:00:00 ps
 7638 pts/0    00:00:00 lsof
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7636 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7636 root  rtd    DIR    8,3     4096        2 /
infty.sh 7636 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7636 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7636 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7636 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7636 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7636 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7636 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7636 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    3r   REG    8,3        0  3110063 /home/yyy/tst_fork/test
infty.sh 7636 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
create 7636
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7631 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7631 root  rtd    DIR    8,3     4096        2 /
infty.sh 7631 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7631 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7631 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7631 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7631 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7631 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7631 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7631 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
 **** After close(fd) ****
run  kill 7631 7636  
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7636 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7636 root  rtd    DIR    8,3     4096        2 /
infty.sh 7636 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7636 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7636 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7636 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7636 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7636 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7636 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7636 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    3r   REG    8,3        0  3110063 /home/yyy/tst_fork/test
infty.sh 7636 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7631 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7631 root  rtd    DIR    8,3     4096        2 /
infty.sh 7631 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7631 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7631 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7631 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7631 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7631 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7631 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7631 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7636 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7636 root  rtd    DIR    8,3     4096        2 /
infty.sh 7636 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7636 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7636 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7636 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7636 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7636 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7636 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7636 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    3r   REG    8,3        0  3110063 /home/yyy/tst_fork/test
infty.sh 7636 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7631 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7631 root  rtd    DIR    8,3     4096        2 /
infty.sh 7631 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7631 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7631 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7631 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7631 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7631 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7631 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7631 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7631 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
COMMAND   PID USER   FD   TYPE DEVICE     SIZE     NODE NAME
infty.sh 7636 root  cwd    DIR    8,3     4096  3110061 /home/yyy/tst_fork
infty.sh 7636 root  rtd    DIR    8,3     4096        2 /
infty.sh 7636 root  txt    REG    8,3   735804 13323587 /bin/bash
infty.sh 7636 root  mem    REG    8,3   129900  7791196 /lib/ld-2.5.so
infty.sh 7636 root  mem    REG    8,3  1693812  7791364 /lib/libc-2.5.so
infty.sh 7636 root  mem    REG    8,3    20668  7791420 /lib/libdl-2.5.so
infty.sh 7636 root  mem    REG    8,3    13084  7792317 /lib/libtermcap.so.2.0.8
infty.sh 7636 root  mem    REG    8,3    25462  8872255 /usr/lib/gconv/gconv-modules.cache
infty.sh 7636 root  mem    REG    8,3 56454576  8749247 /usr/lib/locale/locale-archive
infty.sh 7636 root    0u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    1u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    2u   CHR  136,0                 2 /dev/pts/0
infty.sh 7636 root    3r   REG    8,3        0  3110063 /home/yyy/tst_fork/test
infty.sh 7636 root  255r   REG    8,3       55  3110065 /home/yyy/tst_fork/infty.sh
'test'ファイルを、子プロセスが握っていることが見て取れます。
比較用に、open - close前後の状態を見せています。

対策

man 2 openによると
デフォルトでは、新しいファイルディスクリプタは execve(2) を実行した後もオープンされたままとなる (つまり、 fcntl(2) に説明がある FD_CLOEXEC ファイルディスクリプタフラグは最初は無効である; 後述の O_CLOEXEC フラグを使うとこのデフォルトを変更することができる)。
とあり、
O_CLOEXEC (Linux 2.6.23 以降)
新しいファイルディスクリプタに対して close-on-exec フラグを有効にする。 このフラグを指定することで、プログラムは FD_CLOEXEC フラグをセットするための fcntl(2) F_SETFD 操作を別途呼び出す必要がなくなる。また、ある種のマルチスレッドのプログラムはこのフラグの使用は 不可欠である。なぜなら、個別に FD_CLOEXEC フラグを設定する fcntl(2) F_SETFD 操作を呼び出したとしても、あるスレッドがファイルディスクリプタを オープンするのと同時に別のスレッドが fork(2) と execve(2) を実行するという競合条件を避けるのには十分ではないからである。
と、コレを使えというふうに読めますね。
これって、システム全体で、デフォルトで有効にしたい、とか、できないものなのか・・・。

関連事項

mutexなんかも気をつけないといけない話。
memologue