Table of Contents
- redir
- pipe
- getwis
1. How to process the arguments which will be passed to the program
called?
Intuitively, you will define another array to hold the arguments which
will be passed to the program. There is immediately a question arising.
How big will you allocate the new array? Since user can input as many
as s/he wants, it is inappropriate to define an array with size which
you think is big enough, such as 100. Actually, there exists a much
easier way to handle this. The idea is to reuse the original argv[]
passed to the main program. The argv[0] is "redir". The argv[1] is
"stdin|stdout|stderr", argv[2] is "filename", and the argv[3] is
the program name. The rest are the arguments for the program invoked.
Furthermore, the first argument passed to a program should be
the name of itself. So, we should pass argv[3] and the rest to
the program. So, the easiest way to call another program is to
execvp(argv[3], &argv[3]). If you feel confused with this comments,
you'd better read the section 5.10 of the book "The C Programming
Language", 2nd edition.
2. About the fork() system call.
The fork() system call has three kinds of return value. -1 denotes
the call failed. 0 denotes you are in the child process, since
the child process can get its own pid through system call getpid().
A value bigger than 0 denotes you are in the parent process and the
return value is the pid of the child process, since the parent
process can't get the pid of its child. Most of you would not
test the condtion of -1 as you believe the system call will always
succeed. You're right for most of time, but not 100 percent.
When the system has tons of processes simultaneously, then the
fork() might fail. So, you'd better test it.
3. About the string comparison.
You might want to use == directly to compare strings, but that's
the wrong way. The correct way is to use strcmp() or strcasecmp().
The latter one is case-insensitive.
4. You should test every system/library call.
Otherwise, you may run into a trouble when your system call failed
for some reason, such as the inproper user input. For example, try
your program with the following test cases:
- redir stdin /noplace ls -l
- redir stdout A nocmd
The first one will go wrong if you don't test the freopen call.
The file /noplace doesn't exist, so the freopen call will fail.
But if you don't test it, then your program will continue to
run. When the ls program tries to read the stdin, it will
report error, for the there is something wrong with the stdin
owing to the failed freopen.
The second one will go wrong if you don't test the execvp call.
Your program might output nothing and the user will get lost.
If you test the execvp call, then you will find it failed and
report some kind of error to notify the user.
0. The meaning of the comonents of the graph?
- The biggest rectangle "pipe" is the main process.
- The leftmost rectangle "producer" is the first process(created by
main process), i.e., the "who" in the command line.
- The rightmost rectangle "consumer" is the second process(created
by main process), i.e., the "grep arf" in the command line.
- The reader thread is created by the main process.
- The writer thread is created by the main process.
- The left FIFO is the connection between the producer process
and the reader thread within the main process.
- The right FIFO is the connection between the consumer process
and the writer thread with in the main process.
- The shared buffer is simply an array, which should be used in
a "loop manner".
1. g++ -lpthread -lrt pipe.c
Functions pthread_create and pthread_join are from pthread library, and
functions sem_init, sem_wait, and sem_post are from realtime library,
so you should tell gcc compiler to link these two libraries (they're
not the standard libraries gcc automatically link). -l parameter is
used for this purpose. Be carefull that there's no space after the -l.
2. The sequence of two child processes and two threads.
- Fork producer child process
- Redirect its stdout to the "ReadFrom" FIFO (using freopen)
- Prepare the argument list for execvp call (using prepArgv)
- Call execvp
- Create the reader thread (using pthread_create)
- Fork consumer child process
- Redirect its stdin to the "WriteTo" FIFO (using freopen)
- Prepare the argument list for execvp call (using prepArgv)
- Call execvp
- Create the writer thread (using pthread_create)
- Main process wait for the termination of two child threads
(using pthread_join)
3. About prepArgv.
It is used to prepare the argument list for two execvp calls (producer
and consumer). Obviously, the argument list for producer is from the
argv[1] of main and the argument list for consumer is from the argv[2]
of main.
The function strtok is the abbr. of "string token", which is used to
get the tokens of some string. Of course, the string is delimited by
certain "delimiter".
4. About readFromProducer.
It is the body of the first child thread, i.e., reader thread. It reads
chars from the FIFO which is connected to the producer process. The
process is as follows: you read one char from the FIFO, prepare to enter
the "critical section", put the char into the "loop" buffer, and exit from
the "critical section". Repeat this process until reaching the end of FIFO.
5. About writeToConsumer.
It is the body of the second child thread, i.e., writer thread. It reads
chars from the "loop" buffer and writes into the FIFO which is connected
to the consumer process. The process is as follows: prepare to enter
the "critical section", get the char from the "loop" buffer, exit from
the "critical section", and write the char into the FIFO. Repeat this
process until the buffer is "empty" (how to define the emptyness? tricky
thing).
6. loop buffer
Since the size of the shared buffer is fixed while the content the producer
produces is variable, so we have to reuse the buffer in a "looping" manner.
This is called "loop buffer". The variable in points the next position where
you can put a char. The variable out points the next position where you get
a char. The variable count records the number of chars currently in the buffer.
After you put a char into the buffer, you should modify the in by "in = (in
+ 1) % BufSize". After you get a char from the buffer, you should modify the
out in the same way, "out = (out + 1) % BufSize". Modify the count
correspondingly.
7. Synchronizing the thread reader and writer.
I only want to mention that the check of semaphore mutex should be in the
inner level and the check of semaphore full & empty should be in the outer
level.
First of all, the good news is this program is much easier than the
previous one. As usual, I wrote some hints for your reference.
- Where will you start from?
A very good place to get started from is "section 5.2: A Simple Stream
Client" of
Beej's Guide to Network Programming. You may refer to the material
before section 5.2 which is enough for this project, but never after that.
- About "wisdom.h" head file
You need to put the "wisdom.h" under the same directory as the directory
in which your program is. In your code, wrap it around with double quotes
rather than brackets. The reason is that brackets are used when the header
file is a standard header file provided by the system, while double quotes
are used when the header file is created by you.
- IMPORTANT: network programming part
The most important part of this program is the network programming part. As I
have said, the good news is there is already a framework available for your
reference. It is the "client.c" in the tutorial recommended by Prof. Feuer.
Actually, you almost don't need to modify it. You only have to add some
other stuff to it. Here is the basic framework of the "worker thread":
- Call
gethostbyname to get the IP address of the server.
The only difference here is that the parameter is defined in the wisdom.h
header file.
- Call
socket to get a socket.
- Call
connect to connect to the Wisdom Server.
- Call
recv to get the "banner message" from server.
- Call
send to send the number to server. Here, the
number is actually in string format, not an integer or anything else.
- Strange strings start from here, at least for me. Instead of getting
the wisdom message directly from the server, the server first sends
a new line '\n' character back and then send the wisdom message back.
Thus, you have to call
recv twice. First for the new line.
Second for the really wisdom message. I don't know why.
- Finally, print the wisdom message out.
- About the timeout manipulation.
Signal process is very common in UNIX. Signals are also known as "software
interrupts". The most common one is Ctrl+C, which is used to interrupt
the running process. If you run "kill -l" command, you can see a list
of all signals your system supports. Here, you need to use another signal,
SIGALRM (not SIGALARM), to interrup the worker thread when the timer goes
off.
For the timeout process, you have 3 things to do. The first is to implement
the timeout procedure, which will be called when the timer goes off. It is
actually an ordinary function with an integer parameter and void return value.
The second thing is to "register" the timeout procedure you just implemented.
The registration is done with the system call signal(SIGALRM,
procedure) . The last thing is to "trigger" the time out process,
using system call alarm. Here's an example of how to use
SIGALARM.
Implementing Timers Using Signals
- About thread manipulation
You still need to create a thread, using pthread_create. You
also need to call the pthread_join to wait for the thread
finishing. In fact, the thread may be interrupted if it still doesn't
finish its job before the timer goes off.