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.
172 lines
6.6 KiB
172 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.
|