1

thank you for taking your time to read this. I'm trying to implement a semaphore through a file using C on a linux machine.

I have two process that I must synchronize, one has all the consonants of a file stored in an array, the other has all the vowels. I've arranged these arrays so that if I alternate between them, I can reconstruct the original file and paste it in another file. The issue now is making these two process alternate. This exercise in particular wants me to implement a semaphore using a 3rd file. What I've done is use the first byte of this file as a semaphore, lot loop one process until the other is finished.

I've tested with long sleep()s and yes, if these processes do alternate, the output file is exactly how I want it, but with that said, my current implementation of a semaphore seems not to be working.

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <fcntl.h>
 
 
int main (){
    int child1=0, child2=0, fd, fd2, fs, i=0, i2=0;
    int count=0, count2=0;
    char buf1[20], buf2[20], a, b, con='1', vow='0', check1, check2;
    fd=open("text", O_RDONLY);
    fd2=open("text2", O_CREAT|O_RDWR|O_TRUNC,0777);
    fs=open("semaphore", O_RDWR, 0777);
 
 
    if (fork()==0)
        child1=1;
    else {
        if (fork()==0)
            child2=1;
    }
 
 
    //access vowel child
    if (child1){
        printf("I'm the first child\n");
        while ((read(fd,&a,1))==1){
            if (a=='a' || a=='e' || a=='i' || a=='o' || a=='u')
                buf1[count]=a;
            count++;
            }
        printf("count: %d\n", count);
        //vowels are now into buf1
        //wait for the brother to do the same
        sleep(2);
        for (i; i<=count+1; i++){
            if (buf1[i]!='\0'){
                printf("%c\n", buf1[i]);
                a=buf1[i];
                write(fd2,&a,1);
            }
            lseek(fs,0,SEEK_SET);
            write(fs,&con,1);//tell the semaphore it's the consonant's turn (1)         
            while(check1=='1'){
                lseek(fs,0,SEEK_SET);
                read(fs,&a,1);
                check1=a;
                sleep(1);
            } //get stuck until it's  somebody's else turn
        }
    }   
 
 
 
    //access consonant child
    else if (child2){
        sleep(1);
        lseek(fd,0,SEEK_SET);
        printf ("I'm the second child\n");
        while ((read(fd,&a,1))==1){
            if (a=='a' || a=='e' || a=='i' || a=='o' || a=='u')
                ;
            else
                buf2[count2]=a;
            count2++;
        }
        //resync
        sleep(1);
        printf("count: %d\n", count2);
        //consonants are now into buf1
 
        for (i; i<=count2+1; i++){
            lseek(fs,0,SEEK_SET);
            if (buf2[i]!='\0'){
                printf("%c\n", buf2[i]);
                b=buf2[i];
                write(fd2,&b,1);
            } //wait for vowel
            while(check2=='0'){
                lseek(fs,0,SEEK_SET);
                read(fs,&b,1);
                check2=b;
                sleep(1);
            }
            lseek(fs,0,SEEK_SET);
            write(fs,&vow,1);//tell the semaphore it's the vowel's turn (0)
        }
    }
 
 
 
 
 
    else
        printf("I'm the father\n");
    sleep(10);
    exit(0);
}

The file "text" has "hello world" stored in it. What happens when I execute this code is that what's copy and pasted is "hll wrld eoo". What exactly am I doing wrong with my semaphore?

Maridiama
  • 11
  • 2

1 Answers1

0

Basically, you want to implement acquire/release similar to pthread_mutex_t or sem_t.

You need two functions:

  1. acquire to wait for and acquire ownership
  2. release to release ownership and grant it to the "other" process

You're conflating the two operations into one. And, your code doesn't actually do locking properly.

Side note: You are creating "zombie" processes because the father does not do wait on the children.

Here's some refactored code.

I added acquire and release calls to the places where you had your sempahore code (e.g. the second loops for each child). But, you might need them in the first loops as well (which I did not do).

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void
acquire(int fs,char iam)
{
    char own;

    while (1) {
        lseek(fs,0,SEEK_SET);
        read(fs,&own,1);

        if (own == iam)
            break;

        usleep(1);
    }
}

void
release(int fs,char iam)
{
    char other;

    if (iam == 1)
        other = 2;
    else
        other = 1;

    lseek(fs,0,SEEK_SET);
    write(fs,&other,1);
}

int
main()
{
    int child1 = 0,
        child2 = 0,
        fd,
        fd2,
        fs,
        i = 0,
        i2 = 0;
    int count = 0,
        count2 = 0;
    char buf1[20],
     buf2[20],
     a,
     b,
     con = '1',
        vow = '0',
        check1,
        check2;

    fd = open("text", O_RDONLY);
    fd2 = open("text2", O_CREAT | O_RDWR | O_TRUNC, 0777);

    fs = open("semaphore", O_RDWR | O_CREAT, 0777);

    // initialize semaphore to grant access to one or the other
    buf1[0] = 1;
    write(fs,buf1,1);

    if (fork() == 0)
        child1 = 1;
    else {
        if (fork() == 0)
            child2 = 1;
    }

    // access vowel child
    if (child1) {
        printf("I'm the first child\n");
        while ((read(fd, &a, 1)) == 1) {
            if (a == 'a' || a == 'e' || a == 'i' || a == 'o' || a == 'u')
                buf1[count] = a;
            count++;
        }
        printf("count: %d\n", count);
        // vowels are now into buf1
        // wait for the brother to do the same
        sleep(2);
        for (i; i <= count + 1; i++) {
            acquire(fs,1);
            if (buf1[i] != '\0') {
                printf("%c\n", buf1[i]);
                a = buf1[i];
                write(fd2, &a, 1);
            }
            release(fs,1);
        }
    }

    // access consonant child
    else if (child2) {
        sleep(1);
        lseek(fd, 0, SEEK_SET);
        printf("I'm the second child\n");
        while ((read(fd, &a, 1)) == 1) {
            if (a == 'a' || a == 'e' || a == 'i' || a == 'o' || a == 'u');
            else
                buf2[count2] = a;
            count2++;
        }
        // resync
        sleep(1);
        printf("count: %d\n", count2);
        // consonants are now into buf1

        for (i; i <= count2 + 1; i++) {
            acquire(fs,2);
            if (buf2[i] != '\0') {
                printf("%c\n", buf2[i]);
                b = buf2[i];
                write(fd2, &b, 1);
            }                           // wait for vowel
            release(fs,2);
        }
    }

    else {
        printf("I'm the father\n");
        while (wait(NULL) >= 0);
        printf("all done\n");
    }

    //sleep(10);
    exit(0);
}

UPDATE:

The two child processes share a common file position for the semaphore file because the open call is done in the parent.

So, there is a potential race condition between the lseek and the read or write.

There are two ways to solve this:

  1. Have each child open the semaphore file after the fork. Then, they do not share the file position.
  2. Instead of lseek followed by read or write, use pread or pwrite.

Here is the latter:

void
acquire(int fs,char iam)
{
    char own;

    while (1) {
        pread(fs,&own,1,0);

        if (own == iam)
            break;

        usleep(1);
    }
}

void
release(int fs,char iam)
{
    char other;

    if (iam == 1)
        other = 2;
    else
        other = 1;

    pwrite(fs,&other,1,0);
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48