【问题标题】:Linux system calls vs C lib functionsLinux 系统调用 vs C lib 函数
【发布时间】:2020-01-04 04:01:25
【问题描述】:

我对这两个有点困惑,所以这是我的问题;

Linux 手册页项目列出了所有这些功能: https://www.kernel.org/doc/man-pages/

recvfrom 为例,该函数既作为Linux 系统调用存在,也作为C 库函数存在。他们的文档似乎不同,但都可以使用#include <sys/socket.h> 访问。 我不明白他们的区别?

我还认为系统调用是使用可以直接在汇编中实现的十六进制值定义的,它们的列表在这里: https://syscalls.kernelgrok.com/

但是我在上面的链接中找不到recvfrom。在这一点上,我对 Linux 系统调用和 C lib 函数有点困惑!

编辑:补充问题,很多功能都在 (3) 之下,但不在 (2) 之下,即clean。这是否意味着这些都是由 C 运行时直接完成的,而不依赖于系统调用和底层操作系统?

【问题讨论】:

  • 我猜你的困惑的根源是你已经安装了manpages-posix-dev 或类似的包,它正在安装第 3 节中标准规范的联机帮助页格式版本。
  • 它们在那里是因为你安装了它们,而 POSIX/susv4 不知道“系统调用”,只有“接口”。该标准在 isatty() 之类的库函数或 read() 之类的系统调用包装器之间没有区别。
  • @josh 检查posix_madvise(3)/madvise(2) 和操作POSIX_MADV_DONTNEED/MADV_DONTNEED。 Kinda 解释了两个部分之间的关​​系。
  • 我没有安装任何东西,我只是在看上面的网页。
  • 那个 syscalls.kernelgrok 网页是关于 i386 的,它现在已经成为历史。您系统上的 syscall(2)syscalls(2) 手册页有更重要的信息,包括一个很好的表格,其中包含如何传递参数以及在 linux 支持的每个平台上使用的指令。

标签: c linux system-calls


【解决方案1】:

首先,了解 C 函数和系统调用是两个完全不同的东西

未封装在 C 库中的系统调用必须通过syscall 函数调用。这种调用的一个例子是gettid

要使用syscall 创建gettid 系统调用包装器,请执行以下操作:

#define _GNU_SOURCE
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

pid_t gettid(void)
{
    pid_t tid = (pid_t)syscall(SYS_gettid);
    return tid;
}

这是手册页注释部分的摘录,其中明确指出此函数未在 C 库中定义:

注意事项
Glibc 没有为这个系统调用提供包装器;使用 syscall(2) 调用它。


recvfrom 是一个围绕系统调用的 C 库包装器。
第 (2) 节中的所有内容都是系统调用。第 (3) 节中的所有内容都不是。第 (3) 节中的所有内容(除了一些值得注意的例外,例如 getumask)在 C 库中都有定义。第 (2) 节中大约一半的内容在 C 库中没有定义(或包装器)(POSIX 规定的函数和其他一些扩展除外),如gettid

在 C 中调用 recvfrom 时,C 库调用内核来进行系统调用。

syscall函数是将系统调用号放入%eax寄存器并使用int $0x80的函数。

您在https://syscalls.kernelgrok.com/ 中看不到recvfrom 的原因是https://syscalls.kernelgrok.com/ 非常非常不完整。

(3) 中有许多函数在(2) 中看不到,原因是(3) 中的许多函数没有系统调用。它们可能依赖也可能不依赖系统调用,只是没有支持它们的特定名称的系统调用。

【讨论】:

  • (3)中的函数在后台调用(2)中的函数吗?为什么有些名字一样
  • @josh 2 中的所有内容都是系统调用的包装器。 3 中的所有内容都不是,但可以间接调用 2 中的函数(例如 stdio.h 函数,如 getcprintf,使用 write)。有些名称相同,因为它们可能会或可能不会在系统调用之上实现。
  • @StaceyGirl 我们不要把 Windows 带入其中。
  • 如果我在 C 代码中使用 recvfrom 是使用 (2) 还是 (3),如何使用另一个?
  • 很多功能都在 (3) 之下,但不在 (2) 之下,即干净。这是否意味着这些都是由 C 运行时直接完成的,而不依赖于系统调用和底层操作系统?
【解决方案2】:

在linux系统调用下都存在

用户空间程序与内核通信的方式是使用syscall 函数。 syscall() 所做的只是将一些数字压入特定的寄存器,然后执行特殊的中断指令。在中断时将执行转移到内核,然后内核使用特殊寄存器从用户空间读取数据。

每个系统调用都有一个数字和不同的参数。用户空间程序应该通过例如检查文档来“找出”每个系统调用的参数。

Linux 系统调用只是一个数字,例如 __NR_recvfrom 在 x86-64 架构上等于 231。

