I want the parent process to take the arguments to main() and send the characters in them one at a time to the child process through a pipe starting with argv[1] and continue through the rest of the arguments.(one call to write for each character).
I want the child process to count the characters sent to it by the parent process and print out the number of characters it received from the parent. The child process should not use the arguments to main() in any way whatsoever.
What am i doing wrong? do i need to use exec()?
output that isnt correct:
~ $ gc a03
gcc -Wall -g a03.c -o a03
~ $ ./a03 abcd ef ghi
child: counted 12 characters
~ $
here is the program..
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int length = 0;
int i, count;
int fdest[2]; // for pipe
pid_t pid; //process IDs
char buffer[BUFSIZ];
if (pipe(fdest) < 0) /* attempt to create pipe */
printf("pipe error");
if ((pid = fork()) < 0) /* attempt to create child / parent process */
{
printf("fork error");
}
/* parent process */
else if (pid > 0) {
close(fdest[0]);
for(i = 1; i < argc; i++) /* write to pipe */
{
write(fdest[1], argv[i], strlen(argv[1]));
}
wait(0);
} else {
/* child Process */
close(fdest[1]);
for(i = 0; i < argc; i++)
{
length +=( strlen(argv[i])); /* get length of arguments */
}
count = read(fdest[0], buffer, length);
printf("\nchild: counted %d characters\n", count);
}
exit(0);
}
You said that "the child process should not use the arguments to main() in any way whatsoever". However, I see that your child process is using argc. Doesn't this defeat your restriction?
You also say that you want "one call to write for each character". Your current implementation uses one call to write for each argument, not each character. Was this a typo? If not, you will want to use something more like this:
char nul='\0', endl='\n';
for (a=1; a < argc; ++a) {
for (c=0; c < strlen(argv[a]); ++c) {
write(fdest[1], &argv[a][c], 1);
}
write(fdest[1], &nul, 1);
}
write(fdest[1], &endl, 1);
This will write one character at a time, with each argument as a NULL-terminated string and a newline character at the end. The newline is only there to serve as a marker to indicate that there is no more data to send (and is safe to use since you won't be passing in a newline in a CLI argument).
The child process will just need to be a loop that reads incoming bytes one by one and increments a counter if the byte is not '\0' or '\n'. When it reads the newline character, it breaks out of the input processing loop and reports the value of the counter.
You have an error here:
write(fdest[1], argv[i], strlen(argv[1]));
You should take strlen(argv[i]) rather, or you're telling write() to read past the space of argv[i] and invoke undefined behavior.
Note that you're only calling read() once. By the time you're calling read(), perhaps only one of the argv[]s have been written by the parent. Or 2. Or any number of them.
The problem is here
write(fdest[1], argv[i], strlen(argv[1]));
Notice that this is strlen of argv[1], it should be argv[i]. You are actually referencing past the end of argv[2] and argv[3] in this loop
You are effectively writing strlen("abcd") * 3 characters which is 12 chars
In here:
for(i = 1; i < argc; i++) /* write to pipe */
{
write(fdest[1], argv[i], strlen(argv[1]));
}
strlen(argv[1]) should be in fact strlen(argv[i])
Related
I have an assignment requiring me to write a multi-processed program that works with a memory-mapped file containing a string of characters. After the parent process maps the file to memory, it spawns 2 children processes to modify the file. Child 1 outputs the contents of the file, converts the file's contents to their upper case equivalent, then outputs the file's new contents. Child 2 waits 1 second to let child 1 finish, outputs the file's contents, removes any hyphen " - " characters, then outputs the file's new contents. My problem with both child processes is that after first displaying the file's contents, the processes attempt to modify the contents of the file, but neither child outputs the file's new contents. I get no errors when running or compiling so I can't find out what the problem is. And of course, I'm new to memory mapping so feel free to let me know what I'm doing wrong. Here is my source code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
int main (int argc, char *argv[]) {
struct stat buf;
int fd, length, status, i, j, k;
char *mm_file;
char *string = "this is a lowercase-sentence.";
length = strlen(string);
fd = open(argv[1], O_CREAT | O_RDWR, 0666); //Creates file with name given at command line
write(fd, string, strlen(string)); //Writes the string to be modified to the file
fstat(fd, &buf); //used to determine the size of the file
//Establishes the mapping
if ((mm_file = mmap(0, (size_t) buf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == (caddr_t) - 1) {
fprintf(stderr, "mmap call fails\n");
}
//Initializes child processes
pid_t MC0;
pid_t MC1;
//Creates series of child processes which share the same parent ID
if((MC0 = fork()) == 0) {
printf("Child 1 %d reads: \n %s\n", getpid(), mm_file);
//{convert file content to uppercase string};
for (i = 0; i < length; i++) {
string[i] = toupper(string[i]);
}
//sync the new contents to the file
msync(0, (size_t) buf.st_size, MS_SYNC);
printf("Child 1 %d reads again: \n %s\n", getpid(), mm_file);
exit(EXIT_SUCCESS); //Exits process
} else if ((MC1 = fork()) == 0) {
sleep(1); //so that child 2 will perform its task after child 1 finishes
("Child 2 %d reads: \n %s\n", getpid(), mm_file);
//{remove hyphens}
for (j = 0; j < length; i++) {
if (string[i] == '-') {
string[i] = ' ';
}
}
//sync the new contents to the file
msync(0, (size_t) buf.st_size, MS_SYNC);
printf("Child 2 %d reads again: \n %s\n", getpid(), mm_file);
exit(EXIT_SUCCESS); //Exits process
}
// Waits for all child processes to finish before continuing.
waitpid(MC0, &status, 0);
waitpid(MC1, &status, 0);
return 0;
}
Then my output is as follows:
**virtual-machine:~$** ./testt file
Child 1 3404 reads:
this is a lowercase-sentence.
Child 2 3405 reads:
this is a lowercase-sentence.
All child processes have finished. Now exiting program.
**virtual-machine:~$**
But my desired result would be:
**virtual-machine:~$** ./testt file
Child 1 3404 reads:
this is a lowercase-sentence.
Child 1 3404 reads again:
THIS IS A LOWERCASE-SENTENCE.
Child 2 3405 reads:
THIS IS A LOWERCASE-SENTENCE.
Child 2 3405 reads:
THIS IS A LOWERCASE SENTENCE.
All child processes have finished. Now exiting program.
**virtual-machine:~$**
Any help is greatly appreciated.
There are a few errors here. Firstly, you write into the file and then map it into the memory. The mapping is correct, but the writing not. If the string has n characters, you have to write n+1 characters, since strings in C are null-terminated. Now you only have n, so all C string functions will try to access at least one more byte, which is not good. And if that one extra byte is not null (zero), the functions will go even further. In debug more they might be zeroed, but in optimized code usually not. So you have to use
write(fd, string, strlen(string)+1); //Writes the string to be modified to the file
Then you do this:
for (i = 0; i < length; i++) {
string[i] = toupper(string[i]);
}
This only changes the data that is referred by the pointer string, which has nothing to do with the memory mapped file. You should have:
for (i = 0; i < length; i++) {
mm_file[i] = toupper(mm_file[i]);
}
The same is with the second child process.
Also your msync() call is a bit suspect. You give the memory address as 0, which is not within your memory mapped file, so it will not sync the contents. You need to call msync(mm_file, (size_t) buf.st_size, MS_SYNC);
Also, many compilers will put the constant strings into read-only memory, so you might not even be allowed to change the data referred to by string. In this case it seems you are allowed.
Do also remember, that the length of the file is one byte larger than the length of the string, so use the variables correctly. Currently you do, since you sync the file with file length and handle the string with string length.
You have let mem map get in the way of the logic.
To get this working comment out all mem map stuff and just work on the file.
This will show you that neither child reads from the input file, never mind writing new contents to it.
Under some operating systems, Linux being one if you are mixing reads and writes you need a seek between to keep the write and read pointers in the same position. This may have to be fseek(stream, 0,SEEK_CUR);
Child one should be something like
// Lock file here
rewind(file);
printf ("child 1 reads ");
int ch;
while(1){
ch = fgetc(file);
if(ch == EOF) break;
fputc (Ch,stdout );
}
fputc('\n',stdout );
Rewind(file);
while(1){
ch=fgetc (file);
if(Ch == EOF) break;
fseek(file,-1,SEEK_CUR);
fputc (toupper (Ch),file);
fseek(file,0,SEEK_CUR);
}
Rewind ( file);
Printf (" child 1 reads ");
while(1){
ch=fgetc(file);
if(Ch == EOF) break;
fputc (Ch,file) ;
}
// Unlock file here
Because you have multiple processes acting on the same object you have to implement write locking ( exclusive locks).
Read man pages flock(2), fcntl (2) and lockf(3).
These cooperative locks could be implemented as a semaphore.
With out locking both children may try to write to the same character simultaneously, in this example it shouldn't matter as one child does hyphens and the other letters.
Now it's working uncomment your mem map stuff.
First off, this IS homework, I am not asking for an answer, however I am confused about something.
I have a homework assignment for a programming class, and I am a little confused about how to write the code the specific way that the instructor is asking.
The program first creates a child process, and then proceeds to send command line arguments from the parent process, through a pipe, ONE CHARACTER at a time to the child process, and then read them into the child process ONE CHARACTER at a time, incrementing the character count in the child process each time a character is read in.
I think I accomplished sending the data through the pipe one character at a time, but I have no idea how to "go" to the child process every time a character is sent, read it, increment the number of characters, and then go back to the parent process and repeat.
Here is my code, It works and gives accurate answers, but any tips on how to accomplish what my instructor is asking would be appreciated, thank you!!
// Characters from command line arguments are sent to child process
// from parent process one at a time through pipe.
//
// Child process counts number of characters sent through pipe.
//
// Child process returns number of characters counted to parent process.
//
// Parent process prints number of characters counted by child process.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
// set up pipe
int pA[2];
char buff[50];
pipe(pA);
// call fork()
pid_t childId = fork();
if (childId == 0) {
// -- running in child process --
int nChars = 0;
// close the output side of pipe
close(pA[1]);
// Receive characters from parent process via pipe
// one at a time, and count them.
nChars = read(pA[0], buff, sizeof(buff)); //this line of code is what i need to change to be reading characters in one at a time
// Return number of characters counted to parent process.
return nChars;
}
else {
// -- running in parent process --
int nChars = 0;
int size = 0;
printf("CS201 - Assignment 3 - Timothy Jensen\n");
// close the input side of the pipe
close(pA[0]);
// Send characters from command line arguments starting with
// argv[1] one at a time through pipe to child process.
for (int i = 1; i < argc; i++)
{
size = strlen(argv[i]);
for (int z = 0; z < size; z++)
{
write(pA[1], &argv[i][z], 1);
}
}
// Wait for child process to return. Reap child process.
// Receive number of characters counted via the value
// returned when the child process is reaped.
wait(&nChars);
printf("child counted %d chars\n", nChars/256);
return 0;
}
}
You need to make the following changes:
Make the last argument 1 in the call to read.
read(pA[0], buff, 1);
Put the above call in a while loop and increment nChar for every successful attempt at read.
while ( read(pA[0], buff, 1) == 1 )
{
++nChars;
}
Close the file descriptor from the parent process once you are done writing to it.
Here's a working version of main.
int main(int argc, char **argv)
{
// set up pipe
int pA[2];
char buff[50];
pipe(pA);
// call fork()
pid_t childId = fork();
if (childId == 0) {
// -- running in child process --
int nChars = 0;
// close the output side of pipe
close(pA[1]);
// Receive characters from parent process via pipe
// one at a time, and count them.
while ( read(pA[0], buff, 1) == 1 )
{
++nChars;
}
return nChars;
}
else {
// -- running in parent process --
int nChars = 0;
int size = 0;
printf("CS201 - Assignment 3 - Timothy Jensen\n");
// close the input side of the pipe
close(pA[0]);
// Send characters from command line arguments starting with
// argv[1] one at a time through pipe to child process.
for (int i = 1; i < argc; i++)
{
size = strlen(argv[i]);
for (int z = 0; z < size; z++)
{
write(pA[1], &argv[i][z], 1);
}
}
close(pA[1]);
// Wait for child process to return. Reap child process.
// Receive number of characters counted via the value
// returned when the child process is reaped.
wait(&nChars);
printf("child counted %d chars\n", nChars/256);
return 0;
}
}
It seems a little silly, but you could change:
nChars = read(pA[0], buff, sizeof(buff));
to:
char ch;
nChars = read(pA[0], &ch, 1);
Of course, you would put the above into a loop to assemble a string 'one character at a time' back into buff.
This is a program that was designed to take characters from the program call, pipe them one at a time to a child, count them in the child, return that value to the parent and print that value.
For some reason, the number of characters input isn't being displayed. It compiles without error and runs, but doesn't exit properly. This leads me to believe that the parent doesn't successfully reap the child and take the return value from it.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, char **argv)
{
int comm[2];
char buffer[50];
pid_t pid;
// set up pipe
pipe(comm);
// call fork()
pid = fork();
// code that runs in the child
if (pid == 0) {
// -- running in child process --
int nChars = 0;
close(comm[1]);
// Receive characters from parent process via pipe
// one at a time, and count them.
while(read(comm[0], buffer, 1) ==1) {
++nChars;
}
// Return number of characters counted to parent process.
return nChars;
}
else {
// -- running in parent process --
int nChars = 0;
int size = 0;
printf("CS201 - Assignment 3 - \n");
// Send characters from command line arguments starting with
// argv[1] one at a time through pipe to child process.
close(comm[0]);
for (int i = 1; i < argc ; i++) {
size = strlen(argv[i]);
for (int j = 0; j < size; j++) {
write(comm[1], &argv[i][j], 1);
}
}
// Wait for child process to return. Reap child process.
// Receive number of characters counted via the value
// returned when the child process is reaped.
wait(&nChars);
printf("child counted %d chars\n", nChars/256);
return 0;
}
}
Your parent process needs to close the pipe after it's done writing.
// Send characters from command line arguments starting with
// argv[1] one at a time through pipe to child process.
close(comm[0]);
for (int i = 1; i < argc ; i++) {
size = strlen(argv[i]);
for (int j = 0; j < size; j++) {
write(comm[1], &argv[i][j], 1);
}
}
close(comm[1]); // <--- add this
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int i;
int fd[2];
int values[argc - 1];
for( i = 1; i < argc; i++ ) {
pipe(fd);
switch( fork()) {
case 0: /* child */
/* Do stuff */
close(fd[0]);
int value = atoi(argv[i]);
write(fd[1], &value, sizeof(value));
exit( 0 );
case -1:
perror( "fork" );
exit(1);
default: /* parent */
close(fd[1]);
read(fd[0], &values[i - 1], sizeof(values[i - 1]));
/* do stuff, but don't wait() or terminate */
}
}
for (i = 0; i < (argc - 1); i++)
{
printf("%d\n", values[i]);
}
return 0;
}
im trying to create as many processes as the number of arguments given to the executable and have each process pipe the argument to the parent and store into an array and at the end print out the elements of the array. since the processes run in parallel, when i print out the array elements, the order should be random with respect to the order i entered these elements, but that doesnt seem to be the case as i have ran the executable 1 million times, can someone tell me what the problem is? So someone kindly pointed out that read serializes things, what should i do to make the processes truly parallel?
Your pipe read in the parent serializes things.
You (1) create the child; (2) child writes to the pipe; (3) parent will block on read until there is something to read. This repeats for all the children.
I am trying to write a program that The parent process will take the arguments to main() and send the characters in them one at a time to the child process through a pipe (one call to write for each character). The child process will count the characters sent to it by the parent process and print out the number of characters it received from the parent. The child process should not use the arguments to main() in any way whatsoever. The child should return normally and not have the parent kill the child.
Am i counting the arguments right? am i sending the arguments in one at a time, and am i reaping the child?
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define size = 100;
int main(int argc, char *argv[])
{
int i, count =0;
int c;
int fdest[2]; // for pipe
pid_t pid; //process IDs
char buffer[BUFSIZ];
if (pipe(fdest) < 0) /* attempt to create pipe */
perror( "pipe" );
if ((pid = fork()) < 0) /* attempt to create child / parent process */
{
perror( "fork" );
}
/* parent process */
else if (pid > 0) {
close(fdest[0]);
for (i=1; i < argc; ++i)
{
for (c=0; c < strlen(argv[i]); ++c) {
write(fdest[1], &argv[i][c], 1);
}
}
close(fdest[1]);
wait(NULL);
exit(0);
} else {
/* child Process */
close(fdest[1]);
while (read(fdest[0], &buffer, 1) > 0)
{
count++;
}
printf("\nchild: counted %d characters\n", count);
}
wait(NULL);
exit(0);
}
The second wait() is superfluous; the child has no children of its own to wait for. The second 'exit(0);' could be replaced by 'return(0);'. You could omit the previous 'exit(0);' too.
The '#define size = 100;' is unused, which is just as well since the '=' makes it unusable for most purposes (and the semi-colon is a bad idea too - seldom does a macro end with a semi-colon). It should be '#define size 100' or 'enum { size = 100 };'. Often, people use upper case names for 'manifest constants', hence 'enum { SIZE = 100 };.
If you are reading one character at a time, you really don't need a buffer of size BUFSIZ (which is usually 512 or larger).
Also, it is a bad idea to do 'for (c = 0; c < strlen(argv[c]); c++)' because that calculates the length of the string on each iteration. Replace it with either of these:
for (const char *str = argv[i]; *str != '\0'; str++)
write(fdest, str, 1);
for (c = 0, len = strlen(argv[i]); c < len; c++)
write(fdest[1], &argv[i][c], 1);
You close the unused ends of the pipes - that is a crucial step to making things work correctly.
The code seems to be counting correctly. It works off the shelf when I test it. Why are you suspicious that it does not work?