我们都知道 errno,是存放系统调用错误的地方,其位于 <errno.h> 中,但是 它到底是怎么来的?系统是在什么时候设置的这个值的?毕竟系统调用 syscall 只会返回 -1 来表示当前系统调用出错……

实际上,在使用系统调用的时候,我们通过一系列 syscall 获取的返回值是已经被 libc 覆写过的了

Show me the code

https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/riscv/syscall.c.html#30

这里我们以 risc-v 架构上的代码为例,我们可以看到 syscall 的具体实现:

long int
syscall (long int syscall_number, long int arg1, long int arg2, long int arg3,
	 long int arg4, long int arg5, long int arg6, long int arg7)
{
  long int ret;
  ret = INTERNAL_SYSCALL_NCS_CALL (syscall_number, arg1, arg2, arg3, arg4,
				   arg5, arg6, arg7);
  if (INTERNAL_SYSCALL_ERROR_P (ret))
    return __syscall_error (ret);
  return ret;
}

这里有个宏 INTERNAL_SYSCALL_ERROR_P,其展开后是 ((unsigned long int) (val) > -4096UL)

这里 -4096UL 的十六进制是 FFFF FFFF FFFF F000

我们可以看到实际内核态执行的 syscall 的返回值就是 errno,只不过被 libc 包装了一下

将 errno 的值设置到了线程全局变量 __libc_errno 中,之后用户态的 errno 实际是个读取 __libc_errno 的宏

总结

libc 检查系统调用是否失败,失败后会设置 errno,之后返回的值是 -1

可以看到 __syscall_error 的具体实现:

ENTRY (__syscall_error)
	mv t0, ra
	/* Fall through to __syscall_set_errno.  */
END (__syscall_error)
/* Non-standard calling convention: argument in a0, return address in t0,
   and clobber only t1.  */
ENTRY (__syscall_set_errno)
	/* We got here because a0 < 0, but only codes in the range [-4095, -1]
	  represent errors.  Otherwise, just return the result normally.  */
	li t1, -4096
	bleu a0, t1, 1f
	neg a0, a0
#if RTLD_PRIVATE_ERRNO
	sw a0, rtld_errno, t1
#elif defined(__PIC__)
	la.tls.ie t1, errno
	add t1, t1, tp
	sw a0, 0(t1)
#else
	lui t1, %tprel_hi(errno)
	add t1, t1, tp, %tprel_add(errno)
	sw a0, %tprel_lo(errno)(t1)
#endif
	li a0, -1
1:	jr t0
END (__syscall_set_errno)