Table of Contents

  1. redir
  2. pipe
  3. getwis

redir.c

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:

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.


pipe.c

0. The meaning of the comonents of the graph?

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.

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.

getwis.c

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.

  1. Where will you start from?
  2. 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.
  3. About "wisdom.h" head file
  4. 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.
  5. IMPORTANT: network programming part
  6. 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":
    1. 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.
    2. Call socket to get a socket.
    3. Call connect to connect to the Wisdom Server.
    4. Call recv to get the "banner message" from server.
    5. Call send to send the number to server. Here, the number is actually in string format, not an integer or anything else.
    6. 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.
    7. Finally, print the wisdom message out.
  7. About the timeout manipulation.
  8. 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

  9. About thread manipulation
  10. 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.