Operating Systems. Exercise 05

0. 文字列解釈と生成 (準備課題, 配点無し)
標準C言語関数であるsprintfとsscanfを説明します。 色々な場面で使えるので, 万が一知らない人は良く読んで理解してください。

sscanf
sscanfはstdio.hの中で以下のように宣言されています (% man sscanf で使い方がチェックできます)

int sscanf(const char *str, const char *format, ...);
      
sscanfによる文字列解釈のテスト(StringInterpret.c)

#include <stdio.h>

int main() {

  //解釈したい文字列
  char sStr[32] = "string 1";

  //解釈した結果を格納する変数
  char sSubStr[32] = "";
  int  nInt = 0;

  //sscanf返り値格納変数
  int  nRead = 0;

  //sStrから scanfのように別の変数に値を読み込むことができます
  //返り値は読み込みに成功した変数の数です
  nRead = sscanf(sStr, "%s %d", sSubStr, &nInt);

  printf("nRead: %d\nsSubStr: %s\nnInt: %d\n", nRead, sSubStr, nInt);

}
      
実行すると結果は以下のようになります

nRead: 2
sSubStr: string 
nInt: 1
      

sprintf
sprintfはstdio.hの中で以下のように宣言されています (% man sprintf で使い方がチェックできます)

int sprintf(char *str, const char *format, ...);
      
sprintfによる文字列生成のテスト(StringCreate.c)

#include <stdio.h>

int main() {

  //文字列作成先
  char sDstStr[32];

  //文字列作成元の変数群
  char sSrcStr[32] = "teststr";
  int  nSrcInt = 5;

  //sprintf返り値格納変数
  int  nPrint = 0;

  //sSrcStrとnSrcIntをprintfした結果を, sDstStrに格納します
  //返り値は格納に成功した文字の数です
  nPrint = sprintf(sDstStr, "%s %d", sSrcStr, nSrcInt);

  printf("nPrint: %d\nsDstStr: %s\n", nPrint, sDstStr);

}
      
実行すると結果は以下のようになります

nPrint: 9
sDstStr: teststr 5
      

I. パイプによるプロセス連携 (準備課題, 配点無し)
演習問題を解くために必要なプロセス連携のためのC言語インターフェースを説明します。

pipeによる 標準入出力を利用したプロセス間通信

シェルにはパイプという機能があり, あるコマンドの出力をパイプを通して別のコマンドに入力として渡すことができます。

C言語ではこのパイプを"pipe"システムコールによって実現できます。
以下プログラムはコマンドラインで "ls -l | grep .c" を実行したのと同じことになります。
手順を追い挙動を理解してください。

子プロセスで"ls -l"を実行した結果を、パイプを通して親プロセスに渡し"grep .c"を実行


#include <stdio.h>
#include <stdlib.h>

//プロトタイプ宣言を適宜

int main(int argc, char **argv, char **env) {

    int nPid;

    // pipe関数でパイプのファイルディスクリプタ(入出力先識別ID)を取得
    int naPipeDescripter[2];
    pipe(naPipeDescripter);

    nPid = fork();

    if (!nPid)         ChildProcess(naPipeDescripter);
    else if (nPid > 0) ParentProcess(naPipeDescripter);

}

void ChildProcess(int *naPipeDesc){

    //子プロセスではパイプの出口は使わないので閉じる
    close(naPipeDesc[0]);  

    //標準出力がパイプの入口側になる ("ls -l"の出力先をパイプにする)
    dup2(naPipeDesc[1], fileno(stdout)); 

    //子プロセスで"ls -l"を実行した結果は、パイプに流される
    system("ls -l");

    close(naPipeDesc[1]);

    exit(0);
}

void ParentProcess(int *naPipeDesc){

    //親プロセスではパイプの入口は要らないので閉じておく
    close(naPipeDesc[1]);

    //標準入力がパイプの出口側になる
    dup2(naPipeDesc[0], fileno(stdin));

    //子プロセスが終わるのを待つ("ls -l"を実行し、結果がパイプから流れてくる)
    wait(0);

    //子プロセス"ls -l"の結果から"grep .c"で拡張子が.cのファイルを表示
    system("grep .c");

    close(naPipeDesc[0]);
}
	

