UNIX 7th code reading - ファイルの読み書き処理

はじめに

今回はファイルの読み書き処理を見ていきます。クローズ処理は次回に回します。

read( ), write( )


ユーザプログラムからsys read(ファイルの読み込み), sys write(ファイルの書き込み)が実行されると、それぞれカーネルのread( )とwrite( )が実行されます。

readはFREAD, writeはFWRITEフラグを引数にrdwr( )を呼び出します。6thと同じ処理です。

rdwr( )

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

rdwr( )はファイル読み書きのためにu.u_base, u.u_count, u.u_offset, u.u_segflgをセットしてからreadi( ), writei( )を呼び出します。パイプの読み書きをする場合はreadi( ), writei( )ではなくreadp( ), writep( )を呼び出します。

7thから追加された論理が二箇所有ります。

  58                 ip = fp->f_inode;
  59                 if (fp->f_flag&FMP)
  60                         u.u_offset = 0;
  61                 else
  62                         u.u_offset = fp->f_un.f_offset;
  63                 if((ip->i_mode&(IFCHR&IFBLK)) == 0)
  64                         plock(ip);
  65                 if(mode == FREAD)
  66                         readi(ip);
  67                 else
  68                         writei(ip);
  69                 if((ip->i_mode&(IFCHR&IFBLK)) == 0)
  70                         prele(ip);
  71                 if ((fp->f_flag&FMP) == 0)
  72                         fp->f_un.f_offset += uap->count-u.u_count;

対象inodeが特殊型でなかった場合、readi( ), writei( )の前後でinodeに対しplock( ), prele( )によって排他処理が行われるようになりました。

疑問1. 上記63, 69行目はIFCHR & IFBLKではなくてIFCHR | IFBLKではないだろうか?

疑問2. 6thではこの箇所に排他処理が入っていなかった。複数プロセスが同時に同じinodeに対する読み書き処理をし得た?

また、ファイルにFMPフラグが立っていたらオフセットを0(先頭から読み書き)にし、また読み書き処理後にファイルのオフセットを更新しないようになりました。このFMPというフラグは7thからついかされたもので、どうやらMPX(MultiPleXed) filesというものに使われるようなのですが、その詳細はまだ理解できていません。解明するためにMPX(2)を読んでいるところです。

readi( )

続いてrdwr( )から呼ばれるreadi( )を見ていきます。

http://www.tamacom.com/tour/kernel/unix/S/94.html#L19

readi( )はbmap( )関数を使って、読み出し対象のデータブロックNoを取得し、bread( )、もしくはbread( )を使ってファイルの読み出し処理を開始します。読み出し処理完了後にiomove( )を使ってユーザ領域に読み出したデータをコピーします。

  31         if(u.u_offset < 0) {
  32                 u.u_error = EINVAL;
  33                 return;
  34         }

今回からu.u_offsetが0未満でないかのチェックが入り、妥当性検証の強化されたようです。u.u_offsetが0未満になることはあるのでしょうか? オーバーフロー対策?

  58                 if ((long)bn<0) {
  59                         bp = geteblk();
  60                         clrbuf(bp);
  61                 } else if (ip->i_un.i_lastr+1==lbn)
  62                         bp = breada(dev, bn, rablock);
  63                 else
  64                         bp = bread(dev, bn);

また、longでキャストしたbn(通常ファイルの場合はbmap( )の結果)が0未満の場合は、ファイルの読み出し処理を行わずに、getblk( )でバッファを取得し、clrbuf( )でそのバッファをクリアしています。

疑問3. bnが0になるのはどういうとき? エラー? → 追記:bmap( )でエラーが起きた場合-1が返る。詳しくは↓↓に書いたbmap( )の解説を参照のこと。

疑問4. バッファを取得・クリアしているのは何のため? 後の処理を通常読み出し時と合わせるために、(クリアされた)バッファを必要としている?

writei( )

続いてreadi( )と同様にrdwr( )から呼ばれるwritei( )を見ていきます。

http://www.tamacom.com/tour/kernel/unix/S/94.html#L83

writei( )はbmap( )関数を使って、読み出し対象のデータブロックNoを取得し、iomove( )を使ってユーザ領域からバッファにデータをコピーした後に、bdwrite( )を使ってバッファのデータをファイルに書き込みます。

  92         if(u.u_offset < 0) {
  93                 u.u_error = EINVAL;
  94                 return;
  95         }

readi( )と同様にu.u_offsetのチェックが加わっています。

 121                 if(u.u_error != 0)
 122                         brelse(bp);
 123                 else
 124                         bdwrite(bp);

