feat: add hw5 doc
This commit is contained in:
parent
d167d81dfc
commit
8d089808b6
593
hw5-doc/README.md
Normal file
593
hw5-doc/README.md
Normal file
|
@ -0,0 +1,593 @@
|
|||
# Homework 5 - CSE 320 - Spring 2022
|
||||
#### Professor Eugene Stark
|
||||
|
||||
### **Due Date: Friday 5/6/2022 @ 11:59pm**
|
||||
|
||||
## Introduction
|
||||
|
||||
The goal of this assignment is to become familiar with low-level POSIX
|
||||
threads, multi-threading safety, concurrency guarantees, and networking.
|
||||
The overall objective is to implement a server that simulates the behavior
|
||||
of a Private Branch Exchange (PBX) telephone system.
|
||||
As you will probably find this somewhat difficult, to grease the way
|
||||
I have provided you with a design for the server, as well as binary object
|
||||
files for almost all the modules. This means that you can build a
|
||||
functioning server without initially facing too much complexity.
|
||||
In each step of the assignment, you will replace one of my
|
||||
binary modules with one built from your own source code. If you succeed
|
||||
in replacing all of my modules, you will have completed your own
|
||||
version of the server.
|
||||
|
||||
For this assignment, there are four modules to work on:
|
||||
|
||||
* Server initialization (`main.c`)
|
||||
* Server module (`server.c`)
|
||||
* PBX module (`pbx.c`)
|
||||
* TU (telephone unit) module (`tu.c`)
|
||||
|
||||
It is probably best if you work on the modules in the order listed.
|
||||
You should turn in whatever modules you have worked on.
|
||||
Though the exact details are not set at this time, I expect that your
|
||||
code will be compiled and tested in the following configurations:
|
||||
|
||||
1. Blackbox tests using your `main.c` and your `server.c` (if implemented,
|
||||
otherwise my `server.o`), and my `pbx.o` and `tu.o`.
|
||||
2. Blackbox tests using the modules you implemented, with mine for the rest.
|
||||
3. Unit tests on your `pbx.c` in isolation.
|
||||
4. Unit tests on your `tu.c` in isolation.
|
||||
|
||||
For each configuration, you will receive points for whatever test cases in
|
||||
that configuration are passed. If you are able to achieve some reasonable
|
||||
functionality in a module, it will probably be beneficial to submit it.
|
||||
It is probably not a good strategy to submit modules that are completely
|
||||
broken.
|
||||
It is definitely not a good strategy to submit code that does not compile,
|
||||
as this might prevent you from getting any points at all!
|
||||
|
||||
### Takeaways
|
||||
|
||||
After completing this homework, you should:
|
||||
|
||||
* Have a basic understanding of socket programming
|
||||
* Understand thread execution, mutexes, and semaphores
|
||||
* Have an understanding of POSIX threads
|
||||
* Have some insight into the design of concurrent data structures
|
||||
* Have enhanced your C programming abilities
|
||||
|
||||
## Hints and Tips
|
||||
|
||||
* We strongly recommend you check the return codes of all system
|
||||
calls. This will help you catch errors.
|
||||
|
||||
* **BEAT UP YOUR OWN CODE!** Exercise your modules with rapid and
|
||||
concurrent calls to ensure that they do not crash or deadlock.
|
||||
Ideally, besides basic sequential tests, you would write multi-threaded
|
||||
test drivers to achieve this. We will use tests of this nature in grading.
|
||||
|
||||
* Your code should **NEVER** crash. We will be deducting points for
|
||||
each time your program crashes during grading. Make sure your code
|
||||
handles invalid usage gracefully.
|
||||
|
||||
* You should make use of the macros in `debug.h`. In non-debugging,
|
||||
"production" use, your code should basically be silent, except perhaps
|
||||
to emit a one-line error message just before terminating in case a fatal
|
||||
error situation requires an abort.
|
||||
**FOLLOW THIS GUIDELINE!** `make debug` is your friend.
|
||||
|
||||
> :scream: **DO NOT** modify any of the header files provided to you in the base code.
|
||||
> These have to remain unmodified so that the modules can interoperate correctly.
|
||||
> We will replace these header files with the original versions during grading.
|
||||
> You are of course welcome to create your own header files that contain anything
|
||||
> you wish.
|
||||
|
||||
## Helpful Resources
|
||||
|
||||
### Textbook Readings
|
||||
|
||||
You should make sure that you understand the material covered in
|
||||
chapters **11.4** and **12** of **Computer Systems: A Programmer's
|
||||
Perspective 3rd Edition** before starting this assignment. These
|
||||
chapters cover networking and concurrency in great detail and will be
|
||||
an invaluable resource for this assignment.
|
||||
|
||||
### pthread Man Pages
|
||||
|
||||
The pthread man pages can be easily accessed through your terminal.
|
||||
However, [this opengroup.org site](http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html)
|
||||
provides a list of all the available functions. The same list is also
|
||||
available for [semaphores](http://pubs.opengroup.org/onlinepubs/7908799/xsh/semaphore.h.html).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Fetch and merge the base code for `hw5` as described in `hw0`. You can
|
||||
find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw5.
|
||||
Remember to use the `--stategy-option=theirs` flag for the `git merge`
|
||||
command to avoid merge conflicts in the Gitlab CI file.
|
||||
|
||||
### The Base Code
|
||||
|
||||
Here is the structure of the base code:
|
||||
|
||||
```
|
||||
.
|
||||
├── demo
|
||||
│ └── pbx
|
||||
├── hw5.sublime-project
|
||||
├── include
|
||||
│ ├── debug.h
|
||||
│ ├── pbx.h
|
||||
│ ├── server.h
|
||||
│ └── tu.h
|
||||
├── lib
|
||||
│ └── pbx.a
|
||||
├── Makefile
|
||||
├── src
|
||||
│ ├── globals.c
|
||||
│ ├── main.c
|
||||
│ ├── pbx.c
|
||||
│ ├── server.c
|
||||
│ └── tu.c
|
||||
└── tests
|
||||
├── basecode_tests.c
|
||||
├── script_tester.c
|
||||
└── __test_includes.h
|
||||
|
||||
```
|
||||
|
||||
The base code consists of header files that define module interfaces,
|
||||
a library `pbx.a` containing binary object code for my
|
||||
implementations of the modules, a source code file `globals.c` that
|
||||
contains definitions of some global variables, and a source code file `main.c`
|
||||
that contains a stub for function `main()`. The `Makefile` is
|
||||
designed to compile any existing source code files and then link them
|
||||
against the provided library. The result is that any modules for
|
||||
which you provide source code will be included in the final
|
||||
executable, but modules for which no source code is provided will be
|
||||
pulled in from the library. The `pbx.a` library was compiled
|
||||
without `-DDEBUG`, so it does not produce any debugging printout.
|
||||
Also provided is a demonstration binary `demo/pbx`. This executable
|
||||
is a complete implementation of the PBX server, which is intended to help
|
||||
you understand the specifications from a behavioral point of view.
|
||||
It also does not produce any debugging printout, however.
|
||||
|
||||
Most of the detailed specifications for the various modules and functions
|
||||
that you are to implement are provided in comments that precede stubs for
|
||||
these functions in the various source files. In the interests of brevity and
|
||||
avoiding redundancy, those specifications are not reproduced in this document.
|
||||
Nevertheless, the information they contain is very important, and constitutes
|
||||
the authoritative specification of what you are to implement.
|
||||
|
||||
> :scream: The various functions and variables defined in the header files
|
||||
> constitute the **entirety** of the interfaces between the modules in this program.
|
||||
> Use these functions and variables as described and **do not** introduce any
|
||||
> additional functions or global variables as "back door" communication paths
|
||||
> between the modules. If you do, the modules you implement will not interoperate
|
||||
> properly with my implementations, and it will also likely negatively impact
|
||||
> our ability to test your code.
|
||||
|
||||
The function stubs that appear in the various source files have been
|
||||
commented out in the basecode. This is important, because when the linker
|
||||
does not see any definitions for these functions, it will link in binaries
|
||||
from the `pbx.a` library. If you choose to implement a function in one of the
|
||||
modules, you must uncomment the stubs for all the other functions in that module,
|
||||
otherwise the linker will still link the library version of that module,
|
||||
which will result in "multiply defined" errors at link time.
|
||||
|
||||
## The PBX Server: Overview
|
||||
|
||||
"PBX" is a simple implementation of a server that simulates a PBX
|
||||
telephone system. A PBX is a private telephone exchange that is used
|
||||
within a business or other organization to allow calls to be placed
|
||||
between telephone units (TUs) attached to the system, without having to
|
||||
route those calls over the public telephone network. We will use the
|
||||
familiar term "extension" to refer to one of the TUs attached to a PBX.
|
||||
|
||||
The PBX system provides the following basic capabilities:
|
||||
|
||||
* *Register* a TU as an extension in the system.
|
||||
* *Unregister* a previously registered extension.
|
||||
|
||||
Once a TU has been registered, the following operations are available
|
||||
to perform on it:
|
||||
|
||||
* *Pick up* the handset of a registered TU. If the TU was ringing,
|
||||
then a connection is established with a calling TU. If the TU was not
|
||||
ringing, then the user hears a dial tone over the receiver.
|
||||
* *Hang up* the handset of a registered TU. Any call in progress is
|
||||
disconnected.
|
||||
* *Dial* an extension on a registered TU. If the dialed extension is
|
||||
currently "on hook" (*i.e.* the telephone handset is on the switchhook),
|
||||
then the dialed extension starts to ring, indicating the presence of an
|
||||
incoming call, and a "ring back" notification is played over the receiver
|
||||
of the calling extension. Otherwise, if the dialed extension is "off hook",
|
||||
then a "busy signal" notification is played over the receiver of the
|
||||
calling extension.
|
||||
* *Chat* over the connection made when one TU has dialed an extension
|
||||
and the called extension has picked up.
|
||||
|
||||
The basic idea of these operations should be simple and familiar,
|
||||
since I am sure that everyone has used a telephone system :wink:.
|
||||
However, we will need a rather more detailed and complete specification
|
||||
than just this simple overview.
|
||||
|
||||
## The PBX Server: Details
|
||||
|
||||
Our PBX system will be implemented as a multi-threaded network server.
|
||||
When the server is started, a **master server** thread sets up a socket on
|
||||
which to listen for connections from clients (*i.e.* the TUs). When a network
|
||||
connection is accepted, a **client service thread** is started to handle requests
|
||||
sent by the client over that connection. The client service thread registers
|
||||
the client with the PBX system and is assigned an extension number.
|
||||
The client service thread then executes a service loop in which it repeatedly
|
||||
receives a **message** sent by the client, performs some operation determined
|
||||
by the message, and sends one or more messages in response.
|
||||
The server will also send messages to a client asynchronously as a result of actions
|
||||
performed on behalf of other clients.
|
||||
For example, if one client sends a "dial" message to dial another extension,
|
||||
then if that extension is currently on-hook it will receive an asynchronous
|
||||
"ring" message, indicating that the ringer is to be activated.
|
||||
|
||||
Messages from a client to a server represent commands to be performed.
|
||||
Except for messages containing "chat" sent from a connected TU, every message
|
||||
from the server to a client will consist of a notification of the current state
|
||||
of that client, as it is currently understood by the server.
|
||||
Usually these messages will inform the client of a state change that has occurred
|
||||
as a result of a command the client sent, or of an asynchronous state change
|
||||
that has occurred as a result of a command sent by some other client.
|
||||
If a command sent by a client does not result in any state change, then the
|
||||
response sent by the server will simply report the unchanged state.
|
||||
|
||||
> :nerd: One of the basic tenets of network programming is that a
|
||||
> network connection can be broken at any time and the parties using
|
||||
> such a connection must be able to handle this situation. In the
|
||||
> present context, the client's connection to the PBX server may
|
||||
> be broken at any time, either as a result of explicit action by the
|
||||
> client or for other reasons. When disconnection of the client is
|
||||
> noticed by the client service thread, the server acts as though the client
|
||||
> had sent an explicit **hangup** command, the client is then unregistered from
|
||||
> the PBX, and the client service thread terminates.
|
||||
|
||||
The PBX system maintains the set of registered clients in the form of a mapping
|
||||
from assigned extension numbers to clients.
|
||||
It also maintains, for each registered client, information about the current
|
||||
state of the TU for that client. The following are the possible states of a TU:
|
||||
|
||||
* **On hook**: The TU handset is on the switchhook and the TU is idle.
|
||||
* **Ringing**: The TU handset is on the switchhook and the TU ringer is
|
||||
active, indicating the presence of an incoming call.
|
||||
* **Dial tone**: The TU handset is off the switchhook and a dial tone is
|
||||
being played over the TU receiver.
|
||||
* **Ring back**: The TU handset is off the switchhook and a "ring back"
|
||||
signal is being played over the TU receiver.
|
||||
* **Busy signal**: The TU handset is off the switchhook and a "busy"
|
||||
signal is being played over the TU receiver.
|
||||
* **Connected**: The TU handset is off the switchhook and a connection has been
|
||||
established between this TU and the TU at another extension. In this state
|
||||
it is possible for users at the two TUs to "chat" with each other over the
|
||||
connection.
|
||||
* **Error**: The TU handset is off the switchhook and an "error" signal
|
||||
is being played over the TU receiver.
|
||||
|
||||
Transitions between TU states occur in response to messages received from the
|
||||
associated client, and sometimes also asynchronously in conjunction with
|
||||
state transitions of other TUs.
|
||||
The list below specifies all the possible transitions between states that a TU
|
||||
can perform. The arrival of any message other than those explicitly listed for
|
||||
each particular state does not cause any transition between states to take place.
|
||||
|
||||
* When in the **on hook** state:
|
||||
|
||||
* A **pickup** message from the client will cause a transition to the **dial tone** state.
|
||||
* An asynchronous transition to the **ringing** state is also possible from this state.
|
||||
|
||||
* When in the **ringing** state:
|
||||
|
||||
* A **pickup** message from the client will cause a transition to the **connected** state.
|
||||
Simultaneously, the calling TU will also make an asynchronous transition from the
|
||||
**ring back** state to the **connected** state. The PBX will establish a connection
|
||||
between these two TUs, which we will refer to as *peers* as long as the connection
|
||||
remains established.
|
||||
* An asynchronous transition to the **on hook** state is also possible from this state.
|
||||
This would occur if the calling TU hangs up before the call is answered.
|
||||
|
||||
* When in the **dial tone** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
* A **dial** message from the client will cause a transition to either the **error**,
|
||||
**busy signal**, or **ring back** states, depending firstly on whether or not a TU
|
||||
is currently registered at the dialed extension, and secondly, whether the TU at
|
||||
the dialed extension is currently in the **on hook** state or in some other state.
|
||||
If there is no TU registered at the dialed extension, the transition will be to the
|
||||
**error** state.
|
||||
If there is a TU registered at the dialed extension, then if that extension is
|
||||
currently not in the **on hook** state, then the transition will be to the
|
||||
**busy signal** state, otherwise the transition will be to the **ring back** state.
|
||||
In the latter case, the TU at the dialed extension makes an asynchronous transition
|
||||
to the **ringing** state, simultaneously with the transition of the calling TU to the
|
||||
**ring back** state.
|
||||
|
||||
* When in the **ring back** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
Simultaneously, the called TU will make an asynchronous transition from the **ringing**
|
||||
state to the **on hook** state.
|
||||
* An asynchronous transition to the **connected** state (if the called TU picks up)
|
||||
or to the **dial tone** state (if the called TU unregisters) is also possible from this state.
|
||||
|
||||
* When in the **busy signal** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
|
||||
* When in the **connected** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
Simultaneously, the peer TU will make a transition from the **connected** state to
|
||||
the **dial tone** state.
|
||||
* An asynchronous transition to the **dial tone** state is also possible from this state.
|
||||
This would occur if the peer TU were to hang up.
|
||||
|
||||
* When in the **error** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
|
||||
Messages are sent between the client and server in a text-based format,
|
||||
in which each message consists of a single line of text, the end of which is
|
||||
indicated by the two-byte line termination sequence `"\r\n"`.
|
||||
There is no *a priori* limitation on the length of the line of text that is sent
|
||||
in a message.
|
||||
The possible messages that can be sent are listed below.
|
||||
The initial keywords in each message are case-sensitive.
|
||||
|
||||
* Commands from Client to Server
|
||||
* **pickup**
|
||||
* **hangup**
|
||||
* **dial** #, where # is the number of the extension to be dialed.
|
||||
* **chat** ...arbitrary text...
|
||||
|
||||
* Responses from Server to Client
|
||||
* **ON HOOK** #, where # reports the extension number of the client.
|
||||
* **RINGING**
|
||||
* **DIAL TONE**
|
||||
* **RING BACK**
|
||||
* **CONNECTED** #, where # is the number of the extension to which the
|
||||
connection exists.
|
||||
* **ERROR**
|
||||
* **CHAT** ...arbitrary text...
|
||||
|
||||
### Demonstration Server
|
||||
|
||||
To help you understand what the PBX server is supposed to do, I have provided
|
||||
a complete implementation for demonstration purposes.
|
||||
Run it by typing the following command:
|
||||
|
||||
```
|
||||
$ demo/pbx -p 3333
|
||||
```
|
||||
|
||||
You may replace `3333` by any port number 1024 or above (port numbers below 1024
|
||||
are generally reserved for use as "well-known" ports for particular services,
|
||||
and require "root" privilege to be used).
|
||||
The server should report that it has been initialized and is listening
|
||||
on the specified port.
|
||||
From another terminal window, use `telnet` to connect to the server as follows:
|
||||
|
||||
```
|
||||
$ telnet localhost 3333
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
ON HOOK 4
|
||||
```
|
||||
|
||||
You can now issue commands to the server:
|
||||
|
||||
```
|
||||
pickup
|
||||
DIAL TONE
|
||||
dial 5
|
||||
ERROR
|
||||
hangup
|
||||
ON HOOK 4
|
||||
```
|
||||
|
||||
If you make a second connection to the server from yet another terminal window,
|
||||
you can make calls.
|
||||
|
||||
Note that the server will silently ignore syntactically incorrect commands,
|
||||
such as "dial" without an extension number. If commands are issued when the
|
||||
TU at the server side is in an inappropriate state (for example, if "dial 5"
|
||||
is sent when the TU is "on hook"), then a response will be sent by the server
|
||||
that just repeats the state of the TU, which does not change.
|
||||
|
||||
## Task I: Server Initialization
|
||||
|
||||
When the base code is compiled and run, it will print out a message
|
||||
saying that the server will not function until `main()` is
|
||||
implemented. This is your first task. The `main()` function will
|
||||
need to do the following things:
|
||||
|
||||
- Obtain the port number to be used by the server from the command-line
|
||||
arguments. The port number is to be supplied by the required option
|
||||
`-p <port>`.
|
||||
|
||||
- Install a `SIGHUP` handler so that clean termination of the server can
|
||||
be achieved by sending it a `SIGHUP`. Note that you need to use
|
||||
`sigaction()` rather than `signal()`, as the behavior of the latter is
|
||||
not well-defined in a multithreaded context.
|
||||
|
||||
- Set up the server socket and enter a loop to accept connections
|
||||
on this socket. For each connection, a thread should be started to
|
||||
run function `pbx_client_service()`.
|
||||
|
||||
These things should be relatively straightforward to accomplish, given the
|
||||
information presented in class and in the textbook. If you do them properly,
|
||||
the server should function and accept connections on the specified port,
|
||||
and you should be able to connect to the server using the test client.
|
||||
|
||||
## Task II: Server Module
|
||||
|
||||
In this part of the assignment, you are to implement the server module,
|
||||
which provides the function `pbx_client_service()` that is invoked
|
||||
when a client connects to the server. You should implement this function
|
||||
in the `src/server.c` file.
|
||||
|
||||
The `pbx_client_service` function is invoked as the **thread function**
|
||||
for a thread that is created (using ``pthread_create()``) to service a
|
||||
client connection.
|
||||
The argument is a pointer to the integer file descriptor to be used
|
||||
to communicate with the client. Once this file descriptor has been
|
||||
retrieved, the storage it occupied needs to be freed.
|
||||
The thread must then become detached, so that it does not have to be
|
||||
explicitly reaped, it must initialize a new TU with that file descriptor,
|
||||
and it must register the TU with the PBX module under a particular
|
||||
extension number. The demo program uses the file descriptor as the
|
||||
extension number, but you may choose a different scheme if you wish.
|
||||
Finally, the thread should enter a service loop in which it repeatedly
|
||||
receives a message sent by the client, parses the message, and carries
|
||||
out the specified command.
|
||||
The actual work involved in carrying out the command is performed by calling
|
||||
the functions provided by the PBX module.
|
||||
These functions will also send the required response back to the client
|
||||
(each syntactically correct command will elicit a single response that
|
||||
contains the resulting state of the TU)
|
||||
so the server module need not be directly concerned with that.
|
||||
|
||||
## Task III: PBX Module
|
||||
|
||||
The PBX module is the central module in the implementation of the server.
|
||||
It provides the functions listed below, for which more detailed specifications
|
||||
are given in the source file `pbx.c`.
|
||||
|
||||
* `PBX *pbx_init()`: Initialize a new PBX.
|
||||
* `void pbx_shutdown(PBX *pbx)`: Shut down a PBX.
|
||||
* `int pbx_register(PBX *pbx, TU *tu, int ext)`: Register a TU with a PBX.
|
||||
* `int pbx_unregister(PBX *pbx, TU *tu)`: Unregister a TU from a PBX.
|
||||
* `int pbx_dial(PBX *pbx, TU *tu, int ext)`: Dial an extension.
|
||||
|
||||
The PBX module will need to maintain a registry of connected clients and
|
||||
manage the TU objects associated with these clients.
|
||||
The PBX will need to be able to map each extension number to the associated TU object.
|
||||
As the PBX object will be accessed concurrently by multiple threads,
|
||||
it will need to provide appropriate synchronization (for example, using mutexes and/or
|
||||
semaphores) to ensure correct and reliable operation.
|
||||
Finally, the `pbx_shutdown()` function is required to shut down the network connections
|
||||
to all registered clients (the `shutdown(2)` function can be used to shut down a socket
|
||||
for reading, writing, or both, without closing the associated file descriptor)
|
||||
and it is then required to wait for all the client service threads to unregister
|
||||
the associated TUs before returning. Consider using a semaphore, possibly in conjunction
|
||||
with additional bookkeeping variables, for this purpose.
|
||||
|
||||
## Task IV: TU Module
|
||||
|
||||
The TU module implements objects that simulate a telephone unit.
|
||||
The functions provided by this module are shown below. More detailed specifications
|
||||
can be found in the source file `tu.c`.
|
||||
|
||||
* `TU *tu_init(int fd)`: Initialize a new TU with the file descriptor for a client.
|
||||
* `void tu_ref(TU *tu, char *reason)`: Increase the reference count (see below) of a TU by one.
|
||||
* `void tu_unref(TU *tu, char *reason)`: Decrease the reference count of a TU by one,
|
||||
freeing the TU and associated resources if the reference count reaches zero.
|
||||
* `int tu_fileno(TU *tu)`: Get the file descriptor for the network connection underlying a TU.
|
||||
* `int tu_extension(TU *tu)`: Get the extension number for a TU.
|
||||
* `int tu_set_extension(TU *tu, int ext)`: Set the extension number for a TU.
|
||||
* `int tu_pickup(TU *tu)`: Take a TU receiver off-hook (i.e. pick up the handset).
|
||||
* `int tu_hangup(TU *tu)`: Hang up a TU (i.e. replace the handset on the switchhook).
|
||||
* `int tu_dial(TU *tu, int ext)`: Use a TU to originate a call to a specified extension.
|
||||
* `int tu_chat(TU *tu, char *msg)`: "Chat" during a call to another TU.
|
||||
|
||||
Each TU object will contain the file descriptor of an underlying network connection
|
||||
to a client, as well as a representation of the current state of the TU.
|
||||
It will need to use the file descriptor to issue responses to the client,
|
||||
as well as any required asynchronous notifications, whenever any of the `tu_xxx`
|
||||
functions is called.
|
||||
Since the TU objects will be accessed concurrently by multiple threads,
|
||||
the TU module will need to provide appropriate synchronization
|
||||
Changes to the state of a TU will require exclusive access to the TU.
|
||||
Some operations require simultaneous changes of state of two TUs; these will require
|
||||
exclusive access to both TUs at the same time in order to ensure the simultaneity of
|
||||
the state changes. Care must be taken to avoid deadlock when obtaining exclusive
|
||||
access to two TUs at the same time.
|
||||
Sending responses and notifications to the network client managed by a TU will also
|
||||
require exclusive access to the TU, in order to ensure that messages sent by separate
|
||||
threads are serialized over the network connection, rather than possibly intermingled.
|
||||
|
||||
In order for TU objects to exist independently of a PBX object, yet still avoid the possibility
|
||||
of having "dangling pointers" to TU objects that have unexpectedly been freed, the TU module
|
||||
uses a *reference counting* scheme. The basic idea of reference counts is that they are
|
||||
an integer field stored in an object that keeps track of the number of references
|
||||
(*i.e.* pointers) that exist to that object. When a pointer to a TU object is copied,
|
||||
the reference count on that object must be increased in order to account for the additional
|
||||
pointer that now exists. When a pointer to a TU object is discarded, the reference count on
|
||||
that object must be decreased in order to account for the pointer that no longer exists.
|
||||
When the reference count on a TU object has been decremented to zero, that means there are
|
||||
no longer any pointers by which that object can be accessed and therefore the object can
|
||||
safely be freed. Incrementing and decrementing the reference count of a TU is performed
|
||||
by calling the `tu_ref()` and `tu_unref()` functions. These functions take as an argument
|
||||
the TU to be manipulated, but in addition they take a second argument `msg`.
|
||||
This is for debugging purposes: when you increment or decrement a TU you should supply
|
||||
as the `msg` argument a string giving the reason why the reference count is being changed.
|
||||
If, when you implement the TU module, you have the `tu_ref()` and `tu_unref()` functions
|
||||
announce when they are called, along with the reason why, it will make it easier to
|
||||
debug reference count issues.
|
||||
|
||||
## Test Exerciser
|
||||
|
||||
The `tests` directory in the basecode contains a test driver with a few selected tests
|
||||
to get you started. These tests are coded using Criterion as usual, but note that it
|
||||
is important that when you run the tests you supply the `-j1` argument to `pbx_tests`.
|
||||
This is because each test starts its own server instance and if you run them all
|
||||
concurrently only one server instance will be able to bind the port number that is
|
||||
being used and the other server instances will fail.
|
||||
|
||||
The file `basecode_tests.c` contains the Criterion portion of the tests and the file
|
||||
`script_tester.c` contains the test driver that they use. The file `__test_includes.h`
|
||||
contains some macros and declarations that are shared between the two files.
|
||||
The overall idea of the tests are that they are table-driven. Each test has a "script",
|
||||
which defines an array of `TEST_STEP` structures. The `TEST_STEP` structure is
|
||||
defined in `__test_includes.h`. Each step contains an `id`, which identifies a particular
|
||||
TU that should execute the step, a `command` which is the command to be sent to the TU,
|
||||
an `id_to_dial` field which specifies the extension to be dialed in case the `command`
|
||||
is TU_DIAL, a `response` field, which specifies the TU state that it is expected will
|
||||
be sent in the response from the server, and a `timeout` field, which can be used to
|
||||
place a limit on how long the tester will wait for a response from the server.
|
||||
The `timeout` values themselves have type `struct timeval` (see `man 2 setitimer` for
|
||||
a definition). There are several predefined timeout values of various lengths defined
|
||||
in `__test_includes.h`. The idea is that if the expected response from the server does
|
||||
not arrive before the timeout expires, then the test script fails. A timeout value of
|
||||
`ZERO_SEC` means the test driver will wait indefinitely for the response from the server.
|
||||
|
||||
To use the full capabilities of the test driver is probably somewhat complicated,
|
||||
since if you get multiple TUs sending commands in a concurrent fashion you have to
|
||||
start worrying about asynchronous notifications from the server "crossing in front of"
|
||||
the expected response to a command. But you can probably follow the basic pattern in
|
||||
the scripts that I have provided to create other similar scripts that test the ability of
|
||||
your server process other series of commands issued in rapid succession.
|
||||
|
||||
## Debugging Multi-threaded Code with `gdb`
|
||||
|
||||
The `gdb` debugger has features for debugging multi-threaded code.
|
||||
In particular, it is aware of the presence of multiple threads and it provides
|
||||
commands for you to switch the focus of debugging from one thread to another.
|
||||
Use the `info threads` command to get a list of the existing threads.
|
||||
Use the command `thread nnn` (replace `nnn` by the thread number) to switch
|
||||
the focus of debugging to that thread. Once you have done, this, the `gdb`
|
||||
commands such as `bt` that examine the stack will be executed with respect
|
||||
to the selected thread. Threads can be stopped and started independently
|
||||
using `gdb`, as well.
|
||||
|
||||
## Submission Instructions
|
||||
|
||||
Make sure your hw5 directory looks similarly to the way it did
|
||||
initially and that your homework compiles (be sure to try compiling
|
||||
both with and without "debug").
|
||||
Note that you should omit any source files for modules that you did not
|
||||
complete, and that you might have some source and header files in addition
|
||||
to those shown. You are also, of course, encouraged to create Criterion
|
||||
tests for your code.
|
||||
|
||||
It would definitely be a good idea to use `valgrind` to check your program
|
||||
for memory and file descriptor leaks. Keeping track of allocated objects
|
||||
and making sure to free them is potentially one of the more challenging aspects
|
||||
of this assignment.
|
||||
|
||||
To submit, run `git submit hw5`.
|
Loading…
Reference in New Issue
Block a user