I will use exit (), as in your example, although this applies to all system calls.
Functions of the form sys_exit () are the actual entry points into the kernel program that implements the function that you think of as exit (). These characters are not even accessible to user mode programmers. That is, if you do not crack the kernel, you cannot refer to these functions because their symbols are not available outside the kernel. If I wrote libmsw.a that had a file scope function, like
static int msw_func() {}
which you will not be able to associate with it, since it is not exported to the libmsw symbol table; i.e:
cc your_program.c libmsw.a
will result in an error, for example:
ld: cannot resolve symbol msw_func
because it is not exported; the same applies to sys_exit () contained in the kernel.
In order for the user program to switch to the kernel routine, the syscall (2) interface must be used to switch from user mode to kernel mode. When this mode switch (somtimes, called a trap) is encountered, a small integer is used to find the correct kernel routine in the kernel table, which maps integers to kernel functions. The entry in the table has the form
{SYS_exit, sys_exit},
Where SYS_exit is a preprocessor macro that
#define SYS_exit (1)
and there has been 1 since you were born, because there was no reason to change it. This is also the first entry in the system call table that looks for a simple array index.
As you noted in your question, the correct way for a regular user-mode program to access sys_exit is through a thin shell in glibc (or a similar base library). The only reason you ever need to mess with SYS_exit or sys_exit is if you are writing kernel code.