-1

次のコードを考えてみましょう (ネーミングの問題、構造化の問題などがあることを書かないでください。私もこれを知っています)。ランダムに生成された 3 つの子の x、y、z および r (および pid) 番号を書き出すように記述されていますが、2 つまたは 1 つの「Got this...」行しか出力されないことがよくあります。 、 どうして。何が問題なのか、または私のコードを修正してください。

#include <stdlib.h> 
#include <stdio.h>
#include <sys/types.h> //fork
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h> //lock
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include "sys/ipc.h"
#include "sys/sem.h"

int child;
int cs[3];
int fd[2];
int t;
int parent;
int child;

void okay(int sign)
{
  t = 0;
}

void rr(int sign)
{
  char b[50];
  while(read(fd[0], &b, 50)<0) sleep(0.5);
  printf("Got this: %s \n", b);
}

void ch(int argc, char** argv)
{
  printf("Mypid: %i\n", getpid());
  close(fd[0]);
  while(t==1) sleep(1);
  srand((unsigned)time(NULL)); // init
  int x,y,z,r,pid;
  x = rand() % 101; y = rand() % 101; z = rand() % 101; r = rand() % 101;
  pid = getpid();
  char b[50];
  sprintf(b, "%i %i %i %i %i", pid, x, y, z, r);
  while(write(fd[1], b, 50)<0) sleep(0.2);
  kill(parent, SIGUSR2);
  close(fd[1]);
}

int main(int argc, char** argv)
{
  if(argc < 4)
  {
    printf("Too few args!\n");
    return 0;
  }
  pipe(fd);
  t = 1;
  parent = getpid();
  signal(SIGUSR1, okay);
  child = fork();
  if(child < 0) perror("FORK");
  if(child > 0)
  {
    cs[0] = child;

    child = fork();
    if(child < 0) perror("FORK");
    if(child > 0)
    {

      cs[1] = child;

      child = fork();
      if(child < 0) perror("FORK");
      if(child > 0)
      {
        cs[2] = child; // MAIN
        printf("%i %i %i\n", cs[0], cs[1], cs[2]);
        close(fd[1]);
        signal(SIGUSR2, rr);
        kill(cs[0], SIGUSR1); kill(cs[1], SIGUSR1); kill(cs[2], SIGUSR1);
        int status;

            waitpid(cs[0], &status, 0);
            waitpid(cs[1], &status, 0);
            waitpid(cs[2], &status, 0);
            close(fd[0]);

      }else
      { // ch 3
        ch(argc, argv);
      }   

    }else
    { // ch 2
      ch(argc, argv);
    }  

  }else
  { // ch 1
    ch(argc, argv);
  }
  return 0;
}
4

1 に答える 1

1

書き直された答え

さまざまなバージョンのコードを修正しても、説明されている動作を得ることができました。たとえば、診断を含むバージョンのコードから取得したトレースの1つは次のとおりです。

14607 at work
Children: 14608 14609 14610
Children signalled
Child 14609: signal 30 - setting t to 0
Child 14608: signal 30 - setting t to 0
Child 14610: signal 30 - setting t to 0
Child 14609: at work
Child 14610: at work
Child 14608: at work
Child 14609: sending 14609 65 24 97 0
Child 14609: exiting
Child 14610: sending 14610 87 17 23 57
Adult 14607: signal 31 - reading input
Child 14610: exiting
Child 14608: sending 14608 5 89 95 8
Child 14608: exiting
Adult 14607: got <<14609 65 24 97 0>>
Adult 14607: signal 31 - reading input
Adult 14607: got <<14610 87 17 23 57>>
Child 1 ended
Child 2 ended
Child 3 ended
14607 exiting

親が14609と14610からデータを取得したが、14608からは取得していないことがわかります。これは、シグナルの使用に起因すると考えられます。それらはIPCにとって非常に貧弱なメカニズムです。そして、この場合、彼らはタイミングに信頼できないようです。sigaction()これは、すべての信号をブロックするように設定されたsa.sa_mask値を使用したコードでした( sigfillset(&sa.sa_mask))。

ただし、実際には、子から親に戻るシグナルを使用する必要はありません。親が子に織り方を通知するためのシグナルハンドラーを残しましたが、volatile sig_atomic_t変数の値(t名前はまだ)を1から0に変更するだけに簡略化しました。式は「使用」することです。信号番号パラメーター(signコードで呼び出されます); Mac OSX10.7.5でGCC4.7.1を使用してコンパイルするときの警告を回避します。

gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
    pipes-13905948.c -o pipes-13905948

srand()時間をプロセスのPIDと混合して、各子から異なる値を与えるシード(PIDを単独で使用することもそれを行います)。オリジナルの16個のヘッダー(2回の繰り返しを含む)を7に削除しました。rr()親が子からの信号に応答しなくなったため、削除しました。コードを再構築しましたmain()そのため、ページのRHSから飛び出すことはありません。コードには、何が起こっているかについての豊富な診断が含まれています。メッセージの大部分にメッセージの一部としてPIDが印刷されている場合、このような複数のプロセスを処理するときに役立ちます。「Parent」の代わりに「Adult」を使用したので、出力は「Child」とタグ付けされた行にきちんと揃えられます。子がフォークされる前にシグナルハンドラが設定されることに注意してください。マルチCPUマシンでは、プロセスが実行される順序が保証されていないため、分岐するまで信号のセットアップをそのままにしておくことは、せいぜい賢明ではなく、最悪の場合、予期しない死につながる可能性があります。

