Linux系统调用何时触发segfault与返回EFAULT?

时间:2018-11-19 23:18:26

标签: c linux error-handling

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

  1. EFAULT tp指向可访问的地址空间之外。
  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内核如何在触发分段错误和让系统调用返回-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

2 个答案:

答案 0 :(得分:1)

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

话虽如此,EFAULT从来都不是您应该期望能够依靠的东西。一旦将无效的指针传递给需要有效指针的函数作为其接口协定的一部分,您就具有不确定的行为,而段错误或错误只是其中的一种可能的表现。从这个角度来看,甚至EFAULT都被记录下来是一个错误。

答案 1 :(得分:0)

  

我试图了解clock_gettime()何时会导致错误。

好的。

  

Linux内核如何在触发分段错误和让系统调用返回-EINVAL之间进行选择?它将何时选择后者?

很简单。如果功能设置为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;

如果clock_id值有些奇怪,则glibc包装器集的EINVAL。

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

以下代码产生了恶魔,并应该引发SIGSEGV:

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

以下代码也是如此:

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

如果内核始终发送信号,

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

  

实际上是否需要检查errno是否等于EFAULT?

没有必要检查任何错误。我认为您将解释错误和检查错误混在一起。 如果您的计算机遵循您提到的规范,如果clock_gettime返回EFAULT,则可以编写程序,因此假定clock_gettime的计算机上的基础实现遵循{{3 clock_gettime中的}}。但是,正如您所发现的,它没有发生,而是发生了未定义的行为,内核引发了SIGSEGV。这仅意味着clock_gettime函数的基础实现不遵循该手册。 linux manual page未指定EFAULT errno代码。但是,我相信可能存在可能返回EFAULT errno或任何其他errno代码的实现。但是,收到EFAULT错误时,您希望程序做什么?如何从这种错误中恢复?如果这些问题对您来说没有任何意义,那么为clock_gettime函数编写一个EFAULT处理程序可能是合理的。

请注意,您正在使用linux。 Linux,内核和glibc大多根据GNU通用许可证或GNU次级通用许可证进行许可,其中包含以下内容:

  

由于免费提供了图书馆使用许可,因此在适用法律允许的范围内,图书馆没有任何担保。除在编写版权持有人和/或其他各方的书面声明中另有规定外,“按原样”提供的图书馆不提供任何形式的保证,无论是明示或暗示的,包括但不限于对适销性和适用性的默示保证。 。图书馆的质量和性能承担全部风险。如果图书馆证明是无效的,则您承担所有必要的服务,维修或纠正的费用。

这个问题值得信任:您相信系统的clock_gettime()遵循linux手动实现吗?我不。如果您的系统获得POSIX证书,则可以对它们将按照手册所述的功能给予更多信任。没有人能保证您,它只是许多努力工作的人的良好意愿。