pipeと, read, writeによるプロセス間通信

シグナルとパイプを組み合わせると, シグナルを受け取ったらパイプを通じて
子プロセスから親プロセスにデータを送ることが出来ます。
以下は, 子プロセスからwriteによってデータをパイプに流し, 親プロセスからreadによってデータを読むプログラムです
いろいろ実験し, 挙動を理解してください。

シグナルとパイプ (SignalPipeTest)


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

//プロトタイプ宣言を適宜

int nSignalReceived = 0;

int main(){

  int nPid;
  int naPipeDesc[2];

  signal(SIGUSR1, SignalHandler);
  pipe(naPipeDesc);

  nPid = fork();
  if (!nPid) ChildProcess(naPipeDesc);
  else       ParentProcess(naPipeDesc);

  return 0;
}

void ParentProcess(int *naPipeDesc) {

  //メッセージを受け取るバッファ
  char sCommBuf[1024]; 

  //メッセージの中身を代入するための変数
  char sMessageStr[1024];
  int nMessageInt;

  close(naPipeDesc[1]);
  
  while (1) {

    //データ受信
    if (read(naPipeDesc[0], sCommBuf, 1024) <= 0) break;

    //データ解釈
    sscanf(sCommBuf, "%s %d", sMessageStr, &nMessageInt);

    printf("parent received : %s %d\n", sMessageStr, nMessageInt);

  }

  close(naPipeDesc[0]);
  wait(0);
}

void ChildProcess(int *naPipeDesc) {

  //メッセージをまとめて送るためのバッファ
  char sCommBuf[1024];

  int nSignalCount = 0;

  close(naPipeDesc[0]);

  while (1) {

    //シグナル受信フラグが立っていたら子から親へメッセージ送信
    if (nSignalReceived) {

      ++nSignalCount;

      //メッセージ作成
      sprintf(sCommBuf, "string %d", nSignalCount);

      //メッセージ送信
      write(naPipeDesc[1], sCommBuf, 1024);

      //フラグを降ろす
      nSignalReceived = 0;
    }
   
  }

  close(naPipeDesc[1]);
  exit(0);

}

void SignalHandler(int code) {

  //シグナルを受け取ったらシグナル受信フラグを立てる
  nSignalReceived = 1;

  signal(SIGUSR1, SignalHandler);
}
	
ターミナル1 (シグナル待ちプログラム実行)

% ./SignalPipeTest

string 1 (ターミナル2からSIGUSR1受信)

[Ctrl-C] (終了)
	      
  ターミナル2 (シグナル送信側)

% ps -a | grep SignalPipeTest

 6812 pts/0    00:00:00 SignalPipeTest


% kill -USR1 6812 (ターミナル2からSIGUSR1送信)
	      


II. プロセス連携1(配点100%)

課題

導入: "sed"コマンドは文字の置換に便利なコマンドです。
例えば, "banner"の出力をパイプで"sed"に渡し、"#"から"$"に置き換えることができます。

コマンド


  % ~nisidate/bin/banner Aizu | sed s/#/\$/g
	      
結果

   $
  $ $       $    $$$$$$  $    $
 $   $      $        $   $    $
$     $     $       $    $    $
$$$$$$$     $      $     $    $
$     $     $     $      $    $
$     $     $    $$$$$$   $$$$
	

課題: 子プロセスが"banner"を実行して出力をパイプを通して親プロセスに渡し、
親プロセスは"sed"を使って"#"を任意の文字に置き換えるプログラムを作成してください。
パイプはCシステムコール"pipe"を使ってください。 (テンプレート)

: プログラムの実行形式のファイル名を "ProcPipe1"とし, 任意の文字を"@", 引数を"Aizu"としたとき

コマンド


	% ./ProcPipe1 @ Aizu
	
結果

   @
  @ @       @    @@@@@@  @    @
 @   @      @        @   @    @
@     @     @       @    @    @
@@@@@@@     @      @     @    @
@     @     @     @      @    @
@     @     @    @@@@@@   @@@@