2
votes

I found this problem in a past exam from CMU and I can't get how the outputs were possible.

basically, the idea behind it that there is a parent process that blocks a user-defined signal, then the parent forks a child. and based on which process that runs first (aka: wins the race) a different output is possible. Here is the question that is being asked in the exam (please read it)

and here is the code from the exam:

int i = 1;
void handler (int sig) {
    i++;
}
int main() {
    pid_t pid;
    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGUSR1);
    signal(SIGUSR1, handler);
    sigprocmask(SIG_BLOCK, &s, 0);
    pid = fork();
        <LINE A>
    if (pid != 0) {
        i = 2;
        <LINE B>
    } else {
        i = 3;
        <LINE C>
    }
    sigprocmask(SIG_UNBLOCK, &s, 0);
    pause(); /* pause to allow all signals to arrive */
    printf("%d\n", i);
    exit(0);
}

There are 3 cases that needs to be tested since we need to put the function:

kill(pid,USRSIG1);

either in LINE A or LINE B or LINE C and find the possible output.

Now here is what i did, i placed the function in LINE A.

Let's say that we run the program, then the parent will create an empty set s, add the signal SIGSUR1 to it, then it will assign a custom handler for the SIGUSR1 signal, and it blocks the signals in the set s. Which are these lines

    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGUSR1);
    signal(SIGUSR1, handler);
    sigprocmask(SIG_BLOCK, &s, 0);

Then the parent will run the line

    pid = fork();

which will create a new child from the process.

Now there are 2 cases that will determine the output. The Operating system schedules the parent or the child to run first.

Let's say that the parent runs first. Then it will execute LINE A (which is the kill function)

and since it's the parent the pid value will be the process id of the child. So it will send the USRSIG1 to the child but since it's blocked it will do nothing

The if statement assigns a value for the global variable i. if the process is a parent then i = 2, else then i = 3. So in our parent process, we will have i = 2.

    if (pid != 0) { //if i am a parent then i = 2
        i = 2;
        <LINE B>
    } else { //if i am a child then i = 3
        i = 3;
        <LINE C>
    }

the next line will be executed in the parent and it will unblock the SIGUSR1 signal sigprocmask(SIG_UNBLOCK, &s, 0); and the parent process will pause until it receives a signal

now the child will run and it will send a kill(0,SIGUSR1) signal to all the process in the process group including itself. But since it's blocked in the child nothing will happen. The parent will receive the signal and it will increment me by 1 (So now i = 3 in parent). And it (the parent) will be resumed from the function pause to print the value of I (which is 3) and exit.

the child now resumes from the kill function and since it's a child the if statement will not be true (so the value of i in the child = 3). The child unblocks the signals from the set and pause().

Since there is no other process to send a signal to the child it will stay paused forever and the output was 3 by the parent only. And if we go the other way (the child runs before the parent) then the output will be 4 only.

What is confusing me that the solution from the exam says that there are 2 outputs per run? I don't get how is this possible since one of the process will stay in pause().

The solution key says that the possible output for LINE A is:

3 4, 4 3, 3 5, or 5 3

that is all that I could understand from the question. Any help or hint would be appreciated.

1

1 Answers

0
votes

If the child runs first the output will be 5 as it will receive signals from both itself and the parent. If both processes complete kill(pid,USRSIG1) before either enters pause() neither will terminate or print.

POSIX also permits both processes to terminate and print due to delayed signals (for example if network messages were used instead of shared memory) and for the child to print any int value as i is not of type volatile sig_atomic_t.

It appears from the comment that the exam author wrongly believes that pause() will magically wait until all signals sent or will be sent to the process are received.

If sigprocmask(SIG_UNBLOCK, &s, 0); pause(); were replaced with a appropriate call to sigsuspend it will act as exam author states. The parent will receive 1 signal and the child will receive 1 or 2 signals, as the signal from the parent could either be too late or combined with the signal from itself.