2009-05-22 16 views
8

Trong this câu hỏi trước tôi đã đăng hầu hết mã shell của riêng mình. Bước tiếp theo của tôi là thực hiện tiến trình nền trước và nền và thực hiện đúng cách để chúng kết thúc, vì vậy chúng không tồn tại dưới dạng "thây ma".Làm thế nào để đúng cách chờ đợi các quy trình nền trước/nền trong vỏ của riêng tôi trong C?

Trước khi thêm khả năng chạy chúng trong nền, tất cả các quy trình đang chạy ở nền trước. Và cho rằng, tôi chỉ đơn giản gọi là chờ đợi (NULL) sau khi thực hiện bất kỳ quá trình với execvp(). Bây giờ, tôi kiểm tra ký tự '&' là đối số cuối cùng và nếu nó ở đó, chạy quá trình trong nền bằng cách không gọi chờ (NULL) và quá trình có thể chạy vui vẻ trong nền tôi sẽ quay trở lại shell của tôi.

Đây là tất cả hoạt động đúng (tôi nghĩ), vấn đề bây giờ, là tôi cũng cần phải gọi wait() (hoặc waitpid()?) Bằng cách nào đó để quá trình nền không còn "zombie". Đó là vấn đề của tôi, tôi không chắc chắn làm thế nào để làm điều đó ...

Tôi tin rằng mình phải xử lý SIGCHLD và làm điều gì đó ở đó, nhưng tôi chưa hiểu đầy đủ khi tín hiệu SIGCHLD được gửi vì tôi cũng cố gắng thêm wait (NULL) vào childSignalHandler() nhưng nó không hoạt động vì ngay sau khi tôi thực hiện một tiến trình trong nền, hàm childSignalHandler() đã được gọi và do đó, sự chờ đợi (NULL), có nghĩa là tôi không thể làm bất cứ điều gì với vỏ của tôi cho đến khi quá trình "nền" kết thúc. Mà đã không chạy trên nền nữa vì sự chờ đợi trong xử lý tín hiệu.

Tôi thiếu gì trong tất cả điều này?

Một điều cuối cùng, một phần của bài tập này tôi cũng cần in các thay đổi của trạng thái quy trình, như chấm dứt quá trình. Vì vậy, bất kỳ cái nhìn sâu sắc về điều đó cũng thực sự được đánh giá cao.

Đây là mã đầy đủ của tôi vào lúc này:

#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; 
} 

Trả lời

4

Có những lựa chọn khác nhau để waitpid() để giúp bạn (trích dẫn từ các tiêu chuẩn POSIX):

WCONTINUED

Các waitpid() chức năng sẽ báo cáo tình trạng của bất kỳ quá trình con liên tục được xác định bởi pid có tình trạng đã không được báo cáo kể từ khi nó tiếp tục từ một ngừng kiểm soát công việc.

WNOHANG

Các waitpid() chức năng sẽ không đình chỉ thực hiện các đề gọi nếu tình trạng không có sẵn ngay lập tức với một trong các tiến trình con theo quy định của pid.

Đặc biệt, WNOHANG sẽ cho phép bạn xem liệu có bất kỳ xác chết nào để thu thập mà không gây ra quá trình chặn chờ xác chết không.

Nếu quá trình gọi có SA_NOCLDWAIT được đặt hoặc có SIGCHLD được đặt thành SIG_IGN và quy trình không có trẻ em nào chưa được chuyển đổi thành quy trình zombie, chuỗi cuộc gọi sẽ chặn cho đến khi tất cả trẻ em của quá trình chứa chuỗi gọi kết thúc, và wait() và waitpid() sẽ thất bại và đặt errno thành [ECHILD].

Có thể bạn không muốn bỏ qua SIGCHLD, v.v. và trình xử lý tín hiệu của bạn có thể đang đặt cờ để báo cho vòng lặp chính của bạn "Rất tiếc; có con chết - hãy truy lục xác chết đó!".

Tín hiệu SIGCONT và SIGSTOP cũng sẽ phù hợp với bạn - chúng được sử dụng để khởi động lại và dừng quá trình con tương ứng (trong ngữ cảnh này, ở bất kỳ mức nào).

Tôi muốn khuyên bạn nên xem cuốn sách của Rochkind hoặc cuốn sách của Stevens - chúng bao gồm những vấn đề này một cách chi tiết.

2

Điều này sẽ giúp bạn bắt đầu. Sự khác biệt chính là tôi đã loại bỏ trình xử lý con và thêm waitpid vào vòng lặp chính với một số phản hồi. Thử nghiệm và làm việc, nhưng rõ ràng cần nhiều TLC hơn.

