问题
I am trying to create separate child process for each letter that needs to be counted in a file. I have the file being read in a parent process, but the output is all zeros. I don't understand what I am doing wrong. I need to use child processes for each of the letters, but I am not exactly sure how to create separate processes for each letter. Please help! Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/wait.h>
int main(int argc, char **argv){
char characters[26] = { "abcdefghijklmnopqrstuvwxyz" };
int counter[26] = { 0 };
int n = 26;
int c;
char ch;
if (argc < 2)
return 1;
pid_t pids[26];
for (int i = 0; i < n; i++){
pids[i] = fork();
if (pids[i] < 0) {
printf("Error");
exit(1);
} else if (pids[i] == 0) {
while (c = fgetc(file) != EOF){
if (c == characters[i])
counter[i]++;
}
exit(0);
} else {
FILE *file = fopen(argv[1], "r");
if(file == NULL)
printf("File not found\n");
while (c = fgetc(file) != EOF);
fclose(file);
wait(NULL);
}
}
for (int i = 0; i < n; ++i){
printf("%c: %i\n", characters[i], counter[i]);
}
return 0;
}
回答1:
The problem with forking when the parent has open a file for reading, is that although all children inherit copies of open file descriptors, they all share the same file description.
man fork
The child process is an exact duplicate of the parent process except for the following points:
[...]
- The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two file descriptors share open file status flags, file offset, and signal-driven I/O attributes (see the description of
F_SETOWNandF_SETSIGin fcntl(2)).
You can do such a program, but you would have to synchronize the children with
each other, because every time a child does fgetc(file), the file description
advances for all children. The synchronization would have to be written such as
all children wait for the others to stop reading, do a rewind and then finally
read. In that case having all these children is no gain at all.
For more information about that, see this excellent answer from this question: Can anyone explain a simple description regarding 'file descriptor' after fork()?
Another problem with your code is this:
printf("%c: %i\n", characters[i], counter[i]);
fork duplicates the process and they both run in separate memory spaces.
The children's counter is a copy of the parent's counter, but a modification
of counter in a child process will only affect the counter for that process,
the parent's counter is not affected by that. So in this case you are always
printing 0, because the parent never changed counter.
Also, even if the modification of a child's counter would somehow propagate to
the parent, the parent should wait for the child process to make the
modification before accessing the variable. Again synchronization would be
needed for that.
For the parent to benefit of the work of the children, it must communicate with the children. One way to do it is by creating a pipe for each of the children. The parent closes the writing end of the pipes, the children close the reading end of the pipe. When the child does its work, it writes the results on the writing end of it's pipe back to the parent and exits. The parent must then wait for every children, read from the reading end of the pipe.
This program does exactly that:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv)
{
char characters[26] = "abcdefghijklmnopqrstuvwxyz";
if(argc != 2)
{
fprintf(stderr, "usage: %s file\n", argv[0]);
return 1;
}
size_t i;
int pipes[26][2];
// creating the pipes for all children
for(i = 0; i < 26; ++i)
{
if(pipe(pipes[i]) < 0)
{
perror("unable to create a pipe");
return 1;
}
}
pid_t pids[26];
memset(pids, -1, sizeof pids);
for(i = 0; i < 26; ++i)
{
pids[i] = fork();
if(pids[i] < 0)
{
fprintf(stderr, "Unable to fork for child %lu: %s\n", i, strerror(errno));
continue;
}
if(pids[i] == 0)
{
// CHILD process
// closing reading end of pipe
close(pipes[i][0]);
FILE *fp = fopen(argv[1], "r");
if(fp == NULL)
{
close(pipes[i][1]);
exit(1);
}
int n = 0, c;
while((c = getc(fp)) != EOF)
{
if(c == characters[i])
n++;
}
// sending answer back to parent through the pipe
write(pipes[i][1], &n, sizeof n);
fclose(fp);
close(pipes[i][1]);
exit(0);
}
// PARENT process
// closing writing end of pipe
close(pipes[i][1]);
}
printf("Frequency of characters for %s\n", argv[1]);
for(i = 0; i < 26; ++i)
{
if(pids[i] < 0)
{
fprintf(stderr, "%c: could not create child worker\n", (char) i + 'a');
close(pipes[i][0]);
continue;
}
int status;
waitpid(pids[i], &status, 0);
if(WIFEXITED(status) && WEXITSTATUS(status) == 0)
{
// child ended normally and wrote result
int cnt;
read(pipes[i][0], &cnt, sizeof cnt);
printf("%c: %d\n", (char) i + 'a', cnt);
} else {
printf("%c: no answer from child\n", (char) i + 'a');
}
close(pipes[i][0]);
}
return 0;
}
The parent creates 26 pipes, each one for a child. The it creates an array for
the pids and initializes them to -1 (later for error checking). Then enters in
the loop and creates a new child and closes the writing end of the parent's pipe
for the i-th child. Then it goes again into a loop and checks if a child
process was created for every character. If that's the case, it waits for that
child to exit and checks it's exit status. If and only if the child exits
normally (with an exit status of 0), it reads from the reading end of the pipe
and prints the result, otherwise it prints an error message. Then it closes the
reading end of the pipe and exits.
Meanwhile every child closes its reading end of the pipe and open a file for reading. By doing this, the children don't share the file description and can independently from each other read the contents of the file and calculate the frequency of the letter assigned to the child. If something goes wrong when opening the file, the child closes the writing end of the pipe and exits with a return status of 1, signalling to the parent, that something went wrong and that it won't send any result through the pipe. If everything goes well, the child writes the result in the writing end of the pipe and exits with an exit status of 0.
The output of this program with the its source is:
$ ./counter counter.c
Frequency of characters for counter.c
a: 44
b: 5
c: 56
d: 39
e: 90
f: 40
g: 17
h: 26
i: 113
j: 1
k: 5
l: 35
m: 6
n: 68
o: 45
p: 59
q: 2
r: 78
s: 71
t: 65
u: 25
v: 5
w: 10
x: 3
y: 6
z: 5
来源:https://stackoverflow.com/questions/48547996/creating-seperate-child-function-for-counting-each-letter-in-a-file