سؤال

هذا هو الكود الخاص بي:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <readline/readline.h>

#define NUMPIPES 2

int main(int argc, char *argv[]) {
    char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
    int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
    pid_t pid;

    pipe(fdPipe);

    while(1) {
        bBuffer = readline("Shell> ");

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

        sPtr = bBuffer;
        pCount = -1;

        do {
            aPtr = strsep(&sPtr, "|");
            pipeComms[++pCount] = aPtr;
        } while(aPtr);

        for(i = 0; i < pCount; i++) {
            aCount = -1;

            do {
                aPtr = strsep(&pipeComms[i], " ");
                cmdArgs[++aCount] = aPtr;
            } while(aPtr);

            cmdArgs[aCount] = 0;

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

                if(pid == 0) {
                    if(i == 0) {
                        close(fdPipe[0]);

                        dup2(fdPipe[1], STDOUT_FILENO);

                        close(fdPipe[1]);
                    } else if(i == 1) {
                        close(fdPipe[1]);

                        dup2(fdPipe[0], STDIN_FILENO);

                        close(fdPipe[0]);
                    }

                    execvp(cmdArgs[0], cmdArgs);
                    exit(1);
                } else {
                    lPids[i] = pid;

                    /*waitpid(pid, &status, 0);

                    if(WIFEXITED(status)) {
                        printf("[%d] TERMINATED (Status: %d)\n",
                            pid, WEXITSTATUS(status));
                    }*/
                }
            }
        }

        for(i = 0; i < pCount; i++) {
            waitpid(lPids[i], &status, 0);

            if(WIFEXITED(status)) {
                printf("[%d] TERMINATED (Status: %d)\n",
                    lPids[i], WEXITSTATUS(status));
            }
        }
    }

    return 0;
}

(تم تحديث الكود ليعكس التغييرات التي اقترحتها إجابتان أدناه، ولا يزال لا يعمل كما ينبغي...)

إليك حالة الاختبار التي فشل فيها هذا:

nazgulled ~/Projects/SO/G08 $ ls -l
total 8
-rwxr-xr-x 1 nazgulled nazgulled  7181 2009-05-27 17:44 a.out
-rwxr-xr-x 1 nazgulled nazgulled   754 2009-05-27 01:42 data.h
-rwxr-xr-x 1 nazgulled nazgulled  1305 2009-05-27 17:50 main.c
-rwxr-xr-x 1 nazgulled nazgulled   320 2009-05-27 01:42 makefile
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled  9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
-rwxr-xr-x 1 nazgulled nazgulled    16 2009-05-27 17:19 test
nazgulled ~/Projects/SO/G08 $ ./a.out 
Shell> ls -l|grep prog
[4804] TERMINATED (Status: 0)
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled  9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o

المشكلة هي أنني يجب أن أعود إلى صدفتي بعد ذلك، ويجب أن أرى "Shell>" في انتظار المزيد من المدخلات.يمكنك أيضًا ملاحظة أنك لا ترى رسالة مشابهة لـ "تم إنهاء [4804] (الحالة:0)" (ولكن بمعرف تعريف مختلف)، مما يعني أن العملية الثانية لم تنته.

أعتقد أن الأمر له علاقة بـ grep، لأن هذا يعمل:

nazgulled ~/Projects/SO/G08 $ ./a.out 
Shell> echo q|sudo fdisk /dev/sda
[4838] TERMINATED (Status: 0)

The number of cylinders for this disk is set to 1305.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): 
[4839] TERMINATED (Status: 0)

يمكنك بسهولة رؤية رسالتين "إنهاء"...

إذًا، ما الخطأ في الكود الخاص بي؟

هل كانت مفيدة؟

المحلول

حتى بعد خروج الأمر الأول لخط الأنابيب الخاص بك (وبالتالي إغلاقه stdout=~fdPipe[1])، لا يزال الوالد لديه fdPipe[1] يفتح.

وبالتالي، فإن الأمر الثاني لخط الأنابيب لديه stdin=~fdPipe[0] الذي لا يحصل أبدًا على EOF، لأن نقطة النهاية الأخرى للأنبوب لا تزال مفتوحة.

تحتاج إلى إنشاء جديد pipe(fdPipe) لكل |, وتأكد من إغلاق نقطتي النهاية في الأصل؛أي.

for cmd in cmds
    if there is a next cmd
        pipe(new_fds)
    fork
    if child
        if there is a previous cmd
            dup2(old_fds[0], 0)
            close(old_fds[0])
            close(old_fds[1])
        if there is a next cmd
            close(new_fds[0])
            dup2(new_fds[1], 1)
            close(new_fds[1])
        exec cmd || die
    else
        if there is a previous cmd
            close(old_fds[0])
            close(old_fds[1])
        if there is a next cmd
            old_fds = new_fds
if there are multiple cmds
    close(old_fds[0])
    close(old_fds[1])

أيضًا، لكي تكون أكثر أمانًا، يجب عليك التعامل مع حالة fdPipe و {STDIN_FILENO,STDOUT_FILENO} التداخل قبل تنفيذ أي من close و dup2 عمليات.قد يحدث هذا إذا تمكن شخص ما من بدء تشغيل الصدفة الخاصة بك مع إغلاق stdin أو stdout، وسيؤدي إلى ارتباك كبير مع الكود هنا.

يحرر

   fdPipe1           fdPipe3
      v                 v
cmd1  |  cmd2  |  cmd3  |  cmd4  |  cmd5
               ^                 ^
            fdPipe2           fdPipe4

