c - 我在查看 <unistd.h> 中定义的 read() 函数代码时遇到了麻烦

标签 c unix posix system-calls unistd.h

我现在试图通过查看实际代码实现来了解 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) 时,如何调用这个函数?
  • 在我看来,这段代码与从文件描述符读取缓冲区无关,而只是处理可能的错误的代码: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
    

    我故意用 x86 汇编语言写了这个例子,因为没有办法得到特殊的 syscall来自普通 C 的指令。一些 C 库对 syscall 使用“汇编插入”扩展。指令并用 C 编写包装器的其余部分,但是对于您想要理解的内容,汇编语言是您应该考虑的。

    在内核内部,有一个特殊的“陷阱处理程序”,它接收来自 syscall 的控制权。操作说明。它查看 %eax 中的值,发现它是系统调用号 SYS_read (实际数值可能因操作系统而异),并调用实际实现 read 的代码手术。

    系统调用返回后,包装器测试它是否返回负数。如果是这样,则表示错误。 (注意:这是我删除了一些复杂性的地方之一。)它翻转该数字的符号,将其复制到 errno (这比 mov %eax, errno 更复杂,因为 errnothread-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/

    相关文章:

    regex - 最大咀嚼是如何实现的?

    php - 如何在Windows中安装 'posix'

    c++ - 将自定义控件添加到控制台窗口

    c++ - 基于 32 位和 64 位系统的 C 或 C++ 语言的数据类型大小有何不同

    c - 0A 转换为 C 中的 0D0A

    c - 用于匹配消息的 POSIX 正则表达式

    c - C 中 POSIX 线程的并行 I/O

    c - 如何在 C 编程中扫描文件中的某个单词,然后在另一个文件中打印包含该单词的行?

    C: tcp recv 不清除旧数据

    c - C中fprintf的间距