我现在试图通过查看实际代码实现来了解 read(2) 函数的工作原理,首先,我尝试查看它是如何在#include 头文件中定义的。
在那个文件中,我发现了这个:
ssize_t read(int, void *, size_t) __DARWIN_ALIAS_C(read);
然后,我用谷歌搜索找到了实际的 read() 函数声明。
和,
https://github.com/lattera/glibc/blob/master/io/read.c
我找到了这个。在这段代码中,
/* Read NBYTES into BUF from FD. Return the number read or -1. */
ssize_t
__libc_read (int fd, void *buf, size_t nbytes)
{
if (nbytes == 0)
return 0;
if (fd < 0)
{
__set_errno (EBADF);
return -1;
}
if (buf == NULL)
{
__set_errno (EINVAL);
return -1;
}
__set_errno (ENOSYS);
return -1;
}
这是我现在的问题。
__libc_
之前 read
?为什么需要它?当用户调用 read(2) 时,如何调用这个函数?
那么,代码在哪里实际实现了 read(2) 函数的实际功能呢?
我是否以错误的方式或来源查找和发现?
最佳答案
read
(并且,传统上,Unix 手册的“第 2 节”中定义的所有函数——这就是 (2)
的意思)是系统调用。这意味着大部分工作由操作系统内核完成,而不是由您自己进程中的代码完成。 C 库仅包含一个系统调用包装器,该包装器执行将控制权转移到内核的特殊指令。
您找到的代码是占位符,而不是系统调用包装器。正如您所猜测的,它实际上并没有实现 read
.它只会被临时使用,在一个不完整的操作系统端口中,没有名为 read
的系统调用。 .您正在查看的 C 库中的所有完整端口都没有实际使用该代码。他们改为使用真正的系统调用包装器。这个 C 库在构建时自动生成系统调用包装器,因此我无法链接到实际代码,但我可以向您展示为系统调用包装器生成的代码可能是什么样子的示例。 (注意:这不是我熟悉的任何操作系统上使用的实际代码。我故意删除了一些复杂性。)
.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
我故意用 x86 汇编语言写了这个例子,因为没有办法得到特殊的
syscall
来自普通 C 的指令。一些 C 库对 syscall
使用“汇编插入”扩展。指令并用 C 编写包装器的其余部分,但是对于您想要理解的内容,汇编语言是您应该考虑的。在内核内部,有一个特殊的“陷阱处理程序”,它接收来自
syscall
的控制权。操作说明。它查看 %eax 中的值,发现它是系统调用号 SYS_read
(实际数值可能因操作系统而异),并调用实际实现 read
的代码手术。系统调用返回后,包装器测试它是否返回负数。如果是这样,则表示错误。 (注意:这是我删除了一些复杂性的地方之一。)它翻转该数字的符号,将其复制到
errno
(这比 mov %eax, errno
更复杂,因为 errno
是 thread-local variable ),并返回 -1。否则返回的值是读取的字节数,它直接返回。另一个答案链接到
read
的实现。但不幸的是,它来自一个流行但复杂且难以理解的操作系统内核。我很遗憾地说我没有更好的教学例子来指点你。__libc_
read
上的前缀有占位符实现是因为 read
实际上有三个不同的名称。在这个 C 库中:read
, __read
, 和 __libc_read
.正如另一个答案指出的那样,您引用的代码下方有一些特殊的宏,它们将它们全部安排为同一函数的名称。 read
的自动生成的真实系统调用包装器也将拥有所有这些名称。这是实现“命名空间清洁”的一种技巧,如果您打算实现一个成熟且完全符合标准的 C 库,您只需要担心这一点。简短的版本是C库中有很多函数需要调用
read
,但他们不能使用名称 read
调用它,因为在技术上允许 C 程序定义一个名为 read
的函数本身。顺便说一句,您需要注意查看属于同一个 C 库的头文件和实现代码。您似乎拥有
unistd.h
来自您计算机上的 MacOS,但 read
您找到的代码属于 GNU C 库,这是一个完全不同的实现。 read
的基本声明,ssize_t read(int, void *, size_t);
由 POSIX 标准指定,因此两者相同,但
__DARWIN
之后的事情是 MacOS C 库的一个怪癖。 GNU 库有一个具有不同怪癖的声明:extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
关于c - 我在查看 <unistd.h> 中定义的 read() 函数代码时遇到了麻烦,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57728145/