Operating Systems Principles and Practice 2nd 3Ch Exercises


Preface: Most of the answers below are written by myself --- only instructors are given access to the exercise solutions. If you find anything wrong, please let me know so that I can correct them, thank you!




1. Can UNIX fork return an error? Why or why not?

Note: You can answer this question by looking at the manual page for fork, but before you do that, think about what the fork system call does. If you were designing this call, would you need to allow fork to return an error?


Yes. Because we need to ensure the parent process notified if this system call fails, and returning an error directly is one way to achieve that.

According to the man page of fork(2) in Ubuntu 18.04.1:

On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.




2. Can UNIX exec return an error? Why or why not?

Note: You can answer this question by looking at the manual page for exec, but before you do that, think about what the exec system call does. If you were designing this call, would you need to allow it to return an error?


Yes. Because when a process fails to call exec(), we must ensure it notified to do something about this exception, and returning an error directly is one of the way to achieve that.

According to the man page of execve(2) in Ubuntu18.04.1:

on error -1 is returned, and errno is set appropriately.




3. What happens if we run the following program on UNIX?

main(){
    while (fork() >= 0)
        ;
}


This is called a fork bomb, which is a denial-of-service attack where in a process continually replicates itself to deplete available system resources, slowing down or crashing the system due to resource starvation.

After I run this bomb in Ubuntu 18.04.1 (virtualbox), it seems like the process -resources are being exhausted and other programs can not be started:




4. Explain what must happen for UNIX wait to return immediately (and successfully).


According to the manual pages of FreeBSD, On return from a successful wait() call, the status area contains information about the process that reported a status change will be set. And In the case of a terminated child, performing a wait() allows the system to release the resources associated with the child.




The fifth question is meaningless.




6. What happens if you run “exec csh” in a UNIX shell? Why?


The shell will be replaced with csh, but the process(id) preserved:




7. What happens if you run “exec ls” in a UNIX shell? Why?


The shell will be replaced with the ls program with same process, but since ls is not an interactive program, the terminal window will close instantly.




8. How many processes are created if the following program is run?

void forkthem(int n){
    if (n > 0){
        fork();
        forkthem(n-1);
    }
}

main(int argc, char ** argv){
    forkthem(5);
}


2^5 = 32




9. Consider the following program:


main (int argc, char ** argv) {
    int child = fork();
    int x = 5;
    
    if (child == 0){
        x += 5;
    } else {
        child = fork();
        x += 10;
        if(child) {
            x+= 5;
        }
    }
}

How many different copies of the variable x are there? What are their values when their process finishes?


There will be three different copies of the variable x: 5+5=10, 5+10=15, 5+10+5=20.




10. What is the output of the following programs? (Please try to solve the problem without compiling and running the programs.)

// Program 1
main() {
    int val = 5;
    int pid;
    
    if (pid = fork())
        wait(pid);
    val++;
    printf("%d\n", val);
    return val;
}

//Program 2
main(){
    int val = 5;
    int pid;
    if (pid = fork())
        wait(pid);
    else
        exit(val);
    val++;
    printf("%d\n", val);
    return val;
}


Program 1: 6\n6\n

Program 2: 6\n




11. Implement a simple Linux shell in C capable of executing a sequence of programs that communicate through a pipe. For example, if the user types ls | wc, your program should fork off the two programs, which together will calculate the number of files in the directory. For this, you will need to use several of the Linux system calls described in this chapter: fork, exec, open, close, pipe, dup2, and wait. Note: You will to replace stdin and stdout in the child process with the pipe file descriptors; that is the role of dup2.


I have implemented a shell which supports foreground and background tasks, as well as job control when I read the csapp 3eCode and descriptions here(Chinese), so I will just improve it with pipeline support here.

The part of changed code:

/* 
 * find_second_argv_in_pipe - Find the argv for the second program in 
 * single-pipe command
 * 
 * If no pipe symbol find, return NULL, else return the second argv
 */
char ** find_second_argv_in_pipe(char *argv[])
{
    for(int i = 0; argv[i] != NULL; i++)
    {
        if(!strcmp(argv[i], "|"))
        {
            argv[i] = NULL;
            return &argv[i+1];
        } 
    }
    return NULL;
}

