Path: sran265!katsu From: katsu@sran14.sra.co.jp (WATANABE katsuhiro) Message-ID: Date: 15 Sep 92 18:41:00 Organization: Software Research Associates, Inc.,Japan Distribution: In-reply-to: saitoh@icsts1.osaka-u.ac.jp's message of 7 Sep 92 04:51:54 GMT Newsgroups: fj.sys.news,fj.unix Followup-To: fj.unix Subject: Re: Q. about /etc/pstat References: <9215@icsts1.osaka-u.ac.jp> <9217@icsts1.osaka-u.ac.jp> (Followup-To: に注意)  フォローの遅さでは jp 内で十指に入る渡邊@SRAです。 記事 で image@cs.titech.ac.jp (Takashi Imaizumi) さんいはく > 東工大の今泉です。 > NEWS-OS 4.1で /etc/pstat -T を実行すると、textに関する出力が > 49/ 98 texts active, 98 used > などとなって数字が3つ出て来ます。この最後の数字の意味が分から > ないのですが、お教え願えないでしょうか?    結論をいうと、3つ目の used というのは、有効な vnode と関連づいている テキストテーブルエントリーの数です。 記事 <9217@icsts1.osaka-u.ac.jp> で saitoh@icsts1.osaka-u.ac.jp (SAITOH Akinori) さんいはく > どうやったら確かめられるんでしょうね??  pstat -T は、pstat -f, -i, -p, -x, -s 各々の先頭行に出るサマリーを 表示しているのと同じですから、pstat -T で何らかの疑義が生じたなら、 pstat -x などにたちかえって考えるべきです。  あるいは pstat -x が(上で説明した意味での)used な entry しか 表示しないのが困ることがあるかもしれませんが、そうした場合には /dev/kmem を直接のぞいてしまうというのも(データ構造がさほど 複雑でないのと、配列の大きさが数十から数百程度と小さいことから) 十分現実的な方策だと思います。  そもそも pstat って、カーネル仮想記憶空間上のアドレスが沢山出てきて、  「おおーい、頼むからみてくれえ、お願いだからのぞいてくれえ」 と手足じたばたさせて泣きわめき叫んでるみたいに見えませんか? :-)  この機会に、4.3BSD + NFS という典型的構成の unix(SUN OS は あてはまりません)での、テキストテーブルに関する活動や pstat -x の 動きについて、知っていることを書きます。主に NEWS OS 4.1C/R での 経験から帰納的に推論したものなので、もし誤りや機種依存の部分があれば 指摘してください。 (0)基礎知識  unix では、同一の new process file (cf. execve(2)) から起動された プロセスの間で text を共有することができる。共有されるテキストは struct text で表現され、これがテキストテーブルで管理されている。 process file の同一性は vnode の同一性で判定されるので、リンクを 複数もつファイルであってもうまくテキストが共有される。  共有されるのは magic number (cf. a.out(5), ld(1)) が NMAGIC, ZMAGIC の場合である。pagedaemon のようにテキストを持たないプロセスは、 テキスト共有の概念と無関係である。加えて、biod (や、NEWS-OS 4.1C/R では nfsd も)のように、テキストを持ちながらも定常時にユーザー空間で 実行される部分がないために (cf. nfssvc(2))、テキストテーブルに 登録されない(ps でみると SIZE やTSIZ がゼロだから、そもそも ユーザー空間が開放されている)ものもある。以下ではテキストを共有する 場合だけ考える。  テキストテーブルの大きさは固定で、カーネルの生成時に決まる。 ただし、最近の NEWS OS のようにブート時に決める実装もある。 参照しているプロセスがないエントリーは、フリーリスト(双方向リストで 実装されているが、これは要素の削除を容易にするためと思われる)として 連結されている (cf. )。逆に新たなテキストエントリーを 登録する時に(これは execve(2) の時だけである)テーブルがあふれれば、 "text table full" のメッセージがカーネルから出力される。 (1)テキストの登録  プロセスが起動したとき、new process file の vnode と同じ vnode を 参照しているエントリーがテキストテーブル内にあるか調べる。この際 フリーリスト内のエントリーについても調べる。どこにもなかった場合は、 フリーリストの先頭のエントリーの内容を捨て、新規に必要になった テキスト用として利用する。既にあった場合には、その参照カウント x_count(CNT) を増加して共有するが、もし見つかったのがフリーリスト内 なら、加えてそのエントリーはフリーリストから外される。つまり、 フリーリストはキャッシュとして働いていることになる。 (2)テキストの削除とフリーリストへの登録  プロセスが終了すると、対応するテキストエントリーの CNT が減らされる。 どのプロセスもこのエントリーを利用していない(CNT が 0)状態になると、 これをフリーリストの最後尾に接続する。これと(1)から、フリーリストの 先頭にはいわゆる Least Recent Used なエントリーがくるのが明らかである。  process file の sticky bit(cf. chmod(2))が立っている場合は、例外的に フリーリストに接続されない。  したがって、これに加えて process file の vnode が rnode と結び付いて いるのではない(つまり NFS マウント先にあるわけではない)場合には、 (よしんばテーブルが一杯になっても)内容が捨てられたり、他のテキスト用に 使い回されるようなことはない。  rnode と結び付いていると、(3)で述べるような理由から、sticky bit が 立っているにもかかわらず再利用されてしまうことが起きうる。 (3)フリーなエントリーと vnode との関係 [local case] フリーリスト中のエントリー(フリーなエントリー)"ft" が vnode を介して in core の inode "in" と関係しているとする。この状態では in は参照カウントが 0 でなく、inode のフリーリストにはつながれていない。  いま、(他のテキストに利用されるため)ft の内容が捨てられるとすると、 対応していた inode もどこからも参照されなくなる可能性があり、そうした 場合フリーリストにつながれることになる。逆に、in の存在するデバイスが unmount されたり in のリンクが全て unlink されるなどして in が有効で なくなった場合、ft の内容、特に x_vptr (VPTR) が無意味になるので、 ft は中身がクリアされ、キャッシュとして働くのをやめる。ここで「クリア」 とは、いいかえるとこれはすなわち、pstat -x や pstat -T のいう「used」の 状態ではなくなるわけである。 [NFS case] ft が vnode を介して rnode "rn" と関係していたとする。 rnode 一般に言えることとして、どこからも参照されていないものは カーネルによって適当な時期に回収されるが、inode の場合と違って フリーなテキストエントリーと関連している rnode/vnode も回収されて しまうようである。もし rn についてこうした回収が起きると、ft の中身は クリアされる。  実は rnode の回収は、その sticky bit が立っているかどうかなどとは 無関係に起きる。sticky bit が立っていた rnode と関係していた テキストエントリーは元々フリーリストには入っていなかったはずなので、 これに関する rnode の回収が起きた場合、そのテキストエントリーは クリアされた上でフリーリストへつなぎ直される。  上記のように vnode に関する活動のためにフリーなエントリーが クリアされて not used になる場合、もはやキャッシュとして機能しない ということで、一気にフリーリストの先頭にまわされて優先的に 使われるようになっている実装 (NEWS-OS 4.1C/R) も存在する (4)まとめ(pstat -x の見方に関する tips) ・pstat -x の active は、CNT が 0 でないエントリー数と同じである。 ・pstat -x で出てくるエントリーは、対応する有効な vnode があるもの  (VPTR すなわち x_vptr が 0 でないもの)だけであり、これの数が  結局 used の数にもなっている。 ・上と関連して、pstat -x で出てくる LOC の並びを見たとき、  「歯が抜けている」ようにアドレスが飛んでいる部分に not used な  エントリーが存在する。 ・CNT が 0 なのにフリーでないエントリーは sticky bit が影響している。 ・実装上 vnode は、inode や rnode 等の構造体の内部に含まれている  (cf. ,)。inode と rnode 等では  保持されている記憶域が離れているので、各々に対応する vnode も  アドレスが大きく異なって見える。これを知っていれば、VPTR の  フィールドを見たとき、それが inode か rnode なのか即座に判別できる。 ・フリーリスト中に not used なエントリーがあるとき、pstat -x の  FORW/BACK ポインターをたどることでは当然フリーリスト全体を  観察することはできない。 ・カーネル内でフリーリストの先頭は _xhead、末尾は _xtail という  変数が保持している。フリーリスト先頭の要素の BACK は NULL でなく  _xhead を指していることを覚えておけば、pstat -x においても簡単に  先頭を識別できる。末尾の要素の FORW は NULL である。  参考のために、テキストエントリーとコマンド名 (process file) を できるだけ関連づけて表示させる perl スクリプトと、テキストテーブルの フリーリストをたどる c プログラムを記事の終りにつけます。  なお、rnode の回収というカーネル活動が存在するということは、 歌代(utashiro@sra.co.jp)さんに教えていただきました。 -- 渡邊克宏@SRAソフトウェア工学研究所 -------- ちょきちょき -------- ちょきちょき -------- # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by sran14!katsu on Tue Sep 15 18:34:45 JST 1992 # Contents: README textf.c texts echo x - README sed 's/^@//' > "README" <<'@//E*O*F README//' マニュアルなくてごめんなさい。これらのプログラムは複写、改変、再配布などが 自由です。コメント等を歓迎します。--- 渡邊克宏 ・textf テキストテーブルのフリーリストを先頭から末尾に向かって たどるプログラム。/dev/kmem の read の権限がなければならない。 RISC NEWS では、コンパイル時に -lmld を指定してください。 ・texts pstat -x の各エントリーを、コマンド名 (process file) や ファイル名と対応づけるプログラム。 [texts の使い方要約] このプログラムで完全に対応つくわけではありません。目安、あるいは実験用 ぐらいに考えてください。沢山プロセスを起動するので遅いし、時間差から 矛盾した情報が出ることもあります。 動かすと、参照数 > 1 のエントリーはこんなふうに出ます。 (CADDR から、pstat -p, ps -c を使って PID, COMMAND 名を得る) LOC CADDR VPTR CNT CCNT PID COMMAND 80162470 8015494c 8013d1d0 1 1 1 init 参照数 > 1 なんだけど、コア内のプロセスからの参照数 = 0(つまりこのテキスト を参照している全てのプロセスがスワップアウトしている)だと、次のように コマンド名が [ ] で囲まれて出ます。 (LOC から pstat -p, ps -c を使って PID, COMMAND 名を得る) 80162668 0 8043390c 1 0 24419 [selserv] 参照数 = 0 で、キャッシュとして、あるいは sticky bit の影響で残っている ものは、コマンドファイルがあるデバイスの major dev number, minor dev number および、inode を組にして次のように出ます。 (VPTR から inode のアドレスを計算し、pstat -i を使って inode を得る) 80162b00 0 8013eac0 0 0 (0,5 12674) ちなみにこの機械で、major dev=0, minor dev=5 は何かというと、 brw------- 1 root staff 0, 5 Jul 12 20:24 /dev/sd00f ということになっていて、さらに df /dev/sd00f すると、 /dev/sd00f 54806 47839 4226 92% /usr だからマウントポイントが /usr ですが、find /usr -xdev -inum 12674 -ls などとしてみて 12674 64 -rwxr-xr-x 1 root staff 65536 Mar 17 12:39 /usr/lib/atrun を得るので、このエントリーは atrun に対応しているといえます。  また、 80163190 0 8043ad0c 0 0 () みたいに空の名前が出てきたら、参照数ゼロながらもキャッシュとして残っては いるんだけど、対応する vnode には inode でないものが対応づいていた (つまりは、多分 NFS マウント先にあるコマンドを実行した残骸)ために、 ふつーのユーザーにはコマンド名がわからなかったと考えてください。  あと、text に対応するファイルが dev no と inode の数字の組で出るのは わかりにくいという人は、あらかじめ /tmp/.inodetext という "(dev no, inode)" からファイル名への対応表を作っておけば、 80162b00 0 8013eac0 0 0 (atrun) のように inode からファイル名を引かせてこれを表示させることができます。 この対応表を作るには、-i スイッチに引き続いて、対応表を作っておきたい ディスクパーティションのマウントポイントを指定します。  例えば、一番最初に texts -i / /usr のようなことをやっておくとよいでしょう。  /tmp/.inodetext は他人に見られたくないディレクトリー内の情報も保持して しまうことがありますので注意。 @//E*O*F README// chmod u=rw,g=r,o=r README echo x - textf.c sed 's/^@//' > "textf.c" <<'@//E*O*F textf.c//' /* List free text table entry of 4.3BSD. Written by WATANABE katsuhiro Free to copy, modify, redistribute and so on. You will need access permission to read /dev/kmem. $Id: textf.c,v 2.1 1992/09/15 06:55:44 katsu Exp $ */ #include #include #include #include #include #define KERNELFILE "/vmunix" #define CORE "/dev/kmem" #define ktou(kernelp) ((caddr_t)(kernelp) - ((caddr_t)text - (caddr_t)text_copy)) #define utok(userp) ((caddr_t)(userp) + ((caddr_t)text - (caddr_t)text_copy)) #define E_MALLOC 11 #define E_OPEN 12 #define E_NLISTGET 21 #define E_GETKERNVALUE 22 off_t lseek(); char *malloc(); caddr_t mcopy(); int print_tent(); struct text *text, *text_copy, *xhead, *xtail; int ntext; /* ARGSUSED */ main(argc, argv) int argc; char **argv; { readtextt(); printf(" LOC FORW BACK CADDR VPTR CNT CCNT¥n"); traverse_free(xhead, print_tent); exit(0); } traverse_free(freelist, whattodo) struct text *freelist; /* addr in kernel vm space */ int (*whattodo)(); { for (; freelist != (struct text *)NULL; freelist = ((struct text *)ktou(freelist))->x_forw) { if ((*whattodo)(freelist) == 0) return; } } int print_tent(ktent) struct text *ktent; /* addr in kernel vm space */ { struct text *tent = (struct text *)ktou(ktent); printf("%8lx %8lx %8lx %8lx %8lx %3d %4d¥n", (long)ktent, (long)tent->x_forw, (long)tent->x_back, (long)tent->x_caddr, (long)tent->x_vptr, tent->x_count, tent->x_ccount); return (-1); } readtextt() { static struct nlist namelist[] = { #define STEXT 0 {"_text"}, #define SNTEXT 1 {"_ntext"}, #define SXHEAD 2 {"_xhead"}, #define SXTAIL 3 {"_xtail"}, {""} }; int corefd; struct text **textp, **xheadp, **xtailp; int *ntextp; unsigned texttsize; if (nlist(KERNELFILE, namelist) != 0) error(E_NLISTGET); if ((corefd = open(CORE, O_RDONLY)) < 0) error(E_OPEN, CORE); textp = (struct text **)namelist[STEXT].n_value; if (mcopy(&text, sizeof(text), corefd, (off_t)textp) == (caddr_t)(-1)) error(E_GETKERNVALUE, "_text"); ntextp = (int *)(namelist[SNTEXT].n_value); if (mcopy(&ntext, sizeof(ntext), corefd, (off_t)ntextp) == (caddr_t)(-1)) error(E_GETKERNVALUE, "_ntext"); xheadp = (struct text **)(namelist[SXHEAD].n_value); if (mcopy(&xhead, sizeof(xhead), corefd, (off_t)xheadp) == (caddr_t)(-1)) error(E_GETKERNVALUE, "_xhead"); xtailp = (struct text **)(namelist[SXTAIL].n_value); if (mcopy(&xtail, sizeof(xtail), corefd, (off_t)xtailp) == (caddr_t)(-1)) error(E_GETKERNVALUE, "_xtail"); texttsize = sizeof(struct text) * ntext; text_copy = (struct text *)malloc(texttsize); if (text_copy == (struct text *)NULL) error(E_MALLOC); if (mcopy(text_copy, (int)texttsize, corefd, (off_t)text) == (caddr_t)(-1)) error(E_GETKERNVALUE, "text table"); close(corefd); } /* a mock mmap(). */ caddr_t mcopy(addr, len, fd, off) caddr_t addr; /* destination */ int len; int fd; /* file descriptor */ off_t off; /* source */ { if (lseek(fd, off, L_SET) != off) return (caddr_t)(-1); if (read(fd, addr, len) < len) return (caddr_t)(-1); return (addr); } /* VARARGS1 */ error(no, str) int no; char *str; { switch (no) { case 0: break; case E_MALLOC: fprintf(stderr, "cannot allocate memory.¥n"); break; case E_OPEN: fprintf(stderr, "cannot open %s.¥n", str); break; case E_NLISTGET: fprintf(stderr, "cannot get name list.¥n"); break; case E_GETKERNVALUE: fprintf(stderr, "cannot get kernel value (%s)¥n", str); break; default: fprintf(stderr, "error.(%d)¥n", no); } exit(no); } @//E*O*F textf.c// chmod u=rw,g=r,o=r textf.c echo x - texts sed 's/^@//' > "texts" <<'@//E*O*F texts//' #!/usr/local/bin/perl # # Print text table and conjecture command name. # Written by WATANABE katsuhiro # Free to copy, modify, redistribute and so on. # $Id: texts,v 2.1 1992/09/15 08:36:01 katsu Exp $ $ilistfile="/tmp/.inodetext"; # (dev,inum) to name database $ENV{'PATH'}.='/etc:/usr/etc:'; # Where can I find ps and pstat? # candidates of init's path # (They are used to guess real value of $vnodetoinode.) push(@initid, &getinodeid("/etc/init")); push(@initid, &getinodeid("/sbin/init")); push(@initid, &getinodeid("/usr/etc/init")); # The offset of inode from top of vnode. (if cannot figure out later) $vnodetoinode = (-8); require('getopts.pl'); &Getopts('i'); if ($opt_i) { # make inode-to-name database &read_ilistdb; foreach (@ARGV) { ($dev) = stat($_); ($dmaj, $dmin) = &device_majorminor($dev); open(find, "find $_ -xdev -type f ¥¥( -perm -100 -o -perm -010 -o -perm -001 ¥¥) -ls |") || die "Cannot make inode database for $_"; while () { ($inum, $dsize, $mode, $links, $owner, $group, $size, $mon, $date, $year, $path) = split(' '); (@links) = split(/¥//, $path); $inode2text{"$dmaj,$dmin $inum"} = $links[$#links]; } close(find); } open(ilist, ">$ilistfile") || die "Cannot write inode database"; while (($inodeid, $name) = each %inode2text) { print ilist "$inodeid $name¥n"; } close(ilist); chmod 0666, $ilistfile; exit 0; } $pspid = open(ps, "-|") || exec 'ps','agxc'; # $command{$pspid} = "ps"; while () { @fields = split(' '); # It can happen that STAT field has a space. $pid = $fields[$[]; $command = $fields[$#fields]; $command{$pid} = $command; } close(ps); $pstatppid = open(pstat, "-|") || exec 'pstat', '-p'; $command{$pstatppid}='pstat'; ; ; while () { ($loc, $s, $f, $poip, $pri, $sig, $uid, $slp, $tim, $cpu, $ni, $pgrp, $pid, $ppid, $addr, $rss, $srss, $size, $wchan, $link, $textp) = split; $textp2pid{&kadradj(hex($textp))} = $pid; $ploc2pid{hex($loc)} = $pid; if ($pid == 1) { $inittextp = &kadradj(hex($textp)); } } close(pstat); $pstatipid = open(pstat, "-|") || exec 'pstat','-i'; $command{$pstatipid} = "pstat"; ; ; undef $initinodep; while () { s/,/ /; $iloc = substr($_, 0, 8); $idev = substr($_, 15, 7); ($ino) = split(' ', substr($_, 22)); ($idevmaj, $idevmin) = split(' ', $idev); $inodeid = "${idevmaj},${idevmin} $ino"; $inode{hex($iloc)} = $inodeid; foreach $initid (@initid) { if ($initid eq $inodeid) { $flags = substr($_, 55, 6); if ($flags =‾ /T/) { $initinodep = hex($iloc); } } } } close(pstat); &read_ilistdb; $pstatxpid = open(pstat, "-|") || exec 'pstat', '-x'; $command{$pstatxpid}='pstat'; $summary = ; ; print $summary; print " LOC CADDR VPTR CNT CCNT PID COMMAND¥n"; # try to guess $vnodetoinode while () { push(@pstatx, $_); ($loc, $_) = split(/ /, $_, 2); s/^[^¥da-f]+//; ($daddr, $caddr, $rss, $size, $vptr, $cnt, $ccnt, $forw, $back) = split; if (defined($initinodep) && (hex($loc) == $inittextp)) { $vnodetoinode = $initinodep - hex($vptr); last; } } foreach (@pstatx, ) { ($loc, $_) = split(/ /, $_, 2); s/^[^¥da-f]+//; ($daddr, $caddr, $rss, $size, $vptr, $cnt, $ccnt, $forw, $back) = split; printf("%8s %8s %8s %3d %4d", $loc, $caddr, $vptr, $cnt, $ccnt); if (defined($pid = $ploc2pid{hex($caddr)})) { # resident in core printf(" %5d %s", $pid, $command{$pid}); } elsif ($pid = $textp2pid{hex($loc)}) { # swapped printf(" %5d [%s]", $pid, $command{$pid}); } elsif ($vptr) { # cache $inode = $inode{hex($vptr) + $vnodetoinode}; if ($text = $inode2text{$inode}) { print " ($text)"; } else { print " ($inode)"; } } else { # free (no vnode) } print "¥n"; } close(pstat); sub kadradj { local($msb_cleaned_addr) = @_; $msb_cleaned_addr + 0x80000000; } # stat(filename) and make a string like "$maj_dev,$minor_dev $inum". sub getinodeid { local($filename) = @_; ($dev, $ino, $mode) = stat($filename); ($dmaj, $dmin) = &device_majorminor($dev); "${dmaj},${dmin} $ino"; } # divide device number into (major num , minor num) sub device_majorminor { local($device) = @_; ($dev >> 8, $dev & 0xff); } # read $ilistfile database into %inode2text sub read_ilistdb { local($ilist, $device, $inode, $name); if (open(ilist, "$ilistfile")) { while () { ($device, $inode, $name) = split; $inode2text{"$device $inode"} = $name; } close(ilist); } } @//E*O*F texts// chmod u=rwx,g=rx,o=rx texts exit 0