8

この前の質問では、私自身のシェル コードのほとんどを投稿しました私の次のステップは、フォアグラウンドとバックグラウンドのプロセス実行を実装し、それらが終了するのを適切に待って、「ゾンビ」のままにならないようにすることです。

バックグラウンドで実行する可能性を追加する前は、すべてのプロセスがフォアグラウンドで実行されていました。そのために、execvp() でプロセスを実行した後、単純に wait(NULL) を呼び出しました。ここで、最後の引数として「&」文字をチェックし、そこにある場合は、wait(NULL) を呼び出さずにバックグラウンドでプロセスを実行します。プロセスはバックグラウンドで正常に実行され、シェルに戻ります。

これはすべて正常に機能しています (私は思います)。現在の問題は、バックグラウンド プロセスが「ゾンビ」のままにならないように、何らかの方法で wait() (または waitpid() ?) を呼び出す必要があることです。それは私の問題です、私はそれを行う方法がわかりません...

私は SIGCHLD を処理し、そこで何かをしなければならないと信じていますが、wait(NULL) を childSignalHandler() に追加しようとしたため、SIGCHLD シグナルがいつ送信されるかをまだ完全には理解していませんが、機能しませんでした。バックグラウンドでプロセスを実行すると、childSignalHandler() 関数が呼び出され、その結果、wait(NULL) が呼び出されました。つまり、「バックグラウンド」プロセスが終了するまでシェルで何もできませんでした。シグナルハンドラーで待機しているため、バックグラウンドで実行されなくなりました。

このすべてで何が欠けていますか?

最後にもう 1 つ、この演習の一環として、プロセスの終了など、プロセスの状態の変化も出力する必要があります。したがって、それに関する洞察も本当に高く評価されています。

これは現時点での私の完全なコードです:

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

#include "data.h" // Boolean typedef and true/false macros


void childSignalHandler(int signum) {
    //
}

int main(int argc, char **argv) {
    char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
    bool background;
    ssize_t rBytes;
    int aCount;
    pid_t pid;

    //signal(SIGINT, SIG_IGN);

    signal(SIGCHLD, childSignalHandler);

    while(1) {
        write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
        rBytes = read(0, bBuffer, BUFSIZ-1);

        if(rBytes == -1) {
            perror("read");
            exit(1);
        }

        bBuffer[rBytes-1] = '\0';

        if(!strcasecmp(bBuffer, "exit")) {
            exit(0);
        }

        sPtr = bBuffer;
        aCount = 0;

        do {
            aPtr = strsep(&sPtr, " ");
            pArgs[aCount++] = aPtr;
        } while(aPtr);

        background = FALSE;

        if(!strcmp(pArgs[aCount-2], "&")) {
            pArgs[aCount-2] = NULL;
            background = TRUE;
        }

        if(strlen(pArgs[0]) > 1) {
            pid = fork();

            if(pid == -1) {
                perror("fork");
                exit(1);
            }

            if(pid == 0) {
                execvp(pArgs[0], pArgs);
                exit(0);
            }

            if(!background) {
                wait(NULL);
            }
        }
    }

    return 0;
}
4

4 に答える 4

5

あなたを助けるためのさまざまなオプションがありwaitpid()ます(POSIX標準からの引用):

続き

waitpid()関数は、ジョブ制御停止から継続してからステータスが報告されていないpidによって指定された継続子プロセスのステータスを報告するものとします。

WNOHANG

waitpid()関数は、pidで指定された子プロセスの1つでステータスがすぐに利用できない場合、呼び出し元のスレッドの実行を一時停止してはなりません。

特に、WNOHANGを使用すると、プロセスで死体の待機をブロックすることなく、収集する死体があるかどうかを確認できます。

呼び出し元のプロセスでSA_NOCLDWAITが設定されているかSIGCHLDがSIG_IGNに設定されていて、プロセスに待機していない子がない場合、ゾンビプロセスに変換された子は、呼び出し元のスレッドを含むプロセスのすべての子が終了するまでブロックされます。 wait()とwaitpid()は失敗し、errnoを[ECHILD]に設定します。

