Friedrich-Alexander-Universität Erlangen-Nürnberg  /   Technische Fakultät  /   Department Informatik
Assignment 7: Extended Paging

Especially fork requires copying a lot of pages – although some of them will stay the same on both parent and child and therefore can be shared. This sharing not only saves memory but improves the performance as well.

Copy-On-Write (COW)

Pages to be copied while using fork or send-receive-reply should be displayed (mapped) in the other address space unless a write access occurs – only then they will be physically copied.

To detect a write access, the pages concerned have to be mapped as read-only. Thus, the page fault handler is triggered on a write operation. A shadow page table for all affected physical page addresses should count the number of references on COW pages. On a write operation, the reference gets decremented and the page needs to be copied (unless there is only this one reference left). Additionally, the page mapping of the process needs to be adjusted (don't forget to invalidate those pages in the translation lookaside buffer (TLB)).

Note
Pages originally marked as read-only should (obviously) not become writable after such a copy operation. However, reference counting might be required for such pages as well (for example, if the parent process exits, the text segment has to be available still until the child exits).

For send-recv or reply-send the contents of the buffers can only be copied using COW if the message size is at least equal to the page size (leftovers have to be copied physically) and the alignment of both buffers are identical. To fulfill the latter requirement, the buffers are best placed at page borders. This can be achieved in GCC/LLVM with __attribute__ ((aligned (x))), whereas the constant x defines the alignment in bytes.

Test Application

Again, you should extensively test your implementation. The application of the previous assignment can be re-used with a few adjustments (alignment) as mentioned above.

The forkapp has to be changed accordingly:

char __attribute__((aligned(4096))) sbuf[8194];
char __attribute__((aligned(4096))) rbuf[8194];
void main() {
fork();
fork();
if (fork() == 0) {
/* child */
sbuf[0] = 3;
sbuf[8192] = getppid();
sbuf[8193] = 1;
send(other, sbuf, 4097, rbuf, 4097);
char msg[] = "REPLY: AA\n";
msg[7] += rbuf[0] + sbuf[8193];
msg[8] += 4 + ppid;
write(0, msg, 10);
} else {
/* parent */
int X = recv(rbuf, 8193);
rbuf[0] = rbuf[0] + rbuf[8192];
rbuf[8193] = 7;
reply(X, rbuf, 8193);
}
exit();
}

Demonstrate the advantages of COW regarding the memory usage by printing the available page frames: