开发者

Unix C Shell - Job Control Issue!

开发者 https://www.devze.com 2023-02-12 03:53 出处:网络
I\'ve been working on creating my own Unix Shell in C to get practice with its interworkings...I\'m having some issues getting my process to run in the background while allowing my shell to continue t

I've been working on creating my own Unix Shell in C to get practice with its interworkings...I'm having some issues getting my process to run in the background while allowing my shell to continue taking user input. If you could take the time to dissect what I've got below it would be much appreciated!

My variables are below, just incase that helps understand things more...

#define TRUE 1

static char user_input = '\0'; 

static char *cmd_argv[5]; // array of strings of command
static int cmd_argc = 0; // # words of command

static char buffer[50]; // input line buffer
static int buffer_characters = 0;
int jobs_list_size = 0;

/* int pid; */
int status;
int jobs_list[50];

Here is my main function.

int main(int argc, char **argv)
{           
    printf("[MYSHELL] $ ");

    while (TRUE) {
        user_input = getchar();
        switch (user_input) {

            case EOF:
                exit(-1);

            case '\n':
                printf("[MYSHELL] $ ");
                break;

            default:
                // parse input into cmd_argv - store # commands in cmd_argc
                parse_input();

                //check for zombie processes
                check_zombies();

                if(handle_commands() == 0)
                    create_process();
                    printf("\n[MYSHELL] $ ");

        }
    }
    printf("\n[MYSHELL] $ ");
    return 0;
}

Parse Input...I know, I can't get readline to work on this box :( If provided the & operator, create the job in the background... (see below)

void parse_input()
{
    // clears command line
    while (cmd_argc != 0) {
        cmd_argv[cmd_argc] = NULL;
        cmd_argc--; 
    }

    buffer_characters = 0;

    // get command line input
    while ((user_input != '\n') && (buffer_characters < 50)) {
        buffer[buffer_characters++] = user_input;
        user_input = getchar();
    }

    // clear buffer
    buffer[buffer_characters] = 0x00;

    // populate cmd_argv - array of commands
    char *buffer_pointer;
    buffer_pointer = strtok(buffer, " ");

    while (buffer_pointer != NULL) { 
        cmd_argv[cmd_argc] = buffer_pointer;
        buffer_pointer = strtok(NULL, " ");

        //check for background process execution
        if(strcmp(cmd_argv[cmd_argc], "&")==0){
            printf("Started job %d\n", getpid());    
            make_background_job();
        }

        cmd_argc++;
    }
}

Make background job. Closes child process STDIN, opens new STDIN, and executes.

void make_background_job()
{
    int pid;
    pid = fork();
    fclose(stdin); // close child's stdin
    fopen("/dev/null", "r"); // open a new stdin that is always empty

    fprintf(stderr, "Child pid = %d\n", getpid());

    //add pid to jobs list
    jobs_list[jobs_list_size] = getpid();
/*     printf("jobs list %d", *jobs_list[jobs_list_size]);         */
    jobs_list_size++;

    execvp(*cmd_argv,cmd_argv);

    // this should never be reached, unless there is an error
    fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);     
}

The meat of my job control. Fork spawns child, returns 0 for child and PID for parent.

void create_process()
{   
    pid_t pid;

    pid = fork();
    status = 0;

    switch(pid){
        case -1:
            perror("[MYSHELL ] $ (fork)");
            exit(EXIT_FAILURE);
        case 0:            
            make_background_job();
    开发者_如何学Python        printf("\n\n----Just made background job in case 0 of create_process----\n\n");        
            break;

        default:
            printf("\n\n----Default case of create_process----\n\n");
            // parent process, waiting on child...
            waitpid(pid, &status, 0);

            if (status != 0) 
                fprintf  (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
            else
                break;
    }
}

My problem is when I execute a job in the background, its executing the command twice, and exiting out of the shell. (It functions correctly otherwise if no background process is enabled). Where am I getting confused? I think it may have to do with issues regarding my PID's, as I'm not populating the list correctly either in 'make_background_job'

Here is my output, the example.sh just throws out helloWorld:

[MYSHELL] $ ./example.sh &
Started job 15479
Child pid = 15479
Child pid = 15481
Hello World
Hello World


What seems to happen is

  • in main() the prompt is displayed, expecting a command
  • when a command is input, parse_input() is called
  • it builds the commands array until it finds & where it calls make_background_jobs()
  • that function forks quickly, and executes in parallel, in two processes, execvp()
  • execvp() replaces each of the two processes to execute the command
  • thus two "Hello world" appear.

The problem is in make_background_jobs() where, I think, the expected behavior was that only one of the two processes should execute the command, and the other one (father) returns, to keep the program active.

This can be solved by modifying that function, making the father process return:

    void make_background_job()
    {
      int pid;
      pid = fork();

      if (pid) return; // The father process returns to keep program active
      ...

edit

I gave it a try, removing the unnecessary


void make_background_job()
{
    int pid;
    pid = fork();

    if ( ! pid)
    {
      fclose(stdin); // close child's stdin
      fopen("/dev/null", "r"); // open a new stdin that is always empty

      fprintf(stderr, "Child Job pid = %d\n", getpid());

      //add pid to jobs list
      jobs_list[jobs_list_size] = getpid();
  /*     printf("jobs list %d", *jobs_list[jobs_list_size]);         */
      jobs_list_size++;

      execvp(*cmd_argv,cmd_argv);

    // this should never be reached, unless there is an error
      fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);     
      exit(1);
    }

    waitpid(pid, &status, 0);
}

The background job is created in another process. The father waits for the job to complete.


void parse_input()
{
    // clears command line
    while (cmd_argc != 0) {
        cmd_argv[cmd_argc] = NULL;
        cmd_argc--; 
    }

    buffer_characters = 0;

    // get command line input
    while ((user_input != '\n') && (buffer_characters < 50)) {
        buffer[buffer_characters++] = user_input;
        user_input = getchar();
    }

    // clear buffer
    buffer[buffer_characters] = 0x00;

    // populate cmd_argv - array of commands
    char *buffer_pointer;
    buffer_pointer = strtok(buffer, " ");

    int ok = 0;

    while (buffer_pointer != NULL) { 
        cmd_argv[cmd_argc] = buffer_pointer;
        buffer_pointer = strtok(NULL, " ");

        //check for background process execution
        if(strcmp(cmd_argv[cmd_argc], "&")==0){
          ok = 1;
          break;
        }

        cmd_argc++;
    }

    if (!ok) cmd_argv[cmd_argc = 0] = NULL; // If no & found, reset commands
}

Only parses input.

Below a new handle_commands() that return 0 if there is a command to play, and the main follows.


int handle_commands() { return cmd_argc > 0 ? 0:1; }

int main(int argc, char **argv)
{           
    printf("[MYSHELL] $ ");

    while (TRUE) {
        user_input = getchar();
        switch (user_input) {

            case EOF:
                exit(-1);

            case '\n':
                printf("[MYSHELL] $ ");
                break;

            default:
                // parse input into cmd_argv - store # commands in cmd_argc
                parse_input();

                //check for zombie processes
                check_zombies();

                if(handle_commands() == 0)
                    make_background_job();  // Call directly the bg job
                    printf("\n[MYSHELL] $ ");

        }
    }
    printf("\n[MYSHELL] $ ");
    return 0;
}

The main() calls directly make_background_job().

There is only one fork() in make_background_job. create_process() has been removed.

0

精彩评论

暂无评论...
验证码 换一张
取 消