UNIX 7th code reading - ファイルのクローズ処理

はじめに

今回はファイルのクローズ処理を見て行きます。

close( )

下記のようにCのプログラムからオープン済みのファイルディスクリプタを引数にclose( )を実行すると、

  close( fp ) ;

libcのclose( )からcloseシステムコール(sys close(=6))が発行され、closeシステムコールハンドラ(カーネル中のclose( ))が実行されます。

http://www.tamacom.com/tour/kernel/unix/S/99.html#L163

closeシステムコールハンドラは、引数で与えられたファイルディスクリプタに対応したfile構造体をu.u_ofile[ ]から取得し、そのfile構造体を引数にclosef( )を呼び出します。

closef( )

http://www.tamacom.com/tour/kernel/unix/S/85.html#L45

closef( )はfile構造体の参照カウンタをデクリメントします。

デクリメントの結果、参照カウンタが0になる場合はfile構造体が差しているinodeをデクリメントするためにiput( )を呼び出します。

同時に、必要ならばデバイスのクローズ処理を行うのですが、そこにFMP関連の処理が追加されています。

  90         if ((flag & FMP) == 0)
  91                 for(fp=file; fp < &file[NFILE]; fp++)
  92                         if (fp->f_count && fp->f_inode==ip)
  93                                 return;
  94         (*cfunc)(dev, flag, cp);
  95 }

クローズするファイルにFMPフラグがたっていなかった場合、閉じようとしているinodeを指しているfileエントリが他にもいたらデバイスのクローズ処理を行わずにreturnします。

ちなみに、デバイスのクローズ処理は、v6ではclosei( )という関数に別れていましたが、v7ではclosef( )にインライン展開されています。

iput( )

http://www.tamacom.com/tour/kernel/unix/S/86.html#L122

iput( )はinodeの参照カウンタをデクリメントします。参照カウンタが0になるとき、そのinodeを解放するためにflagとi_numberをクリアします。(こちらはコア中のinodeの話)

また、その際i_nlink(そのinodeへのリンク数。linkシステムコールによりハードリンクが張られるごとに1増える。i_nlinkのデクリメントはunlinkにより行われる)が0のときはシステム中にそのinodeを指すディレクトリエントリがいないことを表すので、itrunc( )によりデータブロックを、ifree( )によりinodeをフリーリストに戻します。(こちらはブロックデバイス中のinode, データブロックの話)

 128                 if(ip->i_nlink <= 0) {
 129                         itrunc(ip);
 130                         ip->i_mode = 0;
 131                         ip->i_flag |= IUPD|ICHG;
 132                         ifree(ip->i_dev, ip->i_number);
 133                 }
 134                 iupdat(ip, &time, &time);

v6と比較すると上記131行目にIUPD, ICHGフラグを立てる処理が追加されています。しかしitrunc( )の最後でもIUPD, ICHGフラグを立てているので、これが何を意味しているのかまだよくわかっていません。

itrunc( ), tloop( )

itrunc( )はinodeが指しているデータブロックを全てフリーリストに戻します。

inodeからデータブロックへのマッピングを表す図を再掲します。

http://www.tamacom.com/tour/kernel/unix/S/86.html#L204

 204 itrunc(ip)
 205 register struct inode *ip;
 206 {
 207         register i;
 208         dev_t dev;
 209         daddr_t bn;
 210 
 211         i = ip->i_mode & IFMT;
 212         if (i!=IFREG && i!=IFDIR)
 213                 return;
 214         dev = ip->i_dev;
 215         for(i=NADDR-1; i>=0; i--) {
 216                 bn = ip->i_un.i_addr[i];
 217                 if(bn == (daddr_t)0)
 218                         continue;
 219                 ip->i_un.i_addr[i] = (daddr_t)0;
 220                 switch(i) {
 221 
 222                 default:
 223                         free(dev, bn);
 224                         break;
 225 
 226                 case NADDR-3:
 227                         tloop(dev, bn, 0, 0);
 228                         break;
 229 
 230                 case NADDR-2:
 231                         tloop(dev, bn, 1, 0);
 232                         break;
 233 
 234                 case NADDR-1:
 235                         tloop(dev, bn, 1, 1);
 236                 }
 237         }
 238         ip->i_size = 0;
 239         ip->i_flag |= ICHG|IUPD;
 240 }

