This is an old revision of the document!
Before you start, create a Google Doc. Here, you will add screenshots / code snippets / comments for each exercise. Whatever you decide to include, it must prove that you managed to solve the given task (so don't show just the output, but how you obtained it and what conclusion can be drawn from it). If you decide to complete the feedback for bonus points, include a screenshot with the form submission confirmation, but not with its contents.
When done, export the document as a pdf and upload in the appropriate assignment on moodle. The deadline is 23:55 on Friday.
When talking about memory, one can be referring either to the CPU's cache or main memory (i.e., RAM). Since the former has been discussed (hopefully exhaustively) during other courses such as ASC, today we'll be focusing on the latter. If you feel that there's still more for you to learn about the CPU cache, check out this very well-known article. With that out of the way, here's a few things to keep in mind moving forward:
Reminding you of this concept may be redundant at this point, but here goes. The programs that you are writing do not have direct access to the physical memory. All addresses that you are accessing from user space are translated to physical addresses by the Memory Management Unit (MMU) of the CPU. The MMU stores as many virtual – physical address pairs as it can in its Translation Lookaside Buffer (TLB). When the TLB fills up, the least accessed addresses are flushed to make room for new ones. When a new virtual address is encountered, the CPU will look up its physical counterpart in a structure managed by the kernel. This structure is in fact a 4-level tree where each node is a list of 512 entries pointing to the next node. The leaf nodes yield the physical page address. Some of you might have already noticed something strange. an offset in the range [0; 511] can be represented using only 9 bits. Having a 4-level page table means that the offsets fit into 36 bits of the 64-bit virtual address. If we add the size of a page offset (12 bits), we're still 16 bits short. Good catch! Modern x64 CPUs, while technically using 64-bit addresses, don't support 2^64 bytes of addressable virtual memory. That being said almost nobody ever complained about this, since 2^48 is still more than anyone needs.
So what are the reasons for implementing virtual memory? Simple: security, performance and convenience. Let's tackle these one by one:
Security: User space processes should not have direct access to the physical address space. If they did, they could inspect and change the memory of other processes, and possibly even the kernel's. Moreover, Every physical address that one can access (from the perspective of the kernel) is not only RAM. Some devices have memory mapped registers that the user can interact with by reading from / writing to them. E.g., a serial device driver can put a char on the line by writing it to a certain 32-bit aligned address. Similarly, it check whether the serial device is currently busy writing the previous character by reading a register constantly updated by said device with its status. Normally, you'd abstract the hardware from user space program by having drivers interpret requests presented by the process via system calls. By using virtual memory, even if the process has knowledge of the underlying hardware, it won't be able to access those device registers.
“But I really want to access those registers…” you may be thinking. No worries, then: Userspace I/O (UIO) is a kernel module that allows mapping device registers to your user space process, thus enabling you to implement drivers without actually knowing anything about how kernel modules work :p. If that's not convenient enough for you, there's also /dev/mem
. This device essentially can be opened as a regular file (i.e.: with open()
) and allows you to read / write physical memory. This is usually done with the pread()
and pwrite()
syscalls, respectively. Needless to say, using either of these systems requires your process having the CAP_SYS_ADMIN capability (if you don't know what that means, just run it with sudo :p).
In some very particular cases, you might want to know the physical addresses of your pages. On the surface, this might seem reasonable. After all, you can access them via virtual addressing, so why not? This could be done via /proc/<pid>/pagemap, but recently it's been changed to also require CAP_SYS_ADMIN. The reason for this is that knowing the physical address of your memory pages can allow you to mount cache-based side channel attacks against other processes. This is not trivial threat; cache side-channels are the most common class of hardware side-channels and among the only practical ones, even in a research context.
Performance: You should already be fairly familiar with this: processes that use the same library don't in fact have their own copy in RAM. In stead, virtual addresses to the read-only pages of a library usually point to the same physical address in RAM. The advantage here is that you don't have to load a dozen different libraries from persistent storage (i.e.: HDD, SSD, etc.) when you start up a process. Let's say that you have 1000 processes, each using libc.so. Having ~1.8MB of read-only pages backed by libc.so copied over in RAM for each process would easily exhaust ~2GB of your RAM. And that just one library… That being said, even mapping these libraries in the virtual address space (using mmap()
, usually taken care of by ld-linux.so
for you) is a costly operation. Looking at the American Fuzzy Lop (AFL) fuzzer, we can find an interesting optimization called Fork Server that allows bypassing the problem of re-mapping all libraries in the address space on newly spawned instances of the same server by hooking the main()
function and in stead of exec()
-ing thousands of times per second, it simply fork()
s the process so that the children start off with a copy of the original's address space. Fun stuff!
For this lab, we will use Google Colab for exploring pandas and seaborn. Please solve your tasks here by clicking “Open in Colaboratory”.
You can then export this python notebook as a PDF (File → Print) and upload it to Moodle.
Please take a minute to fill in the feedback form for this lab.