また、ファイルへの書き込みを行うときは、必ずbdwrite( )(遅延書き込み)を使うようになりました。6thでは、オフセットがブロックサイズ(512Bytes)で切れる場合は、bawrite( )で非同期的に"直ちに"ファイルへ書き込んでいました。

6thと比較すると、7thの方がブロックデバイスへのアクセスが減ると思われます。例えば、書き込んだデータを(遅延書き込み処理が行われる前に)すぐに読み出し、更新して再度書き込む場合を考えます。6thの場合には2回ブロックデバイスへのアクセスが発生するのですが、7thの場合は最初の書き込み処理時にはブロックデバイスへのアクセスが発生しません。

疑問5. 6thはなぜ512bytesで切れる場合にbawrite( )を使用していた?

 128                 ip->i_flag |= IUPD|ICHG;

書き込み処理が完了した後に、inodeにIUPDだけでなくICHGフラグも立つようになりました。ICHGフラグは7thから追加されたものです。

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

  53 /* flags */
  54 #define ILOCK   01              /* inode is locked */
  55 #define IUPD    02              /* file has been modified */
  56 #define IACC    04              /* inode access time to be updated */
  57 #define IMOUNT  010             /* inode is mounted on */
  58 #define IWANT   020             /* some process waiting on lock */
  59 #define ITEXT   040             /* inode is pure text prototype */
  60 #define ICHG    0100            /* inode has been changed */

inode.hを見るとわかるように、7thからファイルが更新された(=IUPD)ことと、inodeが更新された(=ICHG)ことを区別して管理するようになったようです。

bmap( )

続いてinodeとデータブロックのマッピングを行うbmap( )を見ていきます。

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

7thのデータブロックのマッピングの絵を再掲しておきます。

仕様については以前のエントリを参考にしてください。

データブロックへのマッピングの仕様が6thから変わったので、当然ソースコードもがらっと変わっています。簡単に解説していくと

  17 daddr_t
  18 bmap(ip, bn, rwflg)
  19 register struct inode *ip;
  20 daddr_t bn;
  21 {
  22         register i;
  23         struct buf *bp, *nbp;
  24         int j, sh;
  25         daddr_t nb, *bap;
  26         dev_t dev;
  27 
  28         if(bn < 0) {
  29                 u.u_error = EFBIG;
  30                 return((daddr_t)0);
  31         }
  32         dev = ip->i_dev;
  33         rablock = 0;
  34 

引数は以下

  • ip : inode
  • bn : 論理的なblock No. ファイル中の何ブロック目かを表す。オフセットから512(=1blockのサイズ)を割ることで算出される
  • rwflg : 読み書きどちらを行おうとしているか


bnが負でないことをチェックし、メインロジックへ突入します。

  35         /*
  36          * blocks 0..NADDR-4 are direct blocks
  37          */
  38         if(bn < NADDR-3) {
  39                 i = bn;
  40                 nb = ip->i_un.i_addr[i];
  41                 if(nb == 0) {
  42                         if(rwflg==B_READ || (bp = alloc(dev))==NULL)
  43                                 return((daddr_t)-1);
  44                         nb = bp->b_blkno;
  45                         bdwrite(bp);
  46                         ip->i_un.i_addr[i] = nb;
  47                         ip->i_flag |= IUPD|ICHG;
  48                 }
  49                 if(i < NADDR-4)
  50                         rablock = ip->i_un.i_addr[i+1];
  51                 return(nb);
  52         }
  53 

最初に該当ブロックが直接参照ブロックの場合だったときの処理を行います。

該当するinodeのi_addr[ ]が0だった場合、読み出し処理時の場合は-1(エラー)を返します。読もうとしているデータブロックがまだ存在していないからです。ファイルサイズより大きいアドレスを読もうとするときなどに起こりうるでしょう。

書き込み処理時の場合には、新たにデータブロックを確保するためにalloc( )を実行します。この段階でbdwrite( )を呼んでいるのは、B_DONEフラグを立てる(キャッシュを有効にする)ためではないかと思われます。

該当データブロックが直接参照ブロックの最後のブロックでなかった場合、次のデータブロックNoを先読みデータブロックNoを示すrablockに格納しておきます。

直接参照ブロックと間接参照ブロックをまたがって処理を行おうとする場合、その切れ目のタイミングでは先読みが行われないようです。

最後に物理的なデータブロックNoをreturnします。

  54         /*
  55          * addresses NADDR-3, NADDR-2, and NADDR-1
  56          * have single, double, triple indirect blocks.
  57          * the first step is to determine
  58          * how many levels of indirection.
  59          */
  60         sh = 0;
  61         nb = 1;
  62         bn -= NADDR-3;
  63         for(j=3; j>0; j--) {
  64                 sh += NSHIFT;
  65                 nb <<= NSHIFT;
  66                 if(bn < nb)
  67                         break;
  68                 bn -= nb;
  69         }
  70         if(j == 0) {
  71                 u.u_error = EFBIG;
  72                 return((daddr_t)0);
  73         }
  74 
  75         /*
  76          * fetch the address from the inode
  77          */
  78         nb = ip->i_un.i_addr[NADDR-j];
  79         if(nb == 0) {
  80                 if(rwflg==B_READ || (bp = alloc(dev))==NULL)
  81                         return((daddr_t)-1);
  82                 nb = bp->b_blkno;
  83                 bdwrite(bp);
  84                 ip->i_un.i_addr[NADDR-j] = nb;
  85                 ip->i_flag |= IUPD|ICHG;
  86         }
  87 
  88         /*
  89          * fetch through the indirect blocks
  90          */
  91         for(; j<=3; j++) {
  92                 bp = bread(dev, nb);
  93                 if(bp->b_flags & B_ERROR) {
  94                         brelse(bp);
  95                         return((daddr_t)0);
  96                 }
  97                 bap = bp->b_un.b_daddr;
  98                 sh -= NSHIFT;
  99                 i = (bn>>sh) & NMASK;
 100                 nb = bap[i];
 101                 if(nb == 0) {
 102                         if(rwflg==B_READ || (nbp = alloc(dev))==NULL) {
 103                                 brelse(bp);
 104                                 return((daddr_t)-1);
 105                         }
 106                         nb = nbp->b_blkno;
 107                         bdwrite(nbp);
 108                         bap[i] = nb;
 109                         bdwrite(bp);
 110                 } else
 111                         brelse(bp);
 112         }
 113 
 114         /*
 115          * calculate read-ahead.
 116          */
 117         if(i < NINDIR-1)
 118                 rablock = bap[i+1];
 119         return(nb);
 120 }