i_addr[ ]の後方から順にfree( )でデータブロックの解放を行います。i_addr[ ]の後ろの方は間接参照ブロックで、それらの解放はtloop( )を呼び出して行います。

http://www.tamacom.com/tour/kernel/unix/S/86.html#L242

 241 
 242 tloop(dev, bn, f1, f2)
 243 dev_t dev;
 244 daddr_t bn;
 245 {
 246         register i;
 247         register struct buf *bp;
 248         register daddr_t *bap;
 249         daddr_t nb;
 250 
 251         bp = NULL;
 252         for(i=NINDIR-1; i>=0; i--) {
 253                 if(bp == NULL) {
 254                         bp = bread(dev, bn);
 255                         if (bp->b_flags & B_ERROR) {
 256                                 brelse(bp);
 257                                 return;
 258                         }
 259                         bap = bp->b_un.b_daddr;
 260                 }
 261                 nb = bap[i];
 262                 if(nb == (daddr_t)0)
 263                         continue;
 264                 if(f1) {
 265                         brelse(bp);
 266                         bp = NULL;
 267                         tloop(dev, nb, f2, 0);
 268                 } else
 269                         free(dev, nb);
 270         }
 271         if(bp != NULL)
 272                 brelse(bp);
 273         free(dev, bn);
 274 }
 275 

bread( )を使ってデータブロックの一覧が格納されているデータブロックを読み込み、free( )を使って順にデータブロックをフリーリストに返していきます。二重間接参照、三重間接参照の場合はtloop( )を呼び出して、更に一段深く参照ブロックの解放を試みます。

iupdat( )

http://www.tamacom.com/tour/kernel/unix/S/86.html#L149

iupdat( )はinodeが更新されていたらブロックデバイスにそれを反映させます。

 174                 p1 = (char *)dp->di_addr;
 175                 p2 = (char *)ip->i_un.i_addr;
 176                 for(i=0; i<NADDR; i++) {
 177                         *p1++ = *p2++;
 178                         if(*p2++ != 0 && (ip->i_mode&IFMT)!=IFMPC
 179                            && (ip->i_mode&IFMT)!=IFMPB)
 180                                 printf("iaddress > 2^24\n");
 181                         *p1++ = *p2++;
 182                         *p1++ = *p2++;
 183                 }

上記の177-182行目を見ると、コア中のinodeのi_addrは(ip->i_un.i_addr)は4bytes(long)で情報を管理していて、ブロックデバイス中のdi_addr(dp->di_addr)は3bytesで情報を管理しているように見えます。

i_addr[ ]の型を見てみるとdaddr_tの配列であることが確認できます。

http://www.tamacom.com/tour/kernel/unix/S/61.html#L39

  39                         daddr_t i_addr[NADDR];  /* if normal file/directory */

daddr_tはlongであることが確認できます。

http://www.tamacom.com/tour/kernel/unix/S/68.html#L1

   1 typedef long            daddr_t;        /* disk address */

一方di_addrは40bytes分確保されています。NADDRはデフォルトで13なので13*3=39. 40 > 39であり、3bytesで情報管理をしているという推測は当たっていそうな気がします。39bytesでなく40bytesなのはword(2bytes)単位にしたいのと、1inodeあたりのサイズを64bytesにしたいからでしょう。(di_addr以外で1inodeあたり24bytes使用している)

http://www.tamacom.com/tour/kernel/unix/S/60.html#L12

  12         char    di_addr[40];    /* disk block addresses */

……と、同ファイル中にコメントがありました。当たりだったようです。

http://www.tamacom.com/tour/kernel/unix/S/60.html#L18

  18 /*
  19  * the 40 address bytes:
  20  *      39 used; 13 addresses
  21  *      of 3 bytes each.
  22  */

1ブロック(512bytes)あたりに8inodeであるということを示す文もありました。512 bytes / 8 = 64bytesであり、di_addrに40bytes割いているのは1inodeあたりのデータを64bytesにしたいためだという推測も当たっていそうです。

http://www.tamacom.com/tour/kernel/unix/S/60.html#L17

  17 #define INOPB   8       /* 8 inodes per block */

終わりに

次回はsuperblockに関する処理や、alloc( ), ialloc( )などフリーリスト操作を見ていこうと思います。

あと、一度FMPとは何かということをきちんと理解しておく必要があると思いました。