7.3.5 forking and files again: Using process IDs

7.3.5  forking and files again: Using process IDs

 Let us look again at the programs we looked at a short while ago. In this section, we clearly determine which process is writing into a specific file in what order. We also discuss reading from files in this section.

First, we look at a program that forks a process and each process prints one line to the same shared filehandle. This time, a process indicates that it is writing to the file.

 Program 7.21

#!/usr/bin/perl
use IO::Handle;
use strict;

open (OUT, ">out.txt");
print OUT "Before forking\n";
#flush the buffer associated with the parent process
autoflush OUT 1; #flush the output, i.e., print to file immediately

my $pID = fork ();

#Both processes print this line
if ($pID){ #parent process
    print OUT "Parent: After forking\n";
}
else{
    print OUT "Child: After forking\n";
}

close OUT;

The output of this program redirected to the file out.txt is given below.

Before forking
Parent: After forking
Child: After forking

As we know, after forking, one line is written to the file by the parent process and the other by the child. Both processes close the filehandle.

In the next program, we fork a process right in the beginning. These two processes each open the same file in.txt for reading. The two processes print out what they read to the standard output. The file has the numbers 1 through 10, one number per line.

 Program 7.22

#!/usr/bin/perl
use strict;

my $pID = fork ();

#Both processes read lines from IN
if ($pID){ #parent process
    print "File opened by Parent\n";
    open (IN, "in.txt");
    while (){
        print "Parent: $_";
        sleep 1;
    }
    close IN;
    print "File closed by Parent\n";
}
else{ #child process
    print "File opened by Child\n";
    open (IN, "in.txt");
    while (){
        print "Child: $_";
        sleep 2;
    }
    close IN;
    print "File closed by Child\n";
}

We have used two different amount of sleep time in the two processes to stagger the printing each one does. The output of the program printed to STDOUT is given below.

File opened by Parent
Parent: 1
File opened by Child
Child: 1
Parent: 2
Child: 2
Parent: 3
Parent: 4
Child: 3
Parent: 5
Parent: 6
Child: 4
Parent: 7
Parent: 8
Child: 5
Parent: 9
Parent: 10
Child: 6
File closed by Parent
Child: 7
Child: 8
Child: 9
Child: 10
File closed by Child

The output clearly shows that each of the two processes opens the same file separately. Each process keeps a copy of the filehandle IN. Each process has a pointer that points to, possibly a different part of the same file. Each process closes its access to the file using its filehandle. When one process closes the file, the other process still has the file open.

The following program shows a more complex situation in reading from a shared file. In the previous program, there were two filehandles and two file pointers to begin with. That is not the case in this program. Here, the parent process opens a file before forking. Therefore, there is only one filehandle to begin with. But, then, the parent process forks. This causes a copy of the filehandle to be made and given to the child process. In this case, we are performing unbuffered read and write operations. In Perl, a file can be opened for unbuffered reading using sysopen instead of open. In addition, to read in an unbuffered mode from a file, we need to use the sysread command instead of the angle bracket operator we have used most frequently so far. To print in unbuffered mode, the command used is syswrite instead of print.

 Program 7.23

#!/usr/bin/perl
use Fcntl;
use strict;
my ($childPID);

sysopen (IN, "in1.txt", O_RDONLY);

$childPID = fork ();

while (1){
#Both processes read lines
    if ($childPID){ #parent
        sysread (IN, $_, 1);
        if (!$_){last;}
        syswrite  STDOUT, "Parent: $_", 9;
        syswrite  STDOUT, "\n", 1;
    }
    else { #child
        sysread (IN, $_, 1);
        if (!$_){last;}
        syswrite  STDOUT, "Child: $_", 8;
        syswrite  STDOUT, "\n", 1;
    }
}

close IN;

Assume the file in1.txt has the following content.

 

 1234567891011121314151617181920

 

The output of the program is given below. Each process in the program reads one character in unbuffered mode. Each process also prints in unbuffered mode. As a result, the printing operations performed by the two processes is mixed up. 

Parent: 1
Child: 2
Child: 3
Child: 4
Child: 5
Child: 6
Child: 7
Child: 8
Child: 9
Child: 1

Parent: 1
Parent: 1
Parent: 1
Parent: 2
Parent: 1
Parent: 3
Parent: 1
Parent: 4
Parent: 1
Parent: 5
Parent: 1
Parent: 6
Parent: 1
Parent: 7
Parent: 1
Parent: 8
Parent: 1
Parent: 9
Parent: 2

This program uses the Fcntl module to use certain constants such as O_RDONLY with the sysopen command. The program opens a file by the name in1.txt for reading. It uses sysopen so that it can read byte by byte. The O_RDONLY argument to sysopen indicates that the file is opened for reading only. The
program forks next creating a process whose process ID is $childPID, as known by the parent process. Next, the program goes into a while loop that runs for ever, i.e., till exited explicitly from within the loop.

As we know well by now, after the fork, there are two processes executing the code in the program. As usual, we have an if-else statement inside the while loop so that the two processes can execute some part of the code independently of each other. The parent process uses sysread to read one byte from the filehandle IN. The second argument to sysread
contains the variable to which the value is being read. If the byte read is 0 or the empty string, the parent process finishes the loop by executing the last command. If the byte read is non-zero or non-empty, the parent process prints it using syswrite. The last argument to syswrite is the number of bytes written.

The child process executes the code in the else block. The child process also reads a byte and prints it if it is non-zero or non-empty. If it is zero or the empty string, the child process exits the loop.