/Teaching/System Level Programming/Assignments/A4


Pull from upstream before solving this task.


Task: Interprocess Communication (IPC)

This exercise should teach you what interprocess communication is for and how you can realize it.

Main Idea

Everybody of us has used interprocess communication already. Mostly unintentionally at this point in your studies. This is why we wanted to take this specific topic into this semester’s course.

To understand the concept of IPC, some major concepts must be learned and understood beforehand.

  • Virtual Memory
  • Process vs. Thread
  • Shared Resources
  • Locking

Some of those terms are already familiar to you, others not. We will not entirely go into details for this assignment, but it’s always useful looking up information based on those keywords.

Implementation details: Curve Fever (Achtung, die Kurve)


You MUST NOT change ANY existing code, even if it is inside TODOs. Exploits will automatically result in deductions! Do not remove, add or change any usleep or assert statements!

In the upstream repository, you will find a simple implementation of the game “Curve Fever”. However, some important parts have been removed, and it is up to you to make the game playable.

The provided Makefile will produce three independent binaries: server, client, and launcher. As the name suggests, the server will handle incoming connections and player input. One client can host up to four players, and multiple clients can connect to the server at the same time. The launcher is a very simple wrapper around the client, parsing user input (amount of players for this client) and passing it to the client process.

Before you start: Have a proper look at the provided framework, try to understand its structure. Read through the header comments carefully.

Server.c:

  • ”initServer()”
    • initialize the shared memory objects (excluding board).
    • make sure to only set the permissions the process needs!
    • map the shared memory objects to the process’ virtual address space.
    • the permissions need to match the ones you used for the shared memory objects!
  • ”initLocks()”
    • initialize any locks you might need.
  • ”initThreadsServer()”
    • start the connections-thread and the update-thread.
  • ”initRound()”
    • initialize the board shared memory object
    • map it to the process’ virtual address space
  • ”handleInput()”
    • this function handles client input
    • synchronize it using appropriate synchronization primitives
  • ”handleNewConnection()”
    • this function handles an incoming connection request
    • synchronize it using appropriate synchronization primitives
    • Hint: don’t get distracted by the logic that is already implemented, focus on the shared resource
  • ”updateThread()”
    • properly lock shared resources (one or many)
  • ”cleanupRound()”
    • delete the board shared memory object
  • ”cleanupServer()”
    • delete the remaining shared memory objects

Client.c:

  • ”initClient()”
    • initialize the shared memory objects.
    • make sure to only set the permissions the process needs!
    • map the shared memory objects to the process’ virtual address space.
    • the permissions need to match the ones you used for the shared memory objects!
  • ”initThreadsClient()”
    • start the draw-thread.
  • ”startClient()”
    • this function handles command-input and makes it available to the server
    • synchronize the process of sending commands using appropriate synchronization primitives
  • ”connect()”
    • this function transmits information about itself to the server for registration
    • synchronize it using appropriate synchronization primitives
  • ”drawThread()”
    • properly lock shared resources (one or many)
  • ”cleanupClient()”
    • clean up the client’s resources

Launcher.c:

  • the launcher already parses user input for you.
  • start the client process and pass the player count as an argument.
  • wait for the client to finish execution and return its exit code.

util.c:

  • Changes to this file will be ignored, but you can add your own functions for testing here if you want.

util.h:

  • The only change allowed in this file is adding synchronization primitives to the shm_locks struct. All other changes made in this file might make your program not testable. Do not change anything but carefully look at the extern variables, defines, structs and comments.

Bonus: There is a race condition regarding the other threads that are also accessing shared memory on both the client- and server-side. You don’t even have to find it, just apply best practices from previous assignments.

 

DO NOT remove or relocate code for checking your approach. Otherwise, we will deduce points.

For initializing the shared memory objects, CAREFULLY take a look at the defines and structs! The names of those objects, as well as variables to store file descriptors and pointers in, are predefined and can be found in the list of defines and provided structs in util.h.

IPC in a (way too oversimplified) nutshell

Those simplified explanations should not replace your attendance and attention in the lecture nor serve you the detailed solution to this assignment. It should help you to understand the central concept briefly to make research more accessible.

Virtual Memory Virtual memory is part of the concept of modern operating systems. As you may suspect, there is a difference between physical and virtual memory. The physical memory provided by, e.g. (SO-)DIMMs has to be managed by the operating system that uses virtual addresses for locating data. So there has to be a sort of translation between the physical and virtual memory addresses. If you are interested in this topic, you can look up this and related articles [1].

Process vs. Threads As many of you already know from previous assignments, processes are treated differently in contrast to threads – well, kind of. As you discovered, a thread can operate on the whole memory the program has mapped. This means, e.g., the heap-allocated by one thread can be used with any other thread the program launches. It is possible since they share the same virtual address space, as they are all used in one program launched before. So we can deduce that each process has its own virtual memory space. Those virtual memory spaces are strictly separated from each other.
Thus, sharing resources ”between” two processes are realized differently from sharing resources ”in” a process. So we have to share memory between two processes. This memory is surprisingly called shared memory, and it is a central part of IPC [2].

Shared resources Shared resources are “saved” in files, as the file system (FS) is accessible in both processes – well, kind of again. But besides some exceptions, all files saved on the FS can be accessed depending on the rights a user has. But some of you may wonder, how to access this data? Do we have to use ”read()” and ”write()” for sharing information? Well, this is possible with one big limitation. This limitation is that we cannot use virtual addresses to access data easily and fast. Well, there is a solution called ”mmap()”, which takes a file descriptor (fd) that maps the file (full size) to our virtual memory space of the program. That’s it, well, kind of. How this mapping works in the kernel will be taught in the following course called operating systems.

Locking When we use IPC, mostly, some locking mechanisms have to be used. In our case, semaphores or mutexes and condition variables are the way to go [3].


Holding a lock while going to sleep will lead to point deductions.

Some further hints

Remarks

  • For this assignment, you can assume that every party behaves optimally. E.g., the client only connects when the server is running and when a round has been initialized.
  • Please strictly separate your code into the appropriate functions. E.g., do the cleanup in the cleanup functions.

What to do before you start?

  • Pull from upstream!
  • Try to understand the program structure.
  • Look at the man-page and what the parameters of the needed functions are for and how they are used.
  • Only begin, if you understand the basic concept of processes, virtual memory, shared resources as well as mapping them. Bruteforcing will lead to a severe amount of wasted time.
  • Player.h, Computer.h, Launcher.h, util_server.c, util_client.c and the Makefile MUST NOT be changed,
  • You are only allowed to add code in between the TODOs and in the utils.c to create your own tests. Don’t remove or move around existing code ever (this would lead to point deductions).
  • Do not push binary files or any other junk files.
  • Carefully read the TODOs. Some contain crucial information!

FAQ

We will update this section with commonly asked questions.

Can the server terminate itself?

No. The server can only properly be terminated by sending the request “1” from the client. You don’t have to handle any misbehaviour that occurs when the server doesn’t terminate normally (e.g. by pressing Ctrl+C).

Can we assume that the server is already up and running when we start the client?

Yes. If everything is implemented correctly, the client will also return with an error message if the server is not running.

Submission

Modify the files in your git repository. You can find the files in directory A4. Tag the submission with A4 and push it to the server.

Assignment Tutor

If you have any questions regarding this assignment, try Discord first, and slp@iaik.tugraz.at second. If you have a more direct question regarding your specific solution, you can also ask the tutor who organizes this assignment.

Aaron Giner, aaron.giner@student.tugraz.at