C for plug and pipe with non-deterministic outlet

Let's look at the following code (please do not write that there are problems with names, problems with structuring, etc., I also know this). It was written to record arbitrary generated numbers x, y, z and r (and pid) for three children, but it often happens that it prints only two lines of “Got this ...”, and I don’t know why. Could you explain to me what the problem is, or correct my code?

#include <stdlib.h> #include <stdio.h> #include <sys/types.h> //fork #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <fcntl.h> //lock #include <signal.h> #include <time.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #include <sys/wait.h> #include "sys/ipc.h" #include "sys/sem.h" int child; int cs[3]; int fd[2]; int t; int parent; int child; void okay(int sign) { t = 0; } void rr(int sign) { char b[50]; while(read(fd[0], &b, 50)<0) sleep(0.5); printf("Got this: %s \n", b); } void ch(int argc, char** argv) { printf("Mypid: %i\n", getpid()); close(fd[0]); while(t==1) sleep(1); srand((unsigned)time(NULL)); // init int x,y,z,r,pid; x = rand() % 101; y = rand() % 101; z = rand() % 101; r = rand() % 101; pid = getpid(); char b[50]; sprintf(b, "%i %i %i %i %i", pid, x, y, z, r); while(write(fd[1], b, 50)<0) sleep(0.2); kill(parent, SIGUSR2); close(fd[1]); } int main(int argc, char** argv) { if(argc < 4) { printf("Too few args!\n"); return 0; } pipe(fd); t = 1; parent = getpid(); signal(SIGUSR1, okay); child = fork(); if(child < 0) perror("FORK"); if(child > 0) { cs[0] = child; child = fork(); if(child < 0) perror("FORK"); if(child > 0) { cs[1] = child; child = fork(); if(child < 0) perror("FORK"); if(child > 0) { cs[2] = child; // MAIN printf("%i %i %i\n", cs[0], cs[1], cs[2]); close(fd[1]); signal(SIGUSR2, rr); kill(cs[0], SIGUSR1); kill(cs[1], SIGUSR1); kill(cs[2], SIGUSR1); int status; waitpid(cs[0], &status, 0); waitpid(cs[1], &status, 0); waitpid(cs[2], &status, 0); close(fd[0]); }else { // ch 3 ch(argc, argv); } }else { // ch 2 ch(argc, argv); } }else { // ch 1 ch(argc, argv); } return 0; } 
-1
source share
1 answer

Rewritten answer

I managed to describe the behavior even with various modified versions of the code. For example, one trace obtained from the diagnostic version of the code was:

 14607 at work Children: 14608 14609 14610 Children signalled Child 14609: signal 30 - setting t to 0 Child 14608: signal 30 - setting t to 0 Child 14610: signal 30 - setting t to 0 Child 14609: at work Child 14610: at work Child 14608: at work Child 14609: sending 14609 65 24 97 0 Child 14609: exiting Child 14610: sending 14610 87 17 23 57 Adult 14607: signal 31 - reading input Child 14610: exiting Child 14608: sending 14608 5 89 95 8 Child 14608: exiting Adult 14607: got <<14609 65 24 97 0>> Adult 14607: signal 31 - reading input Adult 14607: got <<14610 87 17 23 57>> Child 1 ended Child 2 ended Child 3 ended 14607 exiting 

You can see that the parent received data from 14609 and 14610, but not from 14608. I am going to explain this using signals. They are a very bad mechanism for IPC. And in this case, they seem unreliable in time. This was code using sigaction() and with the value sa.sa_mask set to block all signals ( sigfillset(&sa.sa_mask) ).

However, there is no need to use signals from the child back to the parent. I left the signal handler in place so that the parent signal notified the children about weaving, but simplified it to just change the value of the volatile sig_atomic_t variable ( t by name, still) from 1 to 0. The expression is to “use” the signal number parameter (called sign in the code); it avoids the warning when compiling using GCC 4.7.1 on Mac OS X 10.7.5:

 gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \ pipes-13905948.c -o pipes-13905948 

The seeds in srand() mix the time with the PID of the process to give different values ​​from each child (using only the PID also does this). I canceled 16 headings in the original (including two repeats) to 7. I deleted rr() as the parent no longer responds to signals from the children. I changed the code in main() so that it does not dip the RHS pages. The code includes an extensive diagnosis of what is happening. This is useful when working with multiple processes, for example, if most messages have a PID printed as part of the message. I used "Adult" instead of "Parent", so the result is neatly aligned with the lines with the tags "Child". Please note that the signal handler is installed before the children branch. On a multiprocessor machine, there is no guarantee about the sequence in which the processes will be performed, therefore, the installation of the output signal will continue until forking is unreasonable at best and can lead to unexpected consequences in the worst case.

