read
(and, traditionally, all of the functions defined in "section 2" of the Unix manual -- that's what that (2)
means) is a system call. That means most of the work is done by the operating system kernel, not by code in your own process. The C library only contains a system-call wrapper that executes a special instruction that transfers control to the kernel.
The code you found is a placeholder, not a system-call wrapper. As you surmised, it doesn't actually implement read
. It would only ever be used temporarily, in an incomplete port to an operating system that doesn't have a system call named read
. None of the complete ports in the C library you are looking at actually use that code. They instead use a real system-call wrapper. This C library automatically generates system-call wrappers at build time, so I can't link to actual code, but I can show you an example of what the generated code for a system-call wrapper might look like. (Note: this is NOT the actual code used on any operating system I am familiar with. I deliberately removed some complications.)
.text
.globl read
.type read, @function
read:
movl $SYS_read, %eax
syscall
testq %rax
js .error
ret
.error:
negl %eax
movq errno@gottpoff(%rip), %rdx
movl %eax, %fs:(%rdx)
movq $-1, %rax
ret
I wrote this example in x86 assembly language on purpose, because there's no way to get the special syscall
instruction from plain C. Some C libraries use an "assembly insert" extension for the syscall
instruction and write the rest of the wrapper in C, but for what you're trying to understand, the assembly language is what you should think about.
Inside the kernel, there's a special "trap handler" that receives control from the syscall
instruction. It looks at the value in %eax, sees that it is the system call number SYS_read
(the actual numeric value may vary from OS to OS), and calls the code that actually implements the read
operation.
After the system call returns, the wrapper tests whether it returned a negative number. If so, that indicates an error. (Note: this is one of the places where I removed some complications.) It flips the sign of that number, copies it into errno
(which is more complicated than just mov %eax, errno
because errno
is a thread-local variable), and returns −1. Otherwise the value returned is the number of bytes read and it returns that directly.
The other answer links to an implementation of read
but unfortunately it's from an OS kernel that's popular but complicated and difficult to understand. And I regret to say I don't have a better teaching example to point you at.
The __libc_
prefix on the read
placeholder implementation is there because there are actually three different names for read
in this C library: read
, __read
, and __libc_read
. As the other answer points out, there's some special macros below the code you quoted that arrange for them all to be names for the same function. The auto-generated real system-call wrapper for read
will also have all of those names.
This is a hack to achieve "namespace cleanliness", which you only need to worry about if you ever set out to implement a full-fledged and fully standards compliant C library. The short version is that there are many functions in the C library that need to call read
, but they cannot use the name read
to call it, because a C program is technically allowed to define a function named read
itself.
Incidentally, you need to take care to look at headers and implementation code belonging to the same C library. You appear to have the unistd.h
from MacOS on your computer, but the read
code you found belongs to the GNU C Library, which is a completely different implementation. The basic declaration of read
,
ssize_t read(int, void *, size_t);
is specified by the POSIX standard, so it will be the same in both, but the __DARWIN
thing after that is a quirk of the MacOS C library. The GNU library has a declaration with different quirks:
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
read
is a system call. So all you'll find in the library implementation is the necessary hooks to invoke it. The "meat" of the function is all in the OS kernel. (And the example you found, for some reason, doesn't even do that.) – Stability