بالإضافة إلى التأكد من إغلاق نقاط نهاية الأنبوب في الأصل، كنت أحاول توضيح هذه النقطة fdPipe1, fdPipe2, ، إلخ. لا تستطيع يكون هو نفسه pipe().

/* suppose stdin and stdout have been closed...
 * for example, if your program was started with "./a.out <&- >&-" */
close(0), close(1);

/* then the result you get back from pipe() is {0, 1} or {1, 0}, since
 * fd numbers are always allocated from the lowest available */
pipe(fdPipe);

close(0);
dup2(fdPipe[0], 0);

أعلم أنك لا تستخدم close(0) في الكود الحالي الخاص بك، ولكن الفقرة الأخيرة تحذرك من الحذر من هذه الحالة.

يحرر

الأتى الحد الأدنى التغيير في الكود الخاص بك يجعله يعمل في حالة الفشل المحددة التي ذكرتها:

@@ -12,6 +12,4 @@
     pid_t pid;

-    pipe(fdPipe);
-
     while(1) {
         bBuffer = readline("Shell> ");
@@ -29,4 +27,6 @@
         } while(aPtr);

+        pipe(fdPipe);
+
         for(i = 0; i < pCount; i++) {
                 aCount = -1;
@@ -72,4 +72,7 @@
         }

+        close(fdPipe[0]);
+        close(fdPipe[1]);
+
         for(i = 0; i < pCount; i++) {
                 waitpid(lPids[i], &status, 0);

لن ينجح هذا مع أكثر من أمر واحد في المسار؛لذلك، ستحتاج إلى شيء مثل هذا:(لم يتم اختباره، حيث يتعين عليك إصلاح أشياء أخرى أيضًا)

@@ -9,9 +9,7 @@
 int main(int argc, char *argv[]) {
     char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
-    int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
+    int fdPipe[2], fdPipe2[2], pCount, aCount, i, status, lPids[NUMPIPES];
     pid_t pid;

-    pipe(fdPipe);
-
     while(1) {
         bBuffer = readline("Shell> ");
@@ -32,4 +30,7 @@
                 aCount = -1;

+                if (i + 1 < pCount)
+                    pipe(fdPipe2);
+
                 do {
                         aPtr = strsep(&pipeComms[i], " ");
@@ -43,11 +44,12 @@

                         if(pid == 0) {
-                                if(i == 0) {
-                                        close(fdPipe[0]);
+                                if(i + 1 < pCount) {
+                                        close(fdPipe2[0]);

-                                        dup2(fdPipe[1], STDOUT_FILENO);
+                                        dup2(fdPipe2[1], STDOUT_FILENO);

-                                        close(fdPipe[1]);
-                                } else if(i == 1) {
+                                        close(fdPipe2[1]);
+                                }
+                                if(i != 0) {
                                         close(fdPipe[1]);

@@ -70,4 +72,17 @@
                         }
                 }
+
+                if (i != 0) {
+                    close(fdPipe[0]);
+                    close(fdPipe[1]);
+                }
+
+                fdPipe[0] = fdPipe2[0];
+                fdPipe[1] = fdPipe2[1];
+        }
+
+        if (pCount) {
+            close(fdPipe[0]);
+            close(fdPipe[1]);
         }

نصائح أخرى

من المفترض أن يكون لديك مخرج خطأ بعد execvp() - فسوف يفشل في وقت ما.

exit(EXIT_FAILURE);

كما يشير @uncleo، يجب أن تحتوي قائمة الوسيطات على مؤشر فارغ للإشارة إلى النهاية:

cmdArgs[aCount] = 0;

ليس من الواضح بالنسبة لي أنك تركت كلا البرنامجين يعملان مجانًا - يبدو أنك تحتاج إلى إنهاء البرنامج الأول في المسار قبل بدء البرنامج الثاني، وهذه ليست وصفة للنجاح إذا تم حظر البرنامج الأول بسبب امتلاء الأنبوب.

جوناثان لديه الفكرة الصحيحة.أنت تعتمد على العملية الأولى لتشعب جميع العمليات الأخرى.يجب أن يتم تشغيل كل واحد حتى الاكتمال قبل أن يتم تفرع التالي.

بدلًا من ذلك، قم بتفرع العمليات في حلقة كما تفعل، ولكن انتظرها خارج الحلقة الداخلية (في الجزء السفلي من الحلقة الكبيرة لموجه الصدفة).

loop //for prompt
    next prompt
    loop //to fork tasks, store the pids
        if pid == 0 run command
        else store the pid
    end loop
    loop // on pids
        wait
    end loop
end loop

أعتقد أن عملياتك المتشعبة ستستمر في التنفيذ.

حاول إما:

  • تغييره إلى "إرجاع execvp"
  • أضف "الخروج (1) ؛" بعد execvp

إحدى المشاكل المحتملة هي أن cmdargs قد تحتوي على بيانات غير صحيحة في نهايتها.من المفترض أن تنهي تلك المصفوفة بمؤشر فارغ قبل تمريرها إلى execvp().

يبدو أن grep يقبل STDIN، لذلك قد لا يسبب ذلك أي مشاكل (حتى الآن).

يتم حساب مرجع واصفات الملف من الأنبوب، ويتم زيادتها مع كل تفرع.لكل شوكة، يجب عليك إصدار إغلاق لكلا الواصفين لتقليل العدد المرجعي إلى الصفر والسماح بإغلاق الأنبوب.أخمن.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top