Reading in the signal handler is replaced by reading in the parent code in main() ; this is a much more satisfactory way of dealing with entry. You should strive to do as little as possible in the signal handler. Standard C does not reliably support much more:

ISO / IEC 9899: 2011 §7.14.1 signal function

¶5 If the signal occurs differently than by calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with a static or stream storage duration that is not a blocking atomic object other than assigning a value to the object declared as volatile sig_atomic_t , or the signal handler calls any function in the standard library other than the abort function, the _Exit , quick_exit function, or the signal function with the first argument equal to the signal number corresponding to the signal that caused the handler to be called.

POSIX is softer, but you still need to be very careful about what you do in the signal handler, and you should do as little as possible in the signal handler.

These changes result in this code:

 #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <time.h> #include <unistd.h> static int fd[2]; static volatile sig_atomic_t t = 1; static int parent; static void okay(int sign) { t = (sign == 0); } static void ch(void) { int pid = getpid(); printf("Child %i: at work\n", pid); close(fd[0]); while (t == 1) { printf("Child %d: pausing on t\n", pid); pause(); } srand((unsigned)time(NULL) ^ pid); int x = rand() % 101; int y = rand() % 101; int z = rand() % 101; int r = rand() % 101; char b[50]; sprintf(b, "%i %i %i %i %i", pid, x, y, z, r); printf("Child %d: sending %s\n", pid, b); while (write(fd[1], b, strlen(b)) < 0) printf("Child %d: write failed\n", pid); close(fd[1]); printf("Child %d: exiting\n", pid); exit(0); } int main(void) { int cs[3]; pipe(fd); parent = getpid(); printf("%d at work\n", parent); struct sigaction sa; sa.sa_flags = 0; sigfillset(&sa.sa_mask); sa.sa_handler = okay; sigaction(SIGUSR1, &sa, 0); if ((cs[0] = fork()) < 0) perror("fork 1"); else if (cs[0] == 0) ch(); else if ((cs[1] = fork()) < 0) perror("fork 2"); else if (cs[1] == 0) ch(); else if ((cs[2] = fork()) < 0) perror("fork 3"); else if (cs[2] == 0) ch(); else { printf("Children: %i %i %i\n", cs[0], cs[1], cs[2]); close(fd[1]); kill(cs[0], SIGUSR1); kill(cs[1], SIGUSR1); kill(cs[2], SIGUSR1); printf("Children signalled\n"); char buffer[64]; int nbytes; while ((nbytes = read(fd[0], buffer, sizeof(buffer)-1)) > 0) { buffer[nbytes] = '\0'; printf("Adult %d: read <<%s>>\n", parent, buffer); } int status; waitpid(cs[0], &status, 0); printf("Child 1 ended\n"); waitpid(cs[1], &status, 0); printf("Child 2 ended\n"); waitpid(cs[2], &status, 0); printf("Child 3 ended\n"); close(fd[0]); } printf("%d exiting\n", (int)getpid()); return 0; } 

The code is still flabby for error handling; There are many uncontrolled system calls and unregistered results (for example, child statuses). I am not sure about the repeated attempts to record failures, but the code has never been executed.

This is a trace from a revised version of the code.

 15745 at work Children: 15746 15747 15748 Children signalled Child 15746: at work Child 15746: sending 15746 63 4 70 89 Child 15748: at work Child 15746: exiting Child 15747: at work Adult 15745: read <<15746 63 4 70 89>> Child 15748: sending 15748 44 0 99 37 Child 15748: exiting Child 15747: sending 15747 3 69 68 97 Adult 15745: read <<15748 44 0 99 37>> Child 15747: exiting Adult 15745: read <<15747 3 69 68 97>> Child 1 ended Child 2 ended Child 3 ended 15745 exiting 

Several times I received input, such as:

 Adult 15734: read <<15736 83 95 64 2915737 42 63 66 89>> 

This combines the output of processes 15736 and 15737 into one reading result. I am not happy with this; AFAIK, reading should receive an atomic record of individual children in the form of separate messages. I am going to bring this to the quirk of Mac OS X without exploring it further.


Original answer

Since you are using signal() and not sigaction() , it is possible that your signal handler is reset in SIGDFL before the signal handler is called. You can fix this in okay() by adding:

 void okay(int sign) { signal(sign, okay); t = 0; } 

You can control if there is a problem by checking the return value from signal() in the handler.

<y> The rest of your code currently does not use t (although it is set to 1 in main() ). (Inaccurate observation!)

You can simplify your debugging by having more print operations. You can use a loop to kill and collect your children (although you can write a loop the way you did, do not put three function calls on one line).

0
source

All Articles