Changelog:
- 1 Feb 2023: adjust recommended shared memory region name from /PID-mmchat to /PID-chat
- 1 Feb 2023: replaced
wait for
withoutbox[0]
to stop being\0
wait for
to match the given code and rest of descriptionoutbox[0]
to become\0
In this lab you’ll write yet another chat application.
It will only work on a single machine, not over a network file system or the Internet, but it will also require significantly fewer system resources than our other chats.
If you are using ssh, this means you need to ssh into the same back-end server twice. To do this, first SSH into portal.cs.virginia.edu and run
uname -n
to find out what server you are on (portal## for some two-digit number ##). Then, have your second SSH go directly into that portal##.cs.virginia.edu.
Do the following. We recommend doing them in order. Each is described in more detail below.
Note the above instructions regarding testing on portal.
I would recommend working in pairs on this lab, but note that both instances of the chat program need to run as the same user. (You can’t send signals to another user’s programs.)
Make a program that prints its own PID and reads another PID from the user (store it in a global variable)
Create a signal handler for the SIGINT, SIGTERM, and SIGUSR1 signals
- SIGTERM should invoke a cleanup function
- SIGINT should invoke a cleanup function and then send SIGTERM to the other user
- SIGUSR1 should display the contents of the inbox (eventually in
shared memory; make a pointer to a placeholder character array for now)
and then set its first character to be
'\0'
- (Although normally one should take care to only use signal-safe functions in your signal handlers, for this lab, we do not care if you follow this rule.)
Access some shared memory
- An
inbox
which this process will use to read values from the other process - The inbox of the other process, which we’ll call the
outbox.
- An
Repeat until the end of file:
- Read a typed line into the outbox (without overflow)
- Wait until the outbox is emptied
After the user stops typing (i.e., EOF), send SIGTERM to the other process
Make a cleanup function which destroys both inbox and outbox
The cleanup function should execute no matter how the program exits: via end-of-input, SIGINT, or SIGTERM.
Upload your solution to the submission site or show a TA your program for checkoff.
1 PID
getpid()
(from unistd.h
) returns the PID of
the current process as a pid_t
, which can be treated like
an int
in most situations. This can be printed (e.g., with
printf
) by one process and input to the other (e.g., with
scanf
)
2 Signals
As shown in class, the key parts of signal handling are defining a handler function:
#include <signal.h>
static void your_handler_function_name_here(int signum) {
if (signum == SIGINT) /* ... */
}
and the code in main
to tell the OS to use it and for
which signals:
struct sigaction sa;
.sa_handler = handler;
sa(&sa.sa_mask);
sigemptyset.sa_flags = SA_RESTART;
sa
(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction/* ... */
To send a signal, use kill(pid_to_send_to, SIGTERM)
or
the like.
3 Shared memory
One of the advantages of virtual memory is the OS can map the same physical page to different virtual pages in different processes. The processes can then directly communicate by loading what the other stored in that shared memory.
Because shared memory needs to be able to span processes, it is a bit
more involved to create and destroy than ordinary memory is. There are
multiple ways to create shared memory; in this assignment, it is
recommended to use shm_open
and mmap
.
shm_open
will give you access to special files that are
kept in memory instead of persistent storage. Then, mmap
takes a file and gives you access to it as a region of memory.
(mmap
also works for files that are stored normally on
disk, though we don’t recommend that for this lab.)
Choose a name for each process’s inbox (similar to a filename; in Linux’s implementation of shared memory, this will become a file in the special directory /dev/shm). To allow finding inboxes using PIDs and avoid interference with other students, I recommend chosing a name like
/*PID*-chat
, which you can generate using code like:char inbox_name[512]; snprintf(inbox_name, sizeof inbox_name, "/%d-chat", pid);
Note that the name must start with a
/
.The shared memory regions we will access are a special kind of file opened with
shm_open
and deleted withshm_unlink
.To allocate and get a handle for a shared memory region, open it using shm_open with the
O_CREAT
flag:int inbox_fd = shm_open(inbox_name, O_CREAT | O_RDWR, 0666); if (inbox_fd < 0) { /* something went wrong */ }
The handle,
inbox_fd
in the code above, is an integer that you will pass to themmap
(memory map
) call later. I usefd
in the name because it is a file descriptor.Initially, created shared memory regions will be empty, but you can allocate space for them with
ftruncate
:ftruncate(inbox_fd, size);
(where
size
is a size in bytes; I recommend 4096).To deallocate, use
shm_unlink
:shm_unlink(inbox_name);
Shared memory regions can persist even after the creating program terminates, so always deallocate what you allocate! Even if your program stops early (e.g., by SIGINT) you still need to deallocate. It does not hurt (though it does set
errno
) to deallocate multiple times or from the wrong process.You will need to include
<unistd.h>
,<sys/mman.h>
, and<fcntl.h>
to use these functions as well as#define _XOPEN_SOURCE 600
(or similar) before including anything. On Linux, you will also need to link with-lrt
.A pointer. You attach a pointer to shared memory using
mmap
and detach it withmunmap
:= mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, inbox_fd, 0); inbox if (inbox == (char*) MAP_FAILED) { /* something went wrong */ /* note that MAP_FAILED is *NOT* the same thing as NULL */ } (inbox_fd); // deallocate file descriptor --- can stil use `inbox` after doing this close// ... (inbox, size); munmap
4 Repeating and waiting
You’ve got two things to do: keyboard ↦ outbox and inbox ↦ screen.
4.1 keyboard ↦ outbox
A loop in main
is the way to go: while(!feof(stdin))
you want to fgets
directly into the outbox. After data is
in the outbox, send SIGUSR1 (using kill
) to the other
process; then, wait for outbox[0]
to become '\0'
. Don’t
just busy-wait though: check, then go to sleep for a bit, then check
again. (A better strategy you could also try might be to wait for
SIGUSR2 using sigprocmask() and sigwait() and have the receiving process
send SIGUSR2 when it is done reading the input.)
// wait for '\0' by checking 100 times a second
// 10,000 μs = 10 ms = 1/100 second
while(outbox[0]) { usleep(10000); }
4.2 inbox ↦ screen
When you handle SIGUSR1
fputs
the inbox so we can see what was sent;fflush(stdout)
to ensure we see it now, not later;- set the first character in the inbox to
'\0'
so the other process knows we’re ready for it to send more