At the lowest possible level from a user program running in some operating system, the libc is making system calls (or syscalls). These are often a single machine instruction (often
SYSENTER) which is given some parameters and which switches the microprocessor to supervisor mode in a controlled way. Then the kernel is processing the parameters and handling the syscall and finally returning to user space. For Linux, you might read its Assembly HowTo and the x86-64 ABI spec.
BTW, you could install Linux and study the source code of some libc. Notice that musl-libc is quite readable.
Of course syscalls (and the protocol to invoke them) are operating system specific. You could read syscalls(2) to get a list of them (for Linux).
puts) would ultimately invoke the write(2) syscall. For performance reasons,
<stdio.h> is buffering.
printf is standardized in C99 (a programming language standard) but
write is standardized in the POSIX Portable Operating System Interface standard (but Microsoft Windows is not POSIX compliant).
printf does not call
fputs, but will use some syscall (like
write on Linux). BTW, some implementations (notably the GCC compiler and GNU libc thru its
__builtin__snprintf* builtins known by
/usr/include/bits/stdio2.h included by
<stdio.h>) are able to optimize some simple calls to
printf into calls to