/*
 * eval - Evaluate the command line that the user has just typed in
 *
 * If the user has requested a built-in command (quit, jobs, bg or fg)
 * then execute it immediately. Otherwise, fork a child process and
 * run the job in the context of the child. If the job is running in
 * the foreground, wait for it to terminate and then return.  Note:
 * each child process must have a unique process group ID so that our
 * background children don't receive SIGINT (SIGTSTP) from the kernel
 * when we type ctrl-c (ctrl-z) at the keyboard.
 *
 *2018.10.14: single pipe is supported now
 */
void eval(char *cmdline)
{
    char *argv[MAXARGS];
    char **argv2 = NULL;
    int bg_flag;

    bg_flag = parseline(cmdline, argv); /* true if the user has requested a BG job, false if the user has requested a FG job. */
    argv2 = find_second_argv_in_pipe(argv);

    if (builtin_cmd(argv, cmdline)) /* built-in command */
    {
        return;
    }
    else /* program (file) */
    {
        pid_t pid;
        sigset_t mask, prev;
        sigemptyset(&mask);
        sigaddset(&mask, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask, &prev); /* block SIG_CHLD */

        int pipefd[2]; /* pipefd[0] refers to the read end of the pipe.
        pipefd[1] refers to the write end of the pipe. */

        if (access(argv[0], F_OK)) /* do not fork and addset! */
        {
            fprintf(stderr, "%s: Command not found\n", argv[0]);
            return;
        }
        if(argv2) /* single pipe command */
        {
            if (access(argv2[0], F_OK)) /* do not fork and addset! */
            {
                fprintf(stderr, "%s: Command not found\n", argv2[0]);
                return;
            }
            if(pipe(pipefd))
            {
                perror("Failed to creat pipe file");
                return;
            }
        }

        if ((pid=fork()) == 0) /* child1 */
        {
            sigprocmask(SIG_SETMASK, &prev, NULL); /* unblock SIG_CHLD */

            if (!setpgid(0, 0))
            {
                if(argv2)
                {
                    if ((pid=fork()) == 0) /* child2 */
                    {
                        if((dup2(pipefd[0], STDIN_FILENO)==-1))
                        {
                            perror("Failed to dup2 in program2 for pipe operation");
                            _exit(EXIT_FAILURE);
                        }
                        close(pipefd[0]);close(pipefd[1]);
                        if (execve(argv2[0], argv2, environ))
                        {
                            fprintf(stderr, "%s: Failed to execve\n", argv2[0]);
                            _exit(EXIT_FAILURE);
                        }
                    }
                    if((dup2(pipefd[1], STDOUT_FILENO)==-1))
                    {
                        perror("Failed to dup2 in program1 for pipe operation");
                        _exit(EXIT_FAILURE);
                    }
                    close(pipefd[0]);close(pipefd[1]);
                }
                if (execve(argv[0], argv, environ))
                {
                    fprintf(stderr, "%s: Failed to execve\n", argv[0]);
                    _exit(EXIT_FAILURE);
                }
                /* context changed */
            }
            else
            {
                fprintf(stderr, "Failed to invoke setpgid(0, 0)\n");
                _exit(EXIT_FAILURE);
            }
        }
        else if (pid > 0)/* tsh */
        {
            if (!bg_flag) /* exec foreground */
            {
                fg_pid = pid;
                fg_pid_reap = 0;
                addjob(jobs, pid, FG, cmdline);
                sigprocmask(SIG_SETMASK, &prev, NULL); /* unblock SIG_CHLD */
                waitfg(pid);
            }
            else /* exec background */
            {
                addjob(jobs, pid, BG, cmdline);
                sigprocmask(SIG_SETMASK, &prev, NULL); /* unblock SIG_CHLD */
                printf("[%d] (%d) %s", maxjid(jobs), pid, cmdline);
            }
            /* DONT'T forget to close the pipefd in tsh process
            otherwise the second program will stall output until
            tsh exits */
            close(pipefd[0]);close(pipefd[1]);
            return;
        }
        else
        {
            unix_error("Failed to fork child");
        }
    }
    return;
}





12. Extend the shell implemented above to support foreground and background tasks, as well as job control: suspend, resume, and kill.


Please refer to the code above.

猜你喜欢

转载自www.cnblogs.com/liqiuhao/p/9787404.html
今日推荐