信号ファイル転送

こんにちは、ハラジテリ。 確かに誰もがLinuxのシグナルとその目的を知っています。 しかし、今日、私には、彼らの型にはまらないアプリケーションについてお話したいと思います。



このタスクは非常に手間がかかり、シグナルおよびビット単位の操作のスキルをトレーニングすることを目的としています。 一般的に、タスク:

プログラムは、シグナルのみを使用して、コマンドライン引数として指定されたファイルを親に渡すプロセスを生成する必要があります。 親は結果のファイルを標準出力に出力します。





解決策は非常に簡単です。モールス信号のような信号を使用します。「ポイント」と「ダッシュ」の代わりにSIGUSR1SIGUSR2のみを使用します。



解決策



シグナルSIGUSR2SIGUSR1を使用して、ビット単位でファイルを転送します

SIGUSR2-ゼロに等しいビット、 SIGUSR1-1に等しいビットとします。



データを送信する


ファイルからバイトを変数cに読み込みます。

0b10000000または128に等しいカウンター変数を作成します。

c AND (「ビット単位のand」を意味する) カウンターが1に等しい場合、最上位ビットは1に等しく、SIGUSR1を送信します。それ以外の場合はSIGUSR2を送信します。

カウンターを半分に分割します(0b01000000または64になります)。つまり、左側の2番目のビットに渡します。

カウンターがゼロになるまで繰り返します。 次に、ファイルから新しいバイトを読み取ります。



Cでは、次のようになります。

while (read(fd, &c, 1) > 0) { for ( i = 128; i >= 1; i /= 2) { if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); }
      
      





データ受信


変数out_charで 、最初はゼロに等しい値を受け入れます。

counterはゼロではありませんが、信号を次のように処理します。

SIGUSR1が来た場合、 out_char + = counter 、次にcounter / = 2

SIGUSR1が来た場合、 counter / = 2



シグナルのハンドラーを作成します。

 // SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; } // SIGUSR2 void zero(int signo) { counter/=2; }
      
      





作業オプション



ここで、親または子の予期しない死を考慮する必要があります。 子が死んだ場合、すべてが単純です-親はSIGCHLDを受け取ります。

 // SIGCHLD void childexit(int signo) { exit(EXIT_SUCCESS); }
      
      





