All this stuff is of course documented in (or inferable from) source. Current kernel release as I write is 2.3.99pre6 ($Date: 2000-11-12 20:56:12 $).
There is another explanation at linuxassembly.org. You might like to look at a page about making tiny elf images. For more in depth of the ABI, see SCO Developer Specifications.
The ELF handling kernel code is currently a bit confused with a.out handling code, and tries to support various variants of ELF, making it difficult to understand. The main feature of interest from a userland perspective is what gets left on the stack.
Top of diagram is %esp (the address from which things are popped off).
|Number of arguments (argc)||Unsigned long|
|Program name (argv)||String pointer|
|List of command line arguments, if any||String pointer array|
|Environment variables (VAR=VALUE format)||String pointer array|
|ELF header. Normally contains only AT_HWCAP and AT_PLATFORM (so you can find out CPU version and capability flags). It is terminated by AT_NULL (two unsigned longs).||Pair of unsigned longs per parameter|
Finally, ELF_PLAT_INIT macro is called, which resets registers to 0 (on i386 at least -- see elf.h).
These two and es are reset regularly by something (what?). Therefore it is possible to store only a limited amount of information in them. As far as I can see, if they're 0, they're not changed to non-zero and vice versa, so you get two extra independent bits to store things. Update: I saw a changes list on linux-kernel which claimed that the fs/gs modfication bug reported by Ulrich Drepper was fixed, so perhaps you now get full use of these registers.
System call arguments are passed to the kernel in registers. In entry.S they're all pushed onto the stack, and the actual syscall (usually C) procedure is called. This procedure is declared asmlinkage so that it takes all its arguments off the stack.
There is an example of how to make a syscall in glibc-2.1.2/sysdeps/unix/sysv/linux/i386/sysdep.h, but I don't think it's fully up to date wrt the 2.3.99 kernels. It explains in a comment the argument passing convention, but new kernels (from my understanding of entry.S) do not clobber any registers and allow a sixth argument to be passed in %ebp. In the old days (in which glibc at version 2.1.2 lives), calls with 6 arguments (like mmap) had to pass a pointer to a structure containing them.
|syscall number||%eax||Syscall return value|
|arg 2||%ecx||Clobbered (saved in 2.3?)|
|arg 3||%edx||Clobbered (saved in 2.3?)|