Linux 系统调用何时触发段错误与返回 EFAULT?

When do Linux system calls trigger a segfault vs returning EFAULT?

我正在尝试了解 clock_gettime() 何时会导致错误。手册页列出了以下两种可能性:

  1. EFAULT tp 指向可访问地址之外space。
  2. EINVAL 此系统不支持指定的 clk_id。

很容易触发 EINVAL 错误,但我无法让 clock_gettime()errno 设置为 EFAULT。相反,内核会发送一个 SIGSEGV 信号来终止程序。例如,在下面的代码中:

#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    struct timespec tp;
    double time;

    if (clock_gettime(CLOCK_MONOTONIC, &tp + 4096) == -1) {
        if (errno == EINVAL) {
            perror("EINVAL");
            return EXIT_FAILURE;
        } else if (errno == EFAULT) {
            perror("EFAULT");
            return EXIT_FAILURE;
        } else {
            perror("something else");
            return EXIT_FAILURE;
        }
    }

    time = tp.tv_sec + 1e-9 * tp.tv_nsec;
    printf("%f\n", time);
}

Linux 内核如何在触发分段错误和系统调用 return -EINVAL 之间做出选择?它什么时候会选择做后者?如果内核总是发送信号,是否真的需要检查errno是否等于EFAULT

我是 运行 Linux 内核 4.15,我用(使用 clang v6.0)编译程序: clang -g -O0 -Wall -Wextra -Wshadow -Wstrict-aliasing -ansi -pedantic -Werror -std=gnu11 file.c -o file

clock_gettime 可能不作为系统调用执行,而是作为 vdso 的一部分在用户空间中执行。如果您实际上通过使用以 SYS_clock_gettime 作为参数的 syscall 函数来执行系统调用,我希望您看到 EFAULT.

话虽如此,EFAULT 并不是您应该期望能够依赖的东西。一旦将无效指针传递给需要有效指针作为其接口协定的一部分的函数,就会出现未定义的行为,并且段错误或错误只是一种可能的表现形式许多。从这个角度来看,甚至记录 EFAULT 也是一个错误。

I'm trying to understand when clock_gettime() can lead to errors.

好的。

How does the Linux kernel choose between triggering a segmentation fault and having the system call return -EINVAL? When will it choose to do the latter?

很简单。有一些检查以防它们为真,函数设置 errno。如果您访问受保护的内存区域,内核会向您的进程发送 SIGSEGV。

如果您检查 __clock_gettime from glibc 函数,您会看到:

switch (clock_id)
    {
#ifdef SYSDEP_GETTIME
      SYSDEP_GETTIME;
#endif

#ifndef HANDLED_REALTIME
    case CLOCK_REALTIME:
      ...
      break;
#endif

    default:
#if HP_TIMING_AVAIL
      if ((clock_id ...) == CLOCK_THREAD_CPUTIME_ID)
           ...
      else
#endif
            __set_errno (EINVAL);
      break;

glibc 包装器集的 EINVAL,以防出现一些奇怪的 clock_id 值。

在未定义的行为中取消引用任何有效内存区域之外的指针值并生成 nasal demons. On Linux a SIGSEGV 是发送到试图写入受保护内存区域的进程的信号。

以下代码会产生恶魔并且应该引发 SIGSEGV:

struct timespec tp;
*(&tp + 4096) = (struct timespec){0};

以下代码也是如此:

struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp + 4096)

If the kernel always sends the signal,

不是真的。如果恰好从 &tp + 4096 开始的 sizeof(struct timespec) 字节不在受保护的内存区域中,内核将不会发送任何信号,因为它会认为,你在自己的内存中写入。

is it actually necessary to check whether errno equals EFAULT?

没有必要检查任何错误。我认为您将解释错误与检查错误混为一谈。 如果你的机器遵循你提到的规范,如果clock_gettime returns EFAULT你可以编写你的程序,所以它假设你的机器上的底层实现clock_gettime 遵循 linux manual page of clock_gettime. However, as you discovered, it does not, instead undefined behaviour happens and the kernel raises SIGSEGV. Which only means that the underlying implementation of the clock_gettime function does not follow the manual. The POSIX 没有指定 EFAULT errno 代码。但是我相信可能存在 return EFAULT errno 或任何其他 errno 代码的实现。但是,您希望您的程序在收到 EFAULT 错误时做什么?如何从此类错误中恢复?如果这些问题对您有任何意义,那么为 clock_gettime 函数编写一个 EFAULT 处理程序可能是合理的。

请注意,您正在使用 Linux。 Linux,kernel 和 glibc,大多是在 GNU General License 或 GNU Lesser General License 下授权的,其中包含以下内容:

BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

问题归结为信任:您相信您的系统 clock_gettime() 会遵循 Linux 手动执行吗?我不。如果您的系统是 POSIX 证书,您可以更加信任它们将按照手册所述工作的功能。没有人向你保证,这只是许多努力工作的人的善意。