Lab 2
The Process Abstraction and Creating Processes
CSCE 313 · Introduction to Computer Systems
- Released
- Tuesday, September 8, 2026, 9:00 AM CT
- Due
- Monday, September 21, 2026, 11:59 PM CT
1. Objectives
By the end of this lab you will be able to:
- Create a child process with
fork()and tell parent from child by its return value. - Replace a child’s process image with
execvp(), passing arguments the way the shell would. - Explain what a child inherits from its parent, and what it does not.
- Observe process isolation directly, by printing the same variables from both processes.
- Drive a client-server exchange: build a request, send it, and read the response.
- Shut a system down cleanly, so that no process is left running and no parent waits forever.
Every lab this term builds on one project: a financial management system. This is where it starts. You write the client. The three servers are written for you.
2. Background
2.1 The client-server model
A client handles the user. A server owns a resource and answers requests about it. The client never touches the resource itself; it asks. That buys two things: the resource lives in one place, and each server can be understood, changed, and crashed on its own.
This lab has one client and three servers, each owning a different resource.
Notice what each server owns. The logging server’s file survives the run; the finance server’s accounts do not, because they live in that process’s memory and die with it. That difference is the whole reason the log file exists.
2.2 Requests and responses
Both types are in common.h. A request carries everything a server might need;
most fields go unused for any given request type.
struct Request {
RequestType type; // QUIT, DEPOSIT, WITHDRAW, BALANCE,
// UPLOAD_FILE, DOWNLOAD_FILE, LOGIN, LOGOUT
int user_id;
double amount;
std::string filename;
std::string data;
Request(RequestType t, int uid = 0, double amt = 0.0,
std::string fname = "", std::string d = "");
};
Only type is required; the rest default. So a quit is just Request(QUIT), and a
balance enquiry is Request(BALANCE, user_id).
A response comes back with four fields, and again only some are filled:
struct Response {
bool success; // did the server do what was asked?
double balance; // finance server only
std::string data; // file contents, on a download
std::string message; // short explanation, useful when success is false
};
2.3 fork() and execvp()
fork() is called once and returns twice, because after it there are two processes.
The return value is the only thing that differs, and it is how each process learns
which one it is:
pid_t pid = fork();
if (pid < 0) { /* fork failed */ }
if (pid == 0) { /* the child */ }
if (pid > 0) { /* the parent */ }
The child is a copy: same code, same variables, same next line to execute. What it is not is the same memory. Change a variable in the child and the parent’s copy does not move. Task 2 has you prove that to yourself.
execvp() then throws that copy away and loads a different program into the process:
char* args[] = {(char*)"./program", (char*)"arg1", (char*)"arg2", nullptr};
execvp(args[0], args);
perror("execvp"); // only reached if execvp FAILED
exit(1);
Two things to hold on to. The array must end with nullptr, and args[0] is by
convention the program’s own name. And a successful execvp never returns —
there is no longer any code to return to. So any line after it runs only on failure,
which is exactly why the two lines above are there.
2.4 The RequestChannel
channel.h gives you the whole interface you need:
RequestChannel(const std::string process_name, const Side side);
Response send_request(const Request& req);
A channel is identified by its name. The client and the server must construct it
with the same string, or they are not talking to each other. The names the servers
use are in their source: "finance", "file", "logging". Note the file server’s
channel is called "file" even though its executable is ./fileserver.
Each name must be opened exactly once from each side — one CLIENT_SIDE, one
SERVER_SIDE. Opening the same side twice is undefined behaviour.
You only ever call send_request. receive_request and send_response are the
servers’ half of the conversation, and they are already written.
3. Environment setup
3.1 Accept the assignment
- Sign in to classroom50.org with your GitHub account.
- Accept Lab 2: https://classroom50.org/CSCE-313-FA26/csce-313-fa26/assignments/lab-2/accept. Your repository is created for you.
- Clone it:
git clone https://github.com/CSCE-313-FA26/<your-lab-2-repository>.git
cd <your-lab-2-repository>
3.2 Install clang
The RequestChannel implementation ships as LLVM IR, which clang assembles:
sudo apt update
sudo apt install clang
You do not need clang for anything else in this lab, and you never edit the IR.
3.3 Repository layout
| Path | What it is |
|---|---|
client.cpp |
The only file you modify. Every TODO is here. |
finance.cpp |
Finance server. Complete — do not modify. |
file.cpp |
File server. Complete — do not modify. |
logging.cpp |
Logging server. Complete — do not modify. |
channel.h |
The RequestChannel interface you call |
channel.ll, channel.ll.arm |
Its implementation, as LLVM IR, for x86-64 and ARM |
common.h, common.cpp |
Request, Response, RequestType |
Makefile |
Builds all four programs |
lab2-tests.sh |
The same tests the autograder runs. Run them yourself. |
storage/example_execution.txt |
A full session, showing exactly what correct output looks like |
storage/example.log |
The log that session should have produced |
3.4 Build
make
That produces four executables: client, finance, fileserver, logging.
Note the file server’s executable is fileserver, not file — file is already
a standard Unix command. Its channel, however, is named "file". The two are
different things and both spellings are correct in their own place.
3.5 Running the servers by hand
The finished client starts the servers itself. While you are still building it, you can run each one in its own terminal and drive it from a fourth:
./finance -m 1000 # accounts 0 through 1000
./logging -f system.log # log to system.log
./fileserver .txt .h # allow uploads of .txt and .h only
./client
This is a debugging aid, not the deliverable. The autograder fails every test if the client does not start the servers itself, so implement Task 1 first.
4. Walkthrough
You do not have to read the servers line by line, but you do have to know which fields each one reads out of your request. These three diagrams are that summary.
4.1 The finance server
Started as ./finance -m <max_account_num>. Accounts 0 through max_account_num
inclusive are valid; anything else is refused. Accounts are created on first use with
a balance of zero.
>=.resp.balance is set on all three successful operations, so you can print the new
balance straight from the response without asking again.
4.2 The file server
Started as ./fileserver <ext_1> ... <ext_n>. It owns the storage/ directory.
4.3 The logging server
Started as ./logging -f <log_filename>. It appends one line per request, and the
file survives between runs.
Which field the log line uses depends on the request type, and getting this wrong is the most common way to lose points on Task 3:
| Request | Line written | Field it reads |
|---|---|---|
LOGIN |
[7]: logged in |
user_id |
LOGOUT |
[7]: logged out |
user_id |
DEPOSIT |
[7]: deposited 500 |
amount |
WITHDRAW |
[7]: withdrew 200 |
amount |
BALANCE |
[7]: viewed balance: 300 |
amount, not balance |
UPLOAD_FILE |
[7]: uploaded file: channel.h |
filename |
DOWNLOAD_FILE |
[7]: downloaded file: example.log |
filename |
4.4 The session you are aiming at
storage/example_execution.txt is a complete correct run, and storage/example.log
is the log it produced:
[1]: logged in
[1]: deposited 500
[1]: withdrew 200
[1]: viewed balance: 300
[1]: uploaded file: channel.h
[1]: downloaded file: example.log
[1]: logged out
Reproduce that log exactly and Task 3’s logging half is done.
5. To-do
Everything you write goes in client.cpp, at the TODO comments. Do the tasks in
this order: nothing else can be tested until Task 1 works.
5.1 Task 1 — Run the servers as child processes (45 points)
Start finance, logging and fileserver as children of the client, so that
running ./client in one terminal brings the whole system up.
For each of the three, at its TODO:
fork(), and check for failure.- In the child only, run the block already written there — it changes the three
variables and calls
print_process_info— and thenexecvpthe server. - The parent does nothing here and carries on to start the next server.
The command lines are:
./finance -m <max_account>
./logging -f <log_file_name>
./fileserver <extension1> <extension2> ...
The file server’s argument count is not known until run time, so build its argv
on the heap. Every array must end with nullptr.
5.2 Task 2 — Print process details (15 points)
Fill in print_process_info so each line carries a real value:
Parent process before fork:
PID: 48120
PPID: 3310
Global variable address: 0x5b1f0a2c4010 value: 100
Stack variable address: 0x7ffd41b2ec44 value: 200
Heap variable address: 0x5b1f0b3d52c0 value: 300
----------------------------------------
Use getpid() and getppid(). Print the address and then the value for each
of the global, stack and heap variables. Do not change the label strings or the line
order — the tests read these files by line.
This is the part of the lab that shows you the process abstraction directly. Each
child bumps the three variables by a different amount before exec, so afterwards
compare parent_attributes.txt against the three child files:
- Every child’s
PPIDis the parent’sPID. They really are its children. - Every child’s values differ from the parent’s, and from each other’s.
- Every child’s addresses are the same as the parent’s, or nearly so.
That last one is the point worth sitting with. Two processes report the same address holding different values, because the address is virtual: each process has its own mapping from those numbers to physical memory. Nothing is shared, and nothing needed to be copied until it was written to.
5.3 Task 3 — Client-server communication (30 points)
Create the three channels after the servers are running:
RequestChannel finance("finance", RequestChannel::CLIENT_SIDE);
RequestChannel file("file", RequestChannel::CLIENT_SIDE);
RequestChannel logging("logging", RequestChannel::CLIENT_SIDE);
Then fill in each menu action. Save every reply into the resp variable the starter
already tests, and use send_request for all of them.
| Menu action | Send to finance | Send to logging |
|---|---|---|
| Login | — | LOGIN |
| Deposit | DEPOSIT with amount |
DEPOSIT with the same amount |
| Withdraw | WITHDRAW with amount |
WITHDRAW with the same amount |
| View balance | BALANCE |
BALANCE with the returned balance in amount |
| Upload file | UPLOAD_FILE with filename and data → file server |
UPLOAD_FILE with filename |
| Download file | DOWNLOAD_FILE with filename → file server |
DOWNLOAD_FILE with filename |
| Logout | — | LOGOUT |
Every action that succeeds is logged, and only after it succeeded — the audit line
goes inside the if (resp.success) branch that is already written for you.
Read every response. A request whose reply you never read leaves the channel out of step, and the next reply you read will be the wrong one.
5.4 Task 4 — Close the channels (10 points)
Before the client returns, send Request(QUIT) down all three channels.
The client’s last act is while (wait(NULL) > 0);, which returns only once every
child has exited — and a server exits only when it receives QUIT. So a client that
misses even one QUIT does not crash or complain. It hangs, forever, with no output.
If your program stops responding at exit, this is why.
5.5 Test your work
make test
That runs lab2-tests.sh, which is the same set of checks the autograder runs, out
of 100. Run it before you push; it is much faster than waiting for the grader.
If the script reports a hang, run the client by hand and compare against
storage/example_execution.txt.
6. Deliverables
Commit and push your work to your assignment repository:
git add client.cpp
git commit -m "lab 2"
git push
| # | Path | Description |
|---|---|---|
| 1 | client.cpp |
Your completed client: all four tasks |
That is the whole submission. The programs’ output files — *_attributes.txt, your
log file, and anything uploaded into storage/ — are produced when the grader runs
your code, and are deliberately ignored by .gitignore. Do not commit them, and do
not commit the built executables.
The autograder runs on every push, so you can push as often as you like and read the result.
6.1 Mistakes that cost points
| Symptom | Cause |
|---|---|
| Every test fails at once | The servers are not being started by the client. Task 1 first. |
| Client prints nothing and never exits | A QUIT was not sent to one of the three servers |
viewed balance: 0 in the log |
The balance was not copied into the request’s amount field |
| Process info values all equal the parent’s | The if (pid == 0) block does not wrap the given lines |
execvp “No such file or directory” |
The file server is ./fileserver, not ./file |
| Log file empty | The logging server was started, but no requests were sent to it |
| Deposits succeed but nothing is logged | The audit request was placed outside the if (resp.success) branch |
| Works alone, fails in the grader | You renamed channel.ll. Do not — the Makefile picks the right one |
| Uploads and downloads stop doing anything | An earlier upload was over ~1 KB and killed the file server. Restart the client and use a smaller file |
error: expected type from clang |
You are on ARM with clang 18. See §3.4 — ARM needs clang 20 |
7. Getting help
Ask on Discord. Bring the exact command you ran and the exact output, not a description of it. The Canvas inbox is not monitored.
Office hours and TA contact details are in the syllabus.
Revision history
| Date | Change |
|---|---|
| 2026-09-07 | Migrated from the Google Doc to this page. Due date corrected from June 14 (a leftover from a summer offering) to Monday, September 21. GitHub Classroom replaced by classroom50. ./file corrected to ./fileserver throughout. The file server’s request types corrected from “three” to two. Figure numbering corrected: the old handout skipped Figure 4. The Response field list corrected — it carries success, balance, data, message, not user_id/amount/filename. The en dash in ./logging –f corrected to a hyphen. The pid_t p = fork() snippet corrected to use one variable name. Figures 2, 3 and 4 corrected against the server sources: QUIT sends no response, a withdrawal of exactly the balance succeeds, the file server returns bytes rather than moving files, and r.user_id is not r.id. Added the ARM/x86 note now that the Makefile selects the IR automatically, the BALANCE logging warning, and §5.5 on running the tests locally. |