続いて、間接参照ブロックの処理を行います。

データブロックを何度か辿って、該当のデータブロックNoを割り出します。

直接参照ブロックの処理と7thのデータブロックマッピングの仕様6thのbmap( )の解説を見れば処理内容はわかるかと思いますので、詳細な解説は省略します。

(6thではえらくわかりづらいコードだったのですが、7thではかなり読みやすいものになりましたし)

iomove( )

最後にreadi( ), writei( )から呼び出されるiomove( )を見ていきます。

http://www.tamacom.com/tour/kernel/unix/S/94.html#L173

iomove( )はバッファのデータとユーザ領域のデータをやり取りする関数です。

 181         if(u.u_segflg != 1 &&
 182           (n&(NBPW-1)) == 0 &&
 183           ((int)cp&(NBPW-1)) == 0 &&
 184           ((int)u.u_base&(NBPW-1)) == 0) {
 185                 if (flag==B_WRITE)
 186                         if (u.u_segflg==0)
 187                                 t = copyin(u.u_base, (caddr_t)cp, n);
 188                         else
 189                                 t = copyiin(u.u_base, (caddr_t)cp, n);
 190                 else
 191                         if (u.u_segflg==0)
 192                                 t = copyout((caddr_t)cp, u.u_base, n);
 193                         else
 194                                 t = copyiout((caddr_t)cp, u.u_base, n);
 195                 if (t) {
 196                         u.u_error = EFAULT;
 197                         return;
 198                 }
 199                 u.u_base += n;
 200                 u.u_offset += n;
 201                 u.u_count -= n;
 202                 return;
 203         }

u.u_segflgが0でも1でもないときは、copyiin( ), copyiout( )という関数が使用されるようです。

u.u_segflgが0, 1以外に設定されるのはxalloc( )のみのようです。

http://www.tamacom.com/tour/kernel/unix/S/103.html#L133

 130         u.u_count = u.u_exdata.ux_tsize;
 131         u.u_offset = sizeof(u.u_exdata);
 132         u.u_base = 0;
 133         u.u_segflg = 2;
 134         u.u_procp->p_flag |= SLOCK;
 135         readi(ip);

135行目のreadi( )のときにのみcopyiout( )が使用されるようです。

疑問6. copyiin( )はいつ使われる?

疑問7. copyiin( ), copyiout( )はcopyin( ), copyout( )と何が違う?

疑問7.についてですが、これらの関数はアセンブラで書かれているため、調査は後回しにします。xalloc( )から呼ばれるので、共有テキストセグメント絡みな話な気がしますが。

終わりに

疑問点がいくつか残ったままですが、折を見て真面目に調査をしようと思います。それまでは、とりあえずどんどん先に進んでいきます。

次回こそはファイルのクローズ処理を見ていく予定です。