H.P*_*ter 4 c unix posix system-calls unistd.h
我现在试图通过研究实际的代码实现来了解 read(2) 函数的工作原理,首先,我尝试了解它是如何在 #include 头文件中定义的。
在该文件中,我发现了这个:
ssize_t read(int, void *, size_t) __DARWIN_ALIAS_C(read);
Run Code Online (Sandbox Code Playgroud)
然后,我用谷歌搜索找到了实际的 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;
}
Run Code Online (Sandbox Code Playgroud)
现在这是我的问题。
__libc_之前是什么read?为什么需要它?当用户调用read(2)时,如何调用这个函数?
在我看来,这段代码与从文件描述符读取缓冲区无关,而是只有处理可能的错误的代码: fd < 0 或 buff is NULL 等。那么,代码实际在哪里实现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
Run Code Online (Sandbox Code Playgroud)
我特意用 x86 汇编语言编写了这个示例,因为无法syscall从普通 C 中获取特殊指令。一些 C 库使用指令的“汇编插入”扩展,syscall并用 C 编写包装器的其余部分,但是为了什么呢当你试图理解时,你应该考虑汇编语言。
在内核内部,有一个特殊的“陷阱处理程序”,它接收来自syscall指令的控制。它查看 %eax 中的值,发现它是系统调用号 SYS_read(实际数值可能因操作系统而异),并调用实际实现该read操作的代码。
系统调用返回后,包装器测试它是否返回负数。如果是这样,则表明存在错误。(注意:这是我消除了一些复杂性的地方之一。)它翻转该数字的符号,将其复制到errno(这比仅仅mov %eax, errno因为errno是线程局部变量更复杂),然后返回 -1。否则,返回的值是读取的字节数,并直接返回该值。
另一个答案链接到一个实现,read但不幸的是它来自一个流行但复杂且难以理解的操作系统内核。我很遗憾地说我没有更好的教学例子可以向您指出。
__libc_占位符实现上的前缀之所以存在,是因为此 C 库中read实际上有 3 个不同的名称: 、和。正如另一个答案指出的那样,您引用的代码下面有一些特殊的宏,它们将它们全部安排为同一函数的名称。自动生成的真实系统调用包装器也将具有所有这些名称。readread__read__libc_readread
这是一个实现“命名空间清洁”的 hack,如果您打算实现一个成熟且完全符合标准的 C 库,您只需担心这一点。简而言之,C 库中有许多函数需要调用read,但它们不能使用名称 read来调用它,因为 C 程序在技术上允许定义一个名为read自身的函数。
顺便说一句,您需要注意查看属于同一C 库的头文件和实现代码。您的计算机上似乎有unistd.h来自 MacOS 的代码,但read您找到的代码属于 GNU C 库,这是一个完全不同的实现。的基本声明read,
ssize_t read(int, void *, size_t);
Run Code Online (Sandbox Code Playgroud)
由 POSIX 标准指定,因此两者都是相同的,但__DARWIN之后的事情是 MacOS C 库的一个怪癖。GNU 库有一个具有不同怪癖的声明:
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
Run Code Online (Sandbox Code Playgroud)