SIGCHLDなどを無視したくない場合は、シグナルハンドラーがフラグを設定して、メインループに「おっと、死んだ子がいます。その死体を集めに行ってください」と伝える必要があります。

SIGCONTシグナルとSIGSTOPシグナルも、あなたにとって重要です。これらは、それぞれ子プロセスを再起動および停止するために使用されます(このコンテキストでは、とにかく)。

Rochkindの本またはStevensの本を見るのをお勧めします-それらはこれらの問題を詳細にカバーしています。

于 2009-05-22T23:53:57.137 に答える
3

これで始められるはずです。主な違いは、子ハンドラーを取り除きwaitpid、メイン ループにいくつかのフィードバックを追加したことです。テスト済みで機能していますが、明らかにより多くの TLC が必要です。

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

int main(int argc, char **argv) {
        char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
        int background;
        ssize_t rBytes;
        int aCount;
        pid_t pid;
        int status;
        while(1) {
                pid = waitpid(-1, &status, WNOHANG);
                if (pid > 0)
                        printf("waitpid reaped child pid %d\n", pid);
                write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
                rBytes = read(0, bBuffer, BUFSIZ-1);
                if(rBytes == -1) {
                        perror("read");
                        exit(1);
                }
                bBuffer[rBytes-1] = '\0';
                if(!strcasecmp(bBuffer, "exit")) 
                        exit(0);
                sPtr = bBuffer;
                aCount = 0;
                do {
                        aPtr = strsep(&sPtr, " ");
                        pArgs[aCount++] = aPtr;
                } while(aPtr);
                background = (strcmp(pArgs[aCount-2], "&") == 0);
                if (background)
                        pArgs[aCount-2] = NULL;
                if (strlen(pArgs[0]) > 1) {
                        pid = fork();
                        if (pid == -1) {
                                perror("fork");
                                exit(1);
                        } else if (pid == 0) {
                                execvp(pArgs[0], pArgs);
                                exit(1);
                        } else if (!background) {
                                pid = waitpid(pid, &status, 0);
                                if (pid > 0)
                                        printf("waitpid reaped child pid %d\n", pid);
                        }
                }
        }
        return 0;
}

編集:waitpid() WNOHANGを使用すれば、シグナル処理を元に戻すことは難しくありません。waitpid()ループの先頭からシグナル ハンドラーに要素を移動するのと同じくらい簡単です。ただし、次の 2 つの点に注意する必要があります。

まず、「フォアグラウンド」プロセスでさえ SIGCHLD を送信します。フォアグラウンド プロセスは 1 つしか存在できないためfork()、フォアグラウンドとバックグラウンドの特別な処理を行いたい場合は、フォアグラウンド pid ( からの親の戻り値) をシグナル ハンドラから見える変数に格納するだけです。

次に、現在、標準入力 (read()メイン ループの先頭) でブロッキング I/O を実行しています。read()SIGCHLD が発生したときにブロックされる可能性が非常に高く、システム コールが中断されます。OS によっては、システム コールが自動的に再起動される場合や、処理が必要なシグナルが送信される場合があります。

于 2009-05-23T00:12:54.400 に答える
2

以下を使用できます。

if(!background)
    pause();

このようにして、プロセスはシグナルを受信するまでブロックSIGCHLDし、シグナルハンドラーは待機を行います。

于 2011-05-07T09:58:28.897 に答える
0

グローバル変数を使用する代わりに、別の解決策を考えました。

if(!background) {
    signal(SIGCHLD, NULL);

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

フォアグラウンドプロセスを実行している場合は、SIGCHLDのハンドラーを「削除」して、呼び出されないようにします。次に、waitpid()の後で、ハンドラーを再度設定します。このように、バックグラウンドプロセスのみが処理されます。

このソリューションに何か問題があると思いますか?

于 2009-05-24T16:24:38.590 に答える