uClinux stands for microcontroller Linux (u for the Greek letter mu denoting micro and C for controller). This is a option within the Linux kernel designed for use on devices without a Memory Management Unit (MMU). Considering that the absence of MMU support in uClinux, little kernel and user space software is affected. Two differences, the lack of memory protection and the lack of a virtual memory model, between mainstream Linux and uClinux are a consequence of the removal of MMU support from uClinux.
Memory Protection
One consequence of operating without memory protection is that an invalid pointer reference (null pointer, or pointer to an odd address), even by an unprivileged process, may trigger an address error, and potentially corrupt or even shut down the system. Obviously code running on such a system must be programmed carefully and tested diligently to ensure robustness and security.
Virtual Memory
When Linux is running on an architecture where a full MMU exists, the MMU provides Linux programs basically unlimited stack and heap space. This is done by the the virtualization of physical memory. When an Linux application needs more memory, it asks the memory manager; the memory manager finds empty physical memory (if it can't find physical memory, it swaps physical memory to the hard drive, and frees it), and maps to this physical memory to the virtual memory of the application.
Without the virtual to physical address mapping, you cannot move the physical memory around without breaking applications. This means the kernel cannot swap chunks of memory out to some form of storage media which means memory usage is limited to the amount of physical memory. Because it can not support virtual memory (MMUless), it allocates stack space at the end of the data for the executable. If the stack grows too large on µClinux, it will overwrite the static data and code areas. This means that the developer, who previously was oblivious to stack usage within the application, must now be aware of the stack requirements.
One consequence of running Linux with out virtual memory is that processes which are loaded by the kernel must be able to run independently of their position in memory. One way to achieve this is to fix-up address references in a program once it is loaded into RAM, the other is to generate code that uses only relative addressing (referred to as PIC, or Position Independent Code). uClinux supports both of these methods.
Another consequence arises from memory allocation and deallocation occurring within a flat memory model. A lot of dynamic memory allocation can result in fragmentation which can starve the system. One way to improve the robustness of applications that perform dynamic memory allocation, is to replace malloc() calls with requests from a preallocated buffer pool. Swapping pages in and out of memory is not implemented since it cannot be guaranteed that the pages would be loaded to the same location in RAM. In embedded systems it is also unlikely that it would be acceptable to suspend an application in order to use more RAM than is physically available.
Finally, keep in mind that the lack of an MMU prevents the kernel from over committing memory. The concept of over committing is where the kernel will let applications allocate large chunks of virtual memory without verifying whether there is enough physical memory to back it up. The reason this normally works is that applications commonly ask for huge buffers but then only ever utilize small portions of it. So if the kernel lets the application think it has 20megs because it only ever writes to 1meg (and thus utilize only 1meg of physical memory), everything runs much smoother. Unfortunately, this behavior is attained by delaying the physical memory allocation to when virtual memory requests for it which requires a MMU. So on a no-MMU system, if your application asks for 20megs, the kernel will have to actually reserve 20megs from the physical memory pool, possibly wasting 19megs of memory as your application only needed 1meg. Be mindful of your memory usage and wastage!
The lack of memory management hardware on uClinux target processors requires some changes to the Linux system interface. Perhaps the greatest difference is the absence of the fork() and brk() system calls. A call to fork() clones a process to create a child. Under Linux, fork() is implemented using copy-on-write pages. Without an MMU uClinux cannot completely and reliably clone a process, nor does it have access to copy-on-write. uClinux implements vfork() in order to compensate for the lack of fork(). When a parent process calls vfork() to create a child, both processes share all their memory space including the stack. vfork() then suspends the parent's execution until the child process either calls exit() or execve(). Note that multitasking is not otherwise affected. Programs which rely heavily on the fork() system call may require substantial reworking to perform the same task under uClinux. Also, uClinux has neither an autogrow stack nor brk() and so user space programs must use the mmap() command to allocate memory. For convenience, the C library implements malloc() as a wrapper to mmap(). There is a compile-time option as well as a run time application (flthdr) to set the stack size of a program.
Memory Subsystem
The architecture-generic memory management subsystem of uClinux has been modified to remove reliance on MMU hardware and provides basic memory management functions within the kernel software itself. This is the role of the directory /mmnommu, derived from and replacing the directory /mm. Several subsystems were modified, added, removed or rewritten; kernel and user memory allocation and deallocation routines have been reimplemented; and support for transparent swapping / paging has been removed. Program loaders which support Position Independent Code (PIC) have been added and a new binary object code format named flat, which supports PIC, was created. Other program loaders, such as that for ELF, have been modified to support other formats which, instead of using PIC, use absolute references. It is then the responsibility of the kernel to fix-up these references during run time. Both methods have advantages and disadvantages. Traditionally PIC is quick and compact but has a size restriction on some architectures. The runtime fix-up technique removes this size restriction, but incurs overhead when the program is loaded by the kernel.
C library difference
uClinux uses uClibc as the standard C library instead of glibc. uClibc is much smaller than GNU C Library (glibc), the C library normally used with desktop Linux distributions. While both glibc and uClibc are intended to fully support all relevant C standards across a wide range of platforms, uClibc is specifically focused on embedded Linux. Features can be enabled or disabled according to space requirements, there are features in glibc which are not required by any standard which uClibc does not support.
More information
There is a good article by David McCullough: uClinux for Linux Programmers.