Reputation: 367
I try to read numbers from a file using a father process and child process.
I have a file containing the prime numbers between 1 and 100. I try to read the 10 first numbers using the father process (it works well). After that I create a child process using fork and read the next 10 numbers (it works also well). I use wait() to make the father process wait for the child to finish. Then I kill the child process using kill(). Then, I try continue to read the remaining numbers from the file with the father process but the results are not the same as expected:
Here is my code:
pid_t create_process()
{
pid_t pid;
do {
pid = fork();
} while (pid == -1 && errno == EAGAIN);
return pid;
}
int main()
{
FILE *fichier = fopen("entiers.txt","r");
int i=0;
int n=0;
if (fichier != 0)
{
printf("I am the father process of the pid %d\n",getpid());
for(i=0; i<10; i++)
{
fscanf(fichier, "%d\n", &n);
printf("%d\n",n);
}
pid_t pid = create_process();
if(pid)
{
wait(NULL);
printf("I am the father process of the pid %d\n",getpid());
do
{
n = fgetc(fichier);
printf("%d\n",n);
} while (n != EOF);
fclose(fichier);
kill(pid,SIGKILL);
}
if (!pid)
{
printf("I am the child process of the pid %d\n",getpid());
for(i=0; i<10; i++)
{
fscanf(fichier, "%d\n", &n);
printf("%d\n",n);
}
}
}
return EXIT_SUCCESS;
}
This is my file:
1
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
And this my output:
I am the father process of the pid: 8213 1 2 3 5 7 11 13 17 19 23 I am the child process of the pid: 8214 29 31 37 41 43 47 53 59 61 67 I am the father process of the pid: 8213 50 57 10 51 49 10 51 55 10 52 49 10 52 51 10 52 55 10 53 51 10 53 57 10 54 49 10 54 55 10 55 49 10 55 51 10 55 57 10 56 51 10 56 57 10 57 55 10 55 49 10 55 51 10 55 57 10 56 51 10 56 57 10 57 55 10 -1
Any help?
Upvotes: 0
Views: 3067
Reputation: 753695
Given that the data file is 73 bytes long (give or take — you might have extra white space around that I didn't guess at), the first call to fscanf()
will read the whole file into memory. The parent process then reads 10 lines worth from memory, moving the read pointer in the standard I/O buffer. The trailing newlines in the fscanf()
format strings are not really needed; the %d
skips white space, which includes newlines, and if the input were not coming from a file, the trailing blank line would be a very bad UX — the user would have to type the (start of the) next number to complete the current input. (See scanf()
leaves the newline in the buffer and What is the effect of trailing white space in a scanf()
format string?.)
Then the process forks. The child is an exact copy of the parent, so it continues reading where the parent left off, and prints 10 numbers as you expected, and then exits.
The parent process then resumes. It has done nothing to change the position of the pointer in memory, so it continues where it left off. However, the reading code now reads single characters and prints their decimal values, so it gets 50,
57,
10 — the character codes for '2'
, '9'
, and '\n'
. And so the output continues for all the rest of the prime numbers in the input.
You really need to fix the input to resume using fscanf()
instead of fgetc()
.
There isn't a sensible way for the parent to know what the child has done other than by changing from buffered I/O to unbuffered I/O. If you switched to unbuffered I/O, by calling setbuf(fichier, NULL);
or setvbuf(fichier, NULL, _IONBF, 0);
after opening the file but before doing any other operation on the file stream, then you would see that the parent process continues where it left off.
A side-note: I'm not convinced about the loop in create_process()
— if there aren't enough resources, at least wait a little to give the system time to find some, but it is more common to treat 'out of resources' as a fatal error.
Another side-note: sending a signal to a process that's already died (because you waited for it to die) isn't going to work.
Here's some revised code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
static pid_t create_process(void)
{
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "Failed to fork\n");
exit(1);
}
return pid;
}
int main(void)
{
const char filename[] = "entiers.txt";
FILE *fichier = fopen(filename, "r");
int i = 0;
int n = 0;
// setbuf(fichier, NULL);
// setvbuf(fichier, NULL, _IONBF, 0);
if (fichier == 0)
{
fprintf(stderr, "Failed to open file '%s' for reading\n", filename);
exit(1);
}
printf("I am the parent process with the pid %d\n", getpid());
for (i = 0; i < 10; i++)
{
if (fscanf(fichier, "%d", &n) != 1)
break;
printf("%d\n", n);
}
pid_t pid = create_process();
if (pid == 0)
{
printf("I am the child process with the pid %d\n", getpid());
for (i = 0; i < 10; i++)
{
if (fscanf(fichier, "%d", &n) != 1)
break;
printf("%d\n", n);
}
}
else
{
wait(NULL);
printf("I am the parent process with the pid %d\n", getpid());
while (fscanf(fichier, "%d", &n) == 1)
printf("%d\n", n);
}
fclose(fichier);
return EXIT_SUCCESS;
}
Sample output:
I am the parent process with the pid 15704
2
3
5
7
11
13
17
19
23
29
I am the child process with the pid 15705
31
37
41
43
47
53
59
61
67
71
I am the parent process with the pid 15704
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
Very often, questions like this involve file descriptor I/O and the discussion has to cover the different between an open file descriptor and an open file description and explain what's shared between processes and what isn't. Because the input file is so small, that isn't an issue with this code. If the table of primes went up to, say, 999983 (the largest prime smaller than a million), and the child process read much more data, then you'd see different effects altogether.
scanf()
format stringsEmpirical observation shows that when the original version of the code shown above had scanf("%d\n", &n)
in both the parent's first read loop and the child's read loop, and the program was configured to use unbuffered input, the output would look like:
…
67
71
I am the parent process with the pid 27071
33
79
…
where the 33
isn't expected at first glance. However, there is an explanation for what goes wrong.
There's at least one byte of pushback available on the stream (even with no buffering), so at the point where the parent forks, both parent and child have the 3
from 31
in the pushback position (because the newline was read as a white space character and the first non-blank, aka the 3
of the line containing 31
was read and pushed back into the input buffer).
The child is an almost exact copy of the parent, and reads the pushback character and continues with the 1
and gets the newline and then the 3
of 37
, and prints 31
as you'd expect. This continues until it reads the 7
at the start of 73
and pushes it back into its own input buffer, but that has no effect on the parent's input buffer of course (they're separate processes). The child exits.
The parent resumes. It has a 3
in its pushback position, and then gets the 3
from 73
(because the parent and child share the same open file description, and the read position is associated with the description, not the descriptor, so the child has moved the read position), and then it gets a newline and and terminates its scanning (the last loop was missing the trailing white space in the scanf()
format string anyway), and prints 33
correctly. It then proceeds to read the rest of the input cleanly, skipping over white space (newline) before reading each number.
Changing the code to use fscanf(fichier, "%d", &n)
throughout means that the child process stops with the newline before 73
in its pushback buffer, and the read position pointing at the 7
of 73
, which is exactly where the parent needs it.
If the first parent loop had omitted the newline in the fscanf()
format, then the child would still have worked, but the parent would have reported 3
as the first number when it resumed, instead of 33
.
Upvotes: 3
Reputation: 12668
Sorry, but you say:
I have a file containing the prime numbers between 1 and 100. I try to read the 10 first numbers using the father process (it works well). After that I create a child process using fork and read the next 10 numbers (it works also well). I use wait() to make the father process wait for the child to finish. Then I kill the child process using kill(). Then, I try continue to read the remaining numbers from the file with the father process but the results are not the same as expected:
What is the reason for waiting the child to die, if once it dies, you kill it again. Have you had any case in which the child lived after dying?
Anycase, when you use any of the <stdio.h>
package routines, input is buffered, so when you do a single fgetc()
, fread()
, fscanf()
, etc. call, the input is gathered in blocks, making the remaining bytes ready for successive reads, but already taken from the system.
What this means is, from standard input, when you read a terminal, only one line is read and input goes line by line (yes, even if you asked only for a char) and when you read of a text file, your input is block by block (see the value of #define BLOCSZ
in <stdio.h>
) This means that what the parent gets for it is not the text upto the tenth prime, but the text upto the end of the next block of data. This will mangle the next reader, as if you shared the file descriptor (you opened it before fork()
ing) the pointer to the file is shared between both processes... and probably what the child gets is not at the proper offset in the file.
A second point is that you cannot control the scheduling order of processes, and if the child does its read on the file before or after the father does its one. I've seen you do the read of the first 10 numbers before forking. This warrants the parent process gets the first 10 number in the file... but the buffer can have more stored for reading (worse, the buffer can end half a number, so when you read in the child, it can get in the middle of a number, reading only the second half) But you could end with the reads in the order you want if you properly synchronize the processes and consider properly what happens with buffering.
Upvotes: 1
Reputation: 33601
In the parent, you are doing fgetc
in the second loop, but I think you need to do fscanf
as you do in the child.
Also, the kill
is not necessary because of the preceding wait
(i.e. the child has already terminated [cleanly]).
Note that the parent will redo some of the numbers that the child has processed. This is [probably] because the parent's stream has prebuffered such a small file.
To remedy that, add setbuf(fichier,NULL);
immediately after the fopen
.
Also, remove all \n
from fscanf
. I had done this in earlier versions, but missed one [as Davis pointed out below]. From some of my earlier edits, you can see that it added an extraneous 271
instead of the [correct] 71
on final parent output.
Here's a fix to your code [please pardon the gratuitous style cleanup and some extra debugging code]:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
char buf[30];
char *
tellme(FILE *fi)
{
sprintf(buf,"pos=%llu",ftell(fi));
return buf;
}
pid_t
create_process(void)
{
pid_t pid;
do {
pid = fork();
} while (pid == -1 && errno == EAGAIN);
return pid;
}
int
main(void)
{
FILE *fichier = fopen("entiers.txt", "r");
int i = 0;
int n = 0;
if (fichier == NULL)
return EXIT_FAILURE;
#if 1
setbuf(fichier,NULL);
#endif
printf("I am the father process of the pid %d\n", getpid());
for (i = 0; i < 10; i++) {
fscanf(fichier, "%d", &n);
printf(" %d", n);
}
printf("\n");
printf("I am the father process of the pid %d -- %s\n",
getpid(),tellme(fichier));
fflush(stdout);
pid_t pid = create_process();
if (pid) {
wait(NULL);
#if 0
fflush(fichier);
#endif
printf("I am the father process of the pid %d -- %s\n",
getpid(),tellme(fichier));
#if 0
do {
n = fgetc(fichier);
printf("%d\n", n);
} while (n != EOF);
#else
while (1) {
if (fscanf(fichier, "%d", &n) != 1)
break;
printf(" %d", n);
}
printf("\n");
#endif
fclose(fichier);
// NOTE/BUG: process has already terminated
#if 0
kill(pid, SIGKILL);
#endif
}
if (!pid) {
printf("I am the child process of the pid %d -- %s\n",
getpid(),tellme(fichier));
for (i = 0; i < 10; i++) {
fscanf(fichier, "%d", &n);
printf(" %d", n);
}
printf("\n");
printf("I am the child process of the pid %d -- %s\n",
getpid(),tellme(fichier));
fflush(stdout);
}
return EXIT_SUCCESS;
}
Here's the output (without the setbuf
):
I am the father process of the pid 395735
1 2 3 5 7 11 13 17 19 23
I am the child process of the pid 395736
29 31 37 41 43 47 53 59 61 67
I am the father process of the pid 395735
1 2 3 5 7 11 13 17 19 23
I am the father process of the pid 395735
29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 71 73 79 83 89 97
Here's the output (with the setbuf
and with the newline removed from [various] fscanf
):
I am the father process of the pid 399457
1 2 3 5 7 11 13 17 19 23
I am the father process of the pid 399457 -- pos=25
I am the child process of the pid 399458 -- pos=25
29 31 37 41 43 47 53 59 61 67
I am the child process of the pid 399458 -- pos=54
I am the father process of the pid 399457 -- pos=54
71 73 79 83 89 97
Upvotes: 1