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とは何かということをきちんと理解しておく必要があると思いました。