Search code examples
cpipeforkexecposix-select

C - creating process tree using pipes, select, fork, and execl


I'm trying to write the following, 2 part program. In one file ("root.c"), I read in a random string of 1's and 0's. I then split the resulting string in half, and send each half to its own process through fork(). Each child process uses execl() to run the second program ("bit_count.c").

Within bit_count.c, it: a) checks if the length of the (half)string is 2 or less. If yes, it returns the number of
1's and 0's to it's parent process. b) if not, it begins to recursively split the string in half, and sending each half to its own new process (replicating the procedure in root.c). This creates a binary process tree, until all pieces of the string are 2 characters long or less. c) the count results of the left and right children are aggregated by the parent, and returned to its parent, until returning to the root process, which aggregates the highest two children, and outputs it to the user.

My problem with this project is returning the 2-character counts to the parent. My idea right now is to direct the parent's left and right read pipes to stdin with dup2(), and just to print to stdout with fprintf from the children. The select() function of the parent should catch the returning output, right?

My second problem is the format of the output. if the counts are in ints, what is the best way to return that using select() in this case? I've attached my code below, just be warned that it may be a mess - I'm rusty with C code and this is my first exposure to select() and execl().

root.c:

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[]) {
if (argc != 2) {
    perror("input file name");
    printf("%d", argc);
    exit(1);
}

FILE* fp;
if((fp = fopen(argv[1], "r")) == NULL) {
    perror("open file");
}
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET); 
char *bits = malloc(fsize+1);
fread(bits, fsize, 1, fp);
fclose(fp);


char *left_half = malloc( fsize/2 + 1 );
char *right_half;
if (fsize%2) right_half = malloc( fsize/2 + 2 );
else right_half = malloc( fsize/2 + 1 );

if (!left_half || !right_half) perror("array split");
memcpy(left_half, bits, fsize/2);
if (fsize%2) memcpy(right_half, bits + fsize/2, fsize/2 + 1);
else memcpy(right_half, bits + fsize/2, fsize/2);


int fd_left[2], fd_right[2];
int zero, one;
int *left_res, *right_res;
pid_t left, right;
struct timeval tv;
fd_set readfds;

tv.tv_sec = 2;
tv.tv_usec = 500000;

if ((pipe(fd_left) == -1) || (pipe(fd_right) == -1)){ 
        perror("Create pipe error"); 
        exit(1); 
}

FD_ZERO(&readfds);
FD_SET(fd_left[0], &readfds);
FD_SET(fd_right[0], &readfds);

if ((left=fork()) == 0) {
        close(fd_left[0]);
        execl("./bit_count", "bit_count", left_half, NULL);
        perror("initiating recursion"); 
        exit(1);
}
else if(left > 0) {
    if ((right = fork())==0) {
        close(fd_right[0]);
        execl("./bit_count", "bit_count", right_half, NULL);
        perror("initiating recursion"); 
        exit(1);
    }
    else if (right > 0) { 
        close(fd_right[1]);
        close(fd_left[1]);
        char *left;
        char *right;
        dup2(fd_left[0], 0);
        dup2(fd_right[0], 0);

        int ret = select(2, &readfds, NULL, NULL, &tv);
        read(fd_left[0], &left_res, 1);
        read(fd_right[0], &right_res, 1);           
        printf("Back in root process!\n");

    }   
}

zero = (*right_res + *left_res);
one = (*(left_res+sizeof(int)) + *(right_res+sizeof(int)));

printf("%s had %d zeroes and %d ones\n", argv[1], zero, one);
return 0;
}

bit_count.c (only relevant part):

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
if (argc != 2) {
    perror("sent bit string");
    printf("%d", argc);
    exit(1);
}
char *bit_string = argv[1];
int size = strlen(bit_string);
int counts[2];
counts[0] = 0;
counts[1] = 0;
if (!(size > 2)) {

    int i=0;
    for(; i < size; i++) {
        if (bit_string[i]=='1') ++counts[1];
        else ++counts[0];
    } 

    fprintf(stdout, "%p", &counts);
    fflush(stdout);
    return 0;
}
  } 

Solution

    1. My idea right now is to direct the parent's left and right read pipes to stdin with dup2(), and just to print to stdout with fprintf from the children. The select() function of the parent should catch the returning output, right?

    No. You need to dup2(fd[1], STDOUT_FILENO) in the child before calling execl(). How else is bit_count supposed to know about the pipe? Then in the parent you can just read from fd[0]. To make things easier, you could just make bit_count a function, and call it directly in the child without using execl(). Then you could just write to fd[1] (if you made it global, or passed the value to the bit_count function) from the children.

    1. My second problem is the format of the output. if the counts are in ints, what is the best way to return that using select() in this case?

    You could use write(STDOUT_FILENO, &counts, 2*sizeof(int)) to write the ints directly to the pipe, rather than formatting them as a string. This way the parent does not need to convert them back to ints.