Here's the basic idea of how system calls are performed. The system I'm using for an example is Minix, because it's simple (I spent some time trying to figure out how it works in Linux, and it was such a mess of obscure #define goodies, inline functions, and stuff that I finally decided that if I ever did work it out, it would take several lectures to describe it...).
Something to keep in mind with Minix is that, because of its microkernel structure, it actually only has three system calls: send, receive, and sendrec. The idea is, if you want a subsystem to do something for you, you send a message to the subsystem you have in mind. So, the microkernel itself only recognizes calls to send a message, to receive a message, and to send a message and wait for a reply. Of these three, user processes are only allowed to use the last one (a kernel process typically just calls receive and waits for a message to come in. When a message arrives, it services the request, and sends a response back).
So here is how a ``read'' takes place. First, the user side.
Second comes the code for the handler in the operating system.
Last but not least, the code in the filesystem that interprets the read, in http://www.cs.nmsu.edu/~pfeiffer/classes/474/minix/src/fs/main.c
In Linux, the first step is very similar: the user program calls a
read() function in the standard C library; this function
does little more than put a system call number (the system call number
for read() is three) in the eax
register and invoke a software interrupt. This lands it in some
assembly code that you can find in
/usr/src/linux/arch/i386/kernel/entry.S
at the line containing ENTRY(system_call). This code
simply saves all the registers, makes the sure the call number is
within the range of defined system call numbers, and calls the routine
to service the call. After the call has been serviced, there is quite
a bit of code doing things like handling pending signals, and then it
returns from the interrupt handler.
Note that there is a lot less work here!