| ------------- |
| --- Intro --- |
| ------------- |
| |
| Linux running on processors without a memory management unit place certain |
| restrictions on the userspace programs. Here we will provide some guidelines |
| for people who are not familiar with such systems. |
| |
| If you are not familiar with virtual memory, you might want to review some |
| background such as: |
| http://en.wikipedia.org/wiki/Virtual_Memory |
| /usr/src/linux/Documentation/nommu-mmap.txt |
| |
| ---------------------------- |
| --- No memory protection --- |
| ---------------------------- |
| |
| By virtue of every process getting its own virtual memory space, applications |
| are protected from each other. So a bad memory access in one will not affect |
| the memory of another. When processors forgo virtual memory, they typically |
| do not add memory protection back in to the hardware. There are one or two |
| exceptions to this rule, but for now, we'll assume no one supports it. |
| |
| In practical terms, this means you cannot dereference bad pointers directly |
| and expect the kernel to catch and kill your application. However, you can |
| expect the kernel to catch some bad pointers when given to system calls. |
| |
| For example, this will "work" in the sense that no signal will be sent: |
| char *foo = NULL; |
| foo[0] = 'a'; |
| foo[1] = 'b'; |
| |
| However, the kernel should return errors when using "standard" bad pointers |
| with system calls. Such as: |
| char *foo = NULL; |
| write(1, foo, 10); |
| -> kernel will return EFAULT or similar |
| The other bad pointer you can rely on in your tests is -1: |
| char *foo = (void *)-1; |
| read(0, foo, 10); |
| -> kernel will return EFAULT or similar |
| |
| Otherwise, no bad pointer may reliably be tested, either directly or |
| indirectly via the kernel. This tends to be a large part of the UCLINUX |
| ifdef code that shows up in LTP. |
| |
| ---------------- |
| --- No forks --- |
| ---------------- |
| |
| The ubiquitous fork() function relies completely on the Copy On Write (COW) |
| functionality provided by virtual memory to share pages between processes. |
| Since this isn't feasible without virtual memory, there is no fork() function. |
| You will either get a linker error (undefined reference to fork) or you will |
| get a runtime failure of ENOSYS. |
| |
| Typically, fork() is used for very few programming paradigms: |
| - daemonization |
| - run a program |
| - parallelism |
| |
| For the daemonization functionality, simply use the daemon() function. This |
| works under both MMU and NOMMU systems. |
| |
| To run a program, simply use vfork() followed by an exec-style function. |
| And change the error handler in the child from exit() to _exit(). This too |
| works under both MMU and NOMMU systems. But be aware of vfork() semantics -- |
| since the parent and child share the same memory process, the child has to be |
| careful in what it does. This is why the recommended construct is simply: |
| pid_t child = vfork(); |
| if (vfork == 0) |
| _exit(execl(....)); |
| |
| For parallelism where processes use IPC to work together, you have to options, |
| neither of which are easy. You can rewrite to use threads, or you can re-exec |
| yourself with special flags to pass along updated runtime state. This is what |
| the self_exec() helper function in LTP is designed for. |
| |
| ------------------------- |
| --- No overcommitting --- |
| ------------------------- |
| |
| Virtual memory allows people to do malloc(128MiB) and get back a buffer that |
| big. But that buffer is only of virtual memory, not physical. On a NOMMU |
| system, the memory comes immediately from physical memory and takes it away |
| from anyone else. |
| |
| Avoid large mallocs. |
| |
| --------------------- |
| --- Fragmentation --- |
| --------------------- |
| |
| On a MMU system, when physical memory gets fragmented, things slow down. But |
| they keep working. This is because every new process gets a clean virtual |
| memory address space. While processes can fragment their own virtual address |
| space, this usually takes quite a long time and a lot of effort, so generally |
| it is not a problem people hit. |
| |
| On a NOMMU system, when physical memory gets fragmented, access to large |
| contiguous blocks becomes unavailable which means requests fail. Even if your |
| system has 40MiB _total_ free, the largest contiguous block might only be 1MiB |
| which means that allocations larger than that will always fail. |
| |
| Break up your large memory allocations when possible. Generally speaking, |
| single allocations under 2MiB aren't a problem. |
| |
| ----------------- |
| --- No paging --- |
| ----------------- |
| |
| No virtual memory means you can't mmap() a file and only have the pages read in |
| (paged) on the fly. So if you use mmap() on a file, the kernel must allocate |
| memory for it and read in all the contents immediately. |
| |
| --------------------- |
| --- No swap space --- |
| --------------------- |
| |
| See the "No paging" section above. For the same reason, there is no support |
| for swap partitions. Plus, nommu typically means embedded which means flash |
| based storage which means limited storage space and limited number of times |
| you can write it. |
| |
| ------------------------- |
| --- No dynamic stacks --- |
| ------------------------- |
| |
| No virtual memory means that applications can't all have their stacks at the |
| top of memory and allowed to grown "indefinitely" downwards. Stack space is |
| fixed at process creation time (when it is first executed) and cannot grow. |
| While the fixed size may be increased, it's best to avoid stack pressure in |
| the first place. |
| |
| Avoid the alloca() function and use malloc()/free() instead. |
| |
| Avoid declaring large buffers on the stack. Some people like to do things |
| such as: |
| char buf[PATH_MAX]; |
| This will most likely smash the stack on nommu systems ! Use global variables |
| (the bss), or use malloc()/free() type functions. |
| |
| ------------------------------- |
| --- No dynamic data segment --- |
| ------------------------------- |
| |
| No virtual memory means that mappings cannot arbitrarily be extended. Another |
| process might have its own mapping right after yours! This is where the brk() |
| and sbrk() functions come into play. These are most often used to dynamically |
| increase the heap space via the C library, but a few people use these manually. |
| |
| Best if you simply avoid them, and if you're writing tests to exercise these |
| functions specifically, make them nops/XFAIL for nommu systems. |
| |
| ------------------------------- |
| --- Limited shared mappings --- |
| ------------------------------- |
| |
| No virtual memory means files cannot be mmapped in and have writes to it |
| written back out to disk on the fly. So you cannot use MAP_SHARED when |
| mmapping a file. |
| |
| ------------------------- |
| --- No fixed mappings --- |
| ------------------------- |
| |
| The MAP_FIXED option to mmap() is not supported. It doesn't even really work |
| all that well under MMU systems. |
| |
| Best if you simply avoid it, and if you're writing tests to exercise this |
| option specifically, make them nops/XFAIL for nommu systems. |