#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; 
} 

EDIT: Cộng lại trong việc xử lý tín hiệu không phải là khó khăn với waitpid() sử dụng WNOHANG. Nó đơn giản như di chuyển các công cụ waitpid() từ đầu vòng lặp vào bộ xử lý tín hiệu. Tuy nhiên, bạn nên biết hai điều:

Quy trình đầu tiên, thậm chí "tiền cảnh" sẽ gửi SIGCHLD. Vì có thể chỉ có một tiến trình tiền cảnh, bạn có thể lưu trữ tiền cảnh (giá trị trả về của cha mẹ từ fork()) trong biến hiển thị cho trình xử lý tín hiệu nếu bạn muốn xử lý đặc biệt nền trước và nền.

Thứ hai, bạn hiện đang thực hiện chặn I/O trên đầu vào chuẩn (read() ở đầu vòng chính). Bạn rất có khả năng bị chặn trên read() khi SIGCHLD xảy ra, dẫn đến một cuộc gọi hệ thống bị gián đoạn. Tùy thuộc vào hệ điều hành, nó có thể tự động khởi động lại cuộc gọi hệ thống, hoặc nó có thể gửi một tín hiệu mà bạn phải xử lý.

+0

Cảm ơn, nhưng tôi thực sự cần sử dụng trình xử lý tín hiệu, đó là một phần của bài tập. –

+0

Tôi chỉ nhìn thấy chỉnh sửa của bạn ... Tôi đã đặt waitpid() vào bộ xử lý tín hiệu nhưng tôi có vấn đề bạn mô tả trong đoạn thứ hai của bạn. Tôi không muốn sử dụng các biến toàn cục để khắc phục sự cố nhưng tôi không chắc cách sửa lỗi mà không có chúng ... –

+0

Tránh các biến toàn cục là tốt, nhưng giao tiếp với trình xử lý tín hiệu là một trong những trường hợp chúng ' cần thiết. – dwc

0

Thay vì sử dụng một biến toàn cầu, tôi nghĩ đến một giải pháp khác nhau:

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

    waitpid(pid, NULL, 0); 

    signal(SIGCHLD, childSignalHandler); 
} 

Nếu tôi chạy một quá trình foreground "xóa" các handler cho SIGCHLD vì vậy nó không được gọi. Sau đó, sau waitpid(), hãy đặt lại trình xử lý. Bằng cách này, chỉ các quy trình nền mới được xử lý.

Bạn có nghĩ có vấn đề gì với giải pháp này không?

+1

Bạn đã giới thiệu một điều kiện chạy đua khi quá trình nền thoát nhưng không có trình xử lý tín hiệu nào được cài đặt, dẫn đến một đứa trẻ chưa được giải mã và cuối cùng là một zombie. Đừng thêm các vấn đề như vậy vào chương trình của bạn chỉ để tránh một biến toàn cầu, chỉ vì bạn nghe chúng là xấu. – dwc

+0

Nhưng tôi vẫn không thấy cách thực hiện một biến toàn cục cho điều này và cũng tránh được tình trạng chạy đua. Cách tôi nhìn thấy nó, việc thêm một biến toàn cầu để xử lý tình huống này sẽ vẫn tạo ra một điều kiện chủng tộc. Chăm sóc để cung cấp một ví dụ mà nó không giới thiệu một điều kiện chủng tộc? –

+0

Lưu ý rằng đối số thứ hai cho 'signal()' phải là một con trỏ tới một hàm xử lý tín hiệu, hoặc 'SIG_IGN' hoặc' SIG_DFL'; 'NULL' không phải là một tùy chọn hợp lệ, mặc dù' SIG_IGN' thường là một con trỏ hàm rỗng. Có lẽ bạn nên nắm bắt giá trị trả về từ 'signal() đầu tiên' và phục hồi nó trong lần gọi thứ hai. Nếu các tín hiệu khác SIGCHLD đến, 'waitpid()' có thể trở lại sớm với EINTR. Sử dụng 'sigaction()' thay vì 'signal()' sẽ cho phép bạn kiểm soát những tín hiệu nào được cho phép. –

2

Bạn có thể sử dụng:

if(!background) 
    pause(); 

Bằng cách này, các khối quá trình cho đến khi nó nhận được tín hiệu SIGCHLD, và xử lý tín hiệu sẽ làm những thứ chờ đợi.

+1

Điều gì sẽ xảy ra nếu tín hiệu con xuất hiện giữa việc kiểm tra '' background'' và gọi '' pause() ''? Có một điều kiện chủng tộc ở đây. – jcoffland