C 库函数

C 库函数是由 C 库实现实现的函数。因此,例如 glibc 将 recvfrom 实现为 syscall(__NR_recvfrom, ...) 的简单包装器。这是库提供给程序员访问内核相关函数的 C 接口。所以 C 程序员不需要阅读每个系统调用的文档,并且有很好的 C 接口来调用内核。

但是我在上面的链接中找不到 recvfrom。

然后不要使用链接。最好检查uapi 目录下的内核源代码。

【讨论】:

  • 感谢您提供的信息,那么实际上将某个数字推送到特定寄存器的函数是哪一个?内核函数还是 C 函数?一个在幕后打电话给另一个?为什么名字一样?
  • 两者?那将取决于架构? syscall C 函数将数据推送到寄存器以让内核知道它调用了哪个系统调用,并且它还推送参数。内核将系统调用的返回值推送到寄存器。然后将执行上下文切换回用户空间。但是syscall本身只是一个C接口——它必须用机器指令(汇编)来实现,你必须调用int
  • is one calling the other one behind the scenes? - 我不明白。一旦执行转移到内核,一旦系统调用完成,它就会返回用户空间。异步通知通过信号传输到用户空间,例如。 aio_write。 (在“常规”操作中:)内核不会“调用”用户空间程序。使用空间调用内核。内核可以在用户空间程序中发出信号。 why are the named the same? - 避免混淆?您可以将 C 函数命名为与其所代表的系统调用号相同。
【解决方案3】:

首先,(2) 部分列出的函数是函数。它们与(3) 部分中的函数不同之处在于后面总是有一个系统调用。

这些函数通常会做一些额外的工作来使它们表现得像 POSIX 函数(将返回值转换为 -1errno),或者只是使它们可用(clone 系统调用需要 libc 集成才有用)。有时参数传递给系统调用的方式与函数原型所建议的不同,例如,它们可以打包到一个结构中,指向该结构的指针可以通过寄存器传递。

有时会添加一个新的系统调用来修复旧系统调用的一些问题。在这种情况下,可以使用新的系统调用透明地实现函数(参见mmap vs mmap2sys_select vs sys_old_select)。

对于recvfrom,与套接字相关的功能由their respective syscalls 或旧版sys_socketcall 实现。例如 musl 仍然有这个代码:

#ifdef SYS_socket
#define __socketcall(nm,a,b,c,d,e,f) syscall(SYS_##nm, a, b, c, d, e, f)
#define __socketcall_cp(nm,a,b,c,d,e,f) syscall_cp(SYS_##nm, a, b, c, d, e, f)
#else
#define __socketcall(nm,a,b,c,d,e,f) syscall(SYS_socketcall, __SC_##nm, \
    ((long [6]){ (long)a, (long)b, (long)c, (long)d, (long)e, (long)f }))
#define __socketcall_cp(nm,a,b,c,d,e,f) syscall_cp(SYS_socketcall, __SC_##nm, \
    ((long [6]){ (long)a, (long)b, (long)c, (long)d, (long)e, (long)f }))
#endif

如果可用,它会尝试使用适当的系统调用,否则会退回到socketcall

【讨论】:

  • sys_socketcall 不再适用——这只是为了兼容性,并且仅在某些平台上。
  • 对不起,我还是不明白,所以在 C 代码中调用 recvfrom 时,调用的是哪个 on?一个人在幕后打电话给另一个人吗?哪一个是实际将一些hex 代码放入寄存器然后调用操作系统的int 指令?
  • @josh 在过去,它会调用 socketcall 和适当的 argument。现在似乎有一个单独的系统调用。所以它可能会做一个或另一个。这取决于 libc 的实现。
  • ... 这些平台不是 x86-64、arm-eabi 或 arm64。
  • @mosvy 我不明白您为什么将 x86_32 称为某种死架构。 x86_64 内核仍然运行 32 位程序并且仍然需要实现socketcall__ARCH_WANT_SYS_SOCKETCALL 是无条件定义的 here
【解决方案4】:

recvfrom为例,这个函数既作为Linux系统调用存在,又作为C库函数存在。

我为recvfrom找到了2页:

Linux 页面通常还会说明该功能的 Linux 版本与 POSIX 版本有何不同。

它们与第 (3) 节中的函数不同,因为 [第 2 节] 后面总是有一个系统调用。

不一定。第 2 节是针对用户空间应用程序的 Linux 特定 API。 Linus Torvalds 坚持认为,用户空间应用程序决不能因为 Linux 内核 API 的变化而被破坏。 glibc 或其他库通常实现这些功能,以维护稳定的用户空间 API 并委托给内核。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    • 2012-06-30
    • 1970-01-01
    • 2013-11-27
    相关资源
    最近更新 更多