You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
6.6 KiB
171 lines
6.6 KiB
------------- |
|
--- 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.
|
|
|