UNIX 7th code reading - ファイルのオープン処理

はじめに

今回もUNIX 7thのコードを読み解いていき、6thとの差分を明らかにしていきます。

今回はファイルのオープン時の処理を見ていきます。

creat( ), open( )

ユーザプログラムからsys open(既存のファイルのオープン), sys creat(新規にファイルを生成。既存のファイルがあれば初期化してオープン)が実行され、カーネルシステムコールハンドラ(sys/sys2.cのcreat( ), open( ))が呼び出されます。

(sys命令からカーネルシステムコールハンドラが呼び出されるまでの流れは、また別の機会に確認します)


creat( )もopen( )も6thと差がないようです。

creat( )はnamei( )を実行して、ユーザプログラムから渡されたパス名に対応するファイルが存在していなければmaknode( )を実行してinodeを新たに生成します。そのinodeをopen1( )に渡します。ファイルが存在していれば、そのinodeをopen1( )に渡します。

open( )もcreat( )と同様にnamei( )を実行します。取得したinodeをopen1( )に渡します。ファイルが存在していなければ何もせずにreturnします。

maknode( )

続いて、creat( )で該当ファイルが存在しなかった場合に呼ばれるmaknode( )を見ていきます。

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

maknode( )も6thと大きな違いはなく、ialloc( )でinodeをフリーリストから新たに取得し、そのinodeの初期設定を行った後に、wdir( )を読んでディレクトリにentryを追加してから、そのinodeをreturnします。

ただし、細かなところまで見ると6thとは少し差異があります。

 284         ip = ialloc(u.u_pdir->i_dev);
 285         if(ip == NULL) {
 286                 iput(u.u_pdir);
 287                 return(NULL);
 288         }

フリーリストが空であるなど、ialloc( )でinodeが取得できなかった場合、iput( )を呼び出して、新たにファイルを追加しようとしているディレクトリのロック解放&参照カウンタのデクリメントを行っています。

恐らくこれはnamei( )の中から呼ばれるiget( )でかけられたロック&参照カウンタのインクリメントに対するものではないかと思います。(下記の58-59行目)

  42         for(ip = &inode[0]; ip < &inode[NINODE]; ip++) {
  43                 if(ino == ip->i_number && dev == ip->i_dev) {
  44                         if((ip->i_flag&ILOCK) != 0) {
  45                                 ip->i_flag |= IWANT;
  46                                 sleep((caddr_t)ip, PINOD);
  47                                 goto loop;
  48                         }
  49                         if((ip->i_flag&IMOUNT) != 0) {
  50                                 for(mp = &mount[0]; mp < &mount[NMOUNT]; mp++)
  51                                 if(mp->m_inodp == ip) {
  52                                         dev = mp->m_dev;
  53                                         ino = ROOTINO;
  54                                         goto loop;
  55                                 }
  56                                 panic("no imt");
  57                         }
  58                         ip->i_count++;
  59                         ip->i_flag |= ILOCK;
  60                         return(ip);
  61                 }
  62                 if(oip==NULL && ip->i_count==0)
  63                         oip = ip;
  64         }

このiput( )は7thから追加されたものです。

すると気になるのが6thでのこの箇所の扱いです。iput( )が実行されないので、namei( ), iget( )でかけられたロックが解放されず、参照カウンタもインクリメントされたままです。バグだったのでしょうか。もう一度6thを読み直してみる必要がありそうです。

open1( )

続いてcreat( ), open( )の共通処理open1( )を見ていきます。

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

ここも6thとほぼ同じ処理になっています。最初にaccess( )でパーミッションチェックを行い、必要ならばデータブロックを初期化し、falloc( )でfile[ ]から空きエントリを取得しu.u_ofile[ ]の空きエントリからfileエントリを指すようにします。

u.u_ofile[ ], file[ ], inode[ ]の関係は6thと変わりがないようです。

6thのときのエントリで載せた絵を再掲しておきます。

open1( )の中で一箇所だけ論理の変更があります。

 142         if(trf == 1)
 143                 itrunc(ip);

trfが1(creat( )から呼ばれたとき、かつ、該当ファイルが存在していたとき)にのみ、該当inodeが使用するデータブロックをくりあするようになっています。

6thではtrfが0でないときにitrunc( )を呼んでおり、trfが2(creat( )から呼ばれたとき、かつ、該当ファイルが存在していないとき)の場合にもデータブロックのクリア処理が入っていました。この処理はLions本でも無駄であると指摘されており、7thでは修正が入ったようです。

falloc( ), ufalloc( )

続いて、open1( )から呼ばれるfalloc( )、そしてfalloc( )から呼ばれるufalloc( )を見ていきます。


falloc( )の処理は6thとまったく同じです。ufalloc( )を呼んでu.u_ofile[ ]の空きエントリを取得した後に、file[ ]を線形探索し空きエントリを取得します。u.u_ofile[ ]の空きエントリにfile[ ]の空きエントリ(=fileとします)を対応付け、fileの初期化(参照カウンタのインクリメント, ファイルオフセットのクリア)をした後にfileをreturnします。

ufalloc( )の処理も6thとほぼ同じで、u.u_ofile[ ]から空きエントリを探し、そのindexをreturnします。

このとき、u.u_pofile[ ]の該当エントリを初期化するロジックが追加されています。(下記225行目)

 222         for(i=0; i<NOFILE; i++)
 223                 if(u.u_ofile[i] == NULL) {
 224                         u.u_r.r_val1 = i;
 225                         u.u_pofile[i] = 0;
 226                         return(i);
 227                 }

u.u_pofile[ ]は7thから追加されたされたuの要素です。u.u_pofile[ ]にはEXCLOSEというフラグが立ち得るようです。

そのEXCLOSEフラグが立てられる箇所は、ioctlシステムコールのハンドラであるioctl( )の中のみのようです。

そこでユーザプログラムマニュアルのioctl(2)を見てみたところ、下記の記述がありました。

The following two calls, however, apply to any open file:
  ioctl(fildes, FIOCLEX, NULL);
  ioctl(fildes, FIONCLEX, NULL);
The first causes the file to be closed automatically during a successful exec operation; the second reverses the effect of the first.

このFIOCLEXがEXCLOSEフラグに対応しているようで、オープン済みのファイルごとにEXCLOSEフラグを設定できるようです。

このEXCLOSEフラグが立っているとexec実行時に、そのファイルは自動的に閉じられるようです。

execのシステムコールハンドラの最後の方で実行されるsetregs( )を見てみます。

http://www.tamacom.com/tour/kernel/unix/S/98.html#L276

 290         for(i=0; i<NOFILE; i++) {
 291                 if (u.u_pofile[i]&EXCLOSE) {
 292                         closef(u.u_ofile[i]);
 293                         u.u_ofile[i] = NULL;
 294                         u.u_pofile[i] &= ~EXCLOSE;
 295                 }
 296         }

u.u_pofile[ ]を探索し、EXCLOSEフラグが立っているファイルを閉じているのがわかります。

fork時にはu.u_ofile[ ]情報も子プロセスに継承されます。子プロセスに(暗黙的に)オープン済のファイルを引き継がせたくないときは、該当ファイルに対しioctl(fildes, FIOCLEX, NULL);を実行し、fork -> execのexecの段階でそのファイルを閉じさせるのだと思います。(他に正しい用途がありましたらご指摘ください)

終わりに

次回はファイルの読み書き、クローズ処理を見ていく予定です。