子供にとってはもう少し難しくなります。子供が親の死について何らかの形で知らされるという保証はありません。 したがって、一定の時間後に他の信号が送信されない場合、 SIGALRMを送信するようカーネルに要求します。 これを送信ループに追加します。

 while (read(fd, &c, 1) > 0) { // SIGALRM          alarm(1); //   for ( i = 128; i >= 1; i /= 2) { if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); } }
      
      





親による子からの信号の受信を確認するメカニズムを追加します。 つまり、親がビットの受信を確認するまで、子は次のビットを送信しません。



これは単純に行われ、 1および0関数では、応答送信を追加する必要があります。 シグナルSIGUSR1で応答します。 変更後、関数は次のようになります。

 // SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; kill(pid, SIGUSR1); } // SIGUSR2 void zero(int signo) { counter/=2; kill(pid, SIGUSR1); }
      
      





そして、子供が確認を待つために、 sigsuspend(&set)を追加します:

 while (read(fd, &c, 1) > 0){ // SIGALRM          alarm(1); //   for ( i = 128; i >= 1; i /= 2){ if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); //      //     sigsuspend(&set); }
      
      





確認信号が親から送信されたときに実行される機能は空である必要がありますが、そうでない場合はデフォルト信号の出力であるアクションが実行されます。



実際に機能して設定します:

 // Nothing void empty(int signo) { } // SET sigemptyset(&set); //    // SIGUSR1 - empty() struct sigaction act_empty; memset(&act_empty, 0, sizeof(act_empty)); act_empty.sa_handler = empty; sigfillset(&act_empty.sa_mask); sigaction(SIGUSR1, &act_empty, NULL); // SIGALRM - parentexit() struct sigaction act_alarm; memset(&act_alarm, 0, sizeof(act_alarm)); act_alarm.sa_handler = parentexit; sigfillset(&act_alarm.sa_mask); sigaction(SIGALRM, &act_alarm, NULL);
      
      





親では、シグナルマスクは次のようになります。

 // SIGCHLD - exit struct sigaction act_exit; memset(&act_exit, 0, sizeof(act_exit)); act_exit.sa_handler = childexit; sigfillset(&act_exit.sa_mask); sigaction(SIGCHLD, &act_exit, NULL); // SIGUSR1 - one() struct sigaction act_one; memset(&act_one, 0, sizeof(act_one)); act_one.sa_handler = one; sigfillset(&act_one.sa_mask); sigaction(SIGUSR1, &act_one, NULL); // SIGUSR2 - zero() struct sigaction act_zero; memset(&act_zero, 0, sizeof(act_zero)); act_zero.sa_handler = zero; sigfillset(&act_zero.sa_mask); sigaction(SIGUSR2, &act_zero, NULL); //   sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL );
      
      





プログラムの作業の過程で新しいプロセスが発生し、すぐにシグナル(データ)の送信が開始れるため、フォークの前にsigprocmask(SIG_BLOCK、&set、NULL)を実行する必要があります。そうしないと、レース効果(レース条件)。

その結果、プログラムは次のようになります。

 int out_char = 0, counter = 128; pid_t pid; //      // SIGCHLD void childexit(int signo) { exit(EXIT_SUCCESS); } // SIGALRM void parentexit(int signo) { exit(EXIT_SUCCESS); } // Nothing void empty(int signo) { } // SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; kill(pid, SIGUSR1); } // SIGUSR2 void zero(int signo) { counter/=2; kill(pid, SIGUSR1); } int main(int argc, char ** argv){ if (argc != 2) { fprintf(stderr, "Use: %s [source]\n", argv[0]); exit(EXIT_FAILURE); } pid_t ppid = getpid(); //   ,    sigset_t set; //     //  SIGCHLD -  struct sigaction act_exit; memset(&act_exit, 0, sizeof(act_exit)); act_exit.sa_handler = childexit; sigfillset(&act_exit.sa_mask); sigaction(SIGCHLD, &act_exit, NULL); // SIGUSR1 - one() struct sigaction act_one; memset(&act_one, 0, sizeof(act_one)); act_one.sa_handler = one; sigfillset(&act_one.sa_mask); sigaction(SIGUSR1, &act_one, NULL); // SIGUSR2 - zero() struct sigaction act_zero; memset(&act_zero, 0, sizeof(act_zero)); act_zero.sa_handler = zero; sigfillset(&act_zero.sa_mask); sigaction(SIGUSR2, &act_zero, NULL); //sigemptyset(&set); //   sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL ); sigemptyset(&set); //  pid = fork(); //  () if (pid == 0) { unsigned int fd = 0; char c = 0; sigemptyset(&set); //    // SIGUSR1 - empty() struct sigaction act_empty; memset(&act_empty, 0, sizeof(act_empty)); act_empty.sa_handler = empty; sigfillset(&act_empty.sa_mask); sigaction(SIGUSR1, &act_empty, NULL); // SIGALRM - parentexit() struct sigaction act_alarm; memset(&act_alarm, 0, sizeof(act_alarm)); act_alarm.sa_handler = parentexit; sigfillset(&act_alarm.sa_mask); sigaction(SIGALRM, &act_alarm, NULL); if ((fd = open(argv[1], O_RDONLY)) < 0 ){ perror("Can't open file"); exit(EXIT_FAILURE); } int i; while (read(fd, &c, 1) > 0){ // SIGALRM          alarm(1); //   for ( i = 128; i >= 1; i /= 2){ if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); //     //      sigsuspend(&set); } } //   exit(EXIT_SUCCESS); } errno = 0; //      do { if(counter == 0){ // Whole byte write(STDOUT_FILENO, &out_char, 1); // fflush(stdout); counter=128; out_char = 0; } sigsuspend(&set); //     } while (1); exit(EXIT_SUCCESS); }
      
      





プログラムのソースコードはこちらからダウンロードできます



これは、シグナルのみを使用して、あるプログラムから別のプログラムにファイルを転送する方法です。 確かに、このアプローチはあまり効果的ではなく、実際のプロジェクトに応用できるとは思いません。



ご清聴ありがとうございました!



All Articles