Operating Systems. Exercise 11

I. 排他的ファイル制御 (準備課題, 配点無し)

複数ユーザや複数プロセスが存在する以上, 資源使用の競合問題が起こり得ます。
安全なシステムではメモリだけでなくファイルへのアクセスについても, 必要に応じて排他制御を行うことが求められます。


低水準入出力関数

C言語には低水準ファイル入出力関数というものが用意されています。
高水準ファイル入出力関数(fopen, fprintfやfscanfなど)と比べて不便ですが, 低水準という呼び名通り よりOSに近いインタフェースです。
高水準ファイル入出力関数は, それら低水準ファイル入出力関数により実装されており, 自分でprintfのような関数を作成したり,
より高速な処理を行う場合に使用できます。

入出力オープン


//ファイル名"path", モード"oflag"を与えるとファイルディスクリプタが返る
int open(const char *path, int oflag, ...);
        
入出力クローズ

//ファイルディスクリプタ"fildes"を与えてそのファイルを閉じる
int close(int fildes);
        
読み込み

//ファイルディスクリプタ"fildes", 読込み先バッファ"buf", 読み込みバイト数"nbyte"を与え,
//ファイルの内容をbufに読み込む, 返り値は読み込んだバイト数
ssize_t read(int fildes, void *buf, size_t nbyte);
        
書き込み

//ファイルディスクリプタ"fildes", 書き込み元バッファ"buf", 書き込みバイト数"nbyte"を与え,
//bufの内容をファイルに書き込む, 返り値は書き込んだバイト数
ssize_t write(int fildes, const void *buf, size_t nbyte);
        

ファイル書き込み

ファイルへの書き込みは以下のようにします

FileWrite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>

void WriteFile(const char*);

int main(){
  const char *sFileName = "output.txt";

  //sFileNameのファイルが既に存在したら消去する(ファイル作成テストのため)
  unlink(sFileName);

  WriteFile(sFileName);

  return 0;
}

void WriteFile(const char *sFileName) {

  //OWRONLY|OCREAT : 書き込みモードで開く 無ければ作成する
  int nFileDesc = open(sFileName, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  char *str = "string\n";

  //strが指す文字列"string"を書き込む
  write(nFileDesc, str, strlen(str)*sizeof(char));

  close(nFileDesc);
}
      

ファイル読み込み

ファイル読み込みは以下のようにします

FileRead.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>

void ReadFile(const char*);

int main(){

  const char *sFileName = "output.txt";

  ReadFile(sFileName);

  return 0;
}

void ReadFile(const char *sFileName) {

  //O_RDONLY : 読み込みモード
  int nFileDesc = open(sFileName, O_RDONLY);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  char sBuf[128];

  while (read(nFileDesc, sBuf, 128*sizeof(char))) {
    printf("%s", sBuf);

    //文字列バッファsBufの内容をすべて0で初期化する
    memset(sBuf, 0, 128*sizeof(char));
  }

  close(nFileDesc); 
}
      
ファイルロック

メモリと同様に, ファイルでもセマフォのように読み書きの同期を取ることが出来ます
openは成功するとファイルディスクリプタを返し, そのファイルディスクリプタをflockに渡しロックするファイルを指定します

ファイルのロック/アンロック操作

  int nFileDesc = open(sFileName, ...);

  flock(nFileDesc, LOCK_EX); //排他ロックモード

  //ファイル読み書き
  ....

  flock(nFileDesc, LCK_UN); //ロック解除
        

例えば以下のように, あるプログラムがファイルに書き込み, もう一方のプログラムが同じファイルを読み込む状況があるとします

書き込みプログラム

void WriteFile(const char *sFileName) {

  //OWRONLY|OCREAT : 書き込みモードで開き無ければ作成する
  int nFileDesc = open(sFileName, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  //書き込み操作
  ...

  close(nFileDesc);
}
      
読み込みプログラム

void ReadFile(const char *sFileName) {

  //O_RDONLY : 読み込みモード
  int nFileDesc = open(sFileName, O_RDONLY);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  //読み込み操作
  ...

  close(nFileDesc);
}
      

一方がファイルに書き込んでいる最中にもう一方が読み込む, あるいは一方がファイルを読んでいる最中に
もう一方が書き込むということが起こると不完全なデータをやりとりする事態になるので,
互いにファイルへの読み書きをする間は排他的にファイルを扱うようにします。

書き込み側プログラム

void WriteFile(const char *sFileName) {

  //OWRONLY|OCREAT : 書き込みモードで開き無ければ作成する
  int nFileDesc = open(sFileName, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  flock(nFileDesc, LOCK_EX);//ファイルロック

  //書き込み操作
  ...

  close(nFileDesc);
  flock(nFileDesc, LOCK_UN);//ファイルロック解除
}
      
読み込み側プログラム

void ReadFile(const char *sFileName) {

  //O_RDONLY : 読み込みモード
  int nFileDesc = open(sFileName, O_RDONLY);
  if (nFileDesc < 0) {
    perror("open failed");
    return;
  }

  flock(nFileDesc, LOCK_EX);//ファイルロック

  //読み込み操作
  ...

  close(nFileDesc);
  flock(nFileDesc, LOCK_UN);//ファイルロック解除
}
      

II. ファイルとファイルロックを使ったプロセス間データ通信 (配点100%)

課題

ハノイの塔の問題をファイルロックで排他制御したテンポラリファイル(ファイル名 tempfile)を利用して作業分担します。

一方は円盤数と結果を出力する間隔(秒指定)をコマンドラインから受け取り,
ハノイの塔の問題を解きながらアラームでテンポラリファイルに結果を出力するプログラム。
もう一方は, 実行するとファイルの内容を読み取り出力するようにプログラムを書いてください。

ヘッダ : HanoiFileLock.h

ハノイの塔の円盤移動とファイルへの出力(Solve) (テンプレート)
ハノイの塔の状態を読み込み出力する(Display) (テンプレート)

:

ターミナル1 (プロセス1実行)

% ./HanoiFileLockSolve 30 3
(円盤数30, 3秒ごとにファイルに書き出し)
	    
  ターミナル2 (プロセス2実行)

% ./HanoiFileLockDisplay 

(途中省略)
| 0|   | 0|   | 0|
| 0|   | 0|   | 0|
| 0|   | 0|   | 0|
| 0|   | 1|   | 0|
| 0|   | 2|   | 9|
| 0|   | 3|   |10|
| 0|   | 4|   |11|
| 0|   | 5|   |16|
| 8|   | 6|   |19|
|13|   | 7|   |20|
|14|   |12|   |21|
|17|   |15|   |24|
|22|   |18|   |27|
|25|   |23|   |28|
|26|   |30|   |29|
----   ----   ----
  1      2      3
Number of Moves: 597906885
Number of Disks: 30