シグナルハンドラーでの読み取りは、main();の親コードでの読み取りに置き換えられます。これは、入力を処理するためのはるかに満足のいく方法です。シグナルハンドラーでは、できる限り実行しないようにする必要があります。C標準は、これ以上のことを確実にサポートしていません。

ISO / IEC 9899:2011§7.14.1signal機能

abort¶5または関数を呼び出した結果以外の結果としてシグナルが発生したraise場合、シグナルハンドラーが、値を割り当てる以外にロックフリーのアトミックオブジェクトではない静的またはスレッドの保存期間を持つオブジェクトを参照すると、動作は定義されません。として宣言されたオブジェクトに対して、またはシグナルハンドラーが、関数、関数、 関数、または関数の呼び出しを引き起こしたシグナルに対応するシグナル番号に等しい最初の引数を持つ関数volatile sig_atomic_t以外の標準ライブラリ内の関数を呼び出すハンドラ。abort_Exitquick_exitsignal

POSIXの方が寛容ですが、シグナルハンドラーで行うことには十分注意する必要があり、シグナルハンドラーで行うことはできるだけ少なくする必要があります。

これらの変更により、次のコードが作成されます。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static int fd[2];
static volatile sig_atomic_t t = 1;
static int parent;

static void okay(int sign)
{
    t = (sign == 0);
}

static void ch(void)
{
    int pid = getpid();
    printf("Child %i: at work\n", pid);
    close(fd[0]);
    while (t == 1)
    {
        printf("Child %d: pausing on t\n", pid);
        pause();
    }
    srand((unsigned)time(NULL) ^ pid);
    int x = rand() % 101;
    int y = rand() % 101;
    int z = rand() % 101;
    int r = rand() % 101;
    char b[50];
    sprintf(b, "%i %i %i %i %i", pid, x, y, z, r);
    printf("Child %d: sending %s\n", pid, b);
    while (write(fd[1], b, strlen(b)) < 0)
        printf("Child %d: write failed\n", pid);

    close(fd[1]);
    printf("Child %d: exiting\n", pid);
    exit(0);
}

int main(void)
{
    int cs[3];
    pipe(fd);
    parent = getpid();
    printf("%d at work\n", parent);

    struct sigaction sa;
    sa.sa_flags = 0;
    sigfillset(&sa.sa_mask);

    sa.sa_handler = okay;
    sigaction(SIGUSR1, &sa, 0);

    if ((cs[0] = fork()) < 0)
        perror("fork 1");
    else if (cs[0] == 0)
        ch();
    else if ((cs[1] = fork()) < 0)
        perror("fork 2");
    else if (cs[1] == 0)
        ch();
    else if ((cs[2] = fork()) < 0)
        perror("fork 3");
    else if (cs[2] == 0)
        ch();
    else
    {
        printf("Children: %i %i %i\n", cs[0], cs[1], cs[2]);
        close(fd[1]);

        kill(cs[0], SIGUSR1);
        kill(cs[1], SIGUSR1);
        kill(cs[2], SIGUSR1);
        printf("Children signalled\n");

        char buffer[64];
        int nbytes;
        while ((nbytes = read(fd[0], buffer, sizeof(buffer)-1)) > 0)
        {
            buffer[nbytes] = '\0';
            printf("Adult %d: read <<%s>>\n", parent, buffer);
        }

        int status;
        waitpid(cs[0], &status, 0);
        printf("Child 1 ended\n");
        waitpid(cs[1], &status, 0);
        printf("Child 2 ended\n");
        waitpid(cs[2], &status, 0);
        printf("Child 3 ended\n");
        close(fd[0]);
    }
    printf("%d exiting\n", (int)getpid());
    return 0;
}

コードはまだエラー処理に不安定です。未チェックのシステムコールや報告されていない結果(子のステータスなど)がたくさんあります。失敗時に書き込みを再試行することについては確信が持てませんが、コードが実行されることはありませんでした。

これは、コードの改訂版からのトレースです。

15745 at work
Children: 15746 15747 15748
Children signalled
Child 15746: at work
Child 15746: sending 15746 63 4 70 89
Child 15748: at work
Child 15746: exiting
Child 15747: at work
Adult 15745: read <<15746 63 4 70 89>>
Child 15748: sending 15748 44 0 99 37
Child 15748: exiting
Child 15747: sending 15747 3 69 68 97
Adult 15745: read <<15748 44 0 99 37>>
Child 15747: exiting
Adult 15745: read <<15747 3 69 68 97>>
Child 1 ended
Child 2 ended
Child 3 ended
15745 exiting

数回、次のような入力を受け取りました。

Adult 15734: read <<15736 83 95 64 2915737 42 63 66 89>>

これは、プロセス15736と15737の出力を読み取りからの単一の結果に結合します。私はそれについて満足していません。AFAIK、読み取りは、個別の子のアトミック書き込みを個別のメッセージとして取得する必要があります。私はそれをさらに研究することなく、MacOSXの癖に落とし込むつもりです。


元の回答

signal()ではなくを使用しているためsigaction()、シグナルハンドラーが呼び出される前にシグナルハンドラーがSIGDFLにリセットされる可能性があります。okay()次を追加することで修正できます。

void okay(int sign)
{
    signal(sign, okay);
    t = 0;
}

signal()ハンドラーでからの戻り値をチェックすることで、それが問題であるかどうかを監視できます。

コードの残りの部分は現在使用されていませんt(ただし、に設定されています1main()不正確な観察!

より多くの印刷操作を行うことで、デバッグを容易にすることができます。ループを使用して子を殺して収集することができます(ただし、ループを作成したように書き出すことは可能です。ただし、1行に3つの関数呼び出しを配置し​​ないでください)。

于 2012-12-16T22:21:09.253 に答える