美文网首页
errno详解

errno详解

作者: 谦悠 | 来源:发表于2019-10-18 13:50 被阅读0次

在库函数执行出错时,通常都会返回一个负值,并设置全局变量errno为特定信息的值,那errno是如何解决多线程访问问题的呢?

下面开始通过源码一探究竟。

(使用的源码版本linux-4.7.1)

想要使用errno需要include errno.h头文件,首先来查看一下errno.h

#include <uapi/linux/errno.h>
... ...

这里定义了一些errno特定信息的值,但并没有errno的定义。

然后查看uapi/linux/errno.h

#include <asm/errno.h>

这个文件只有一行代码。

下面继续查看asm/errno.h
选择一个平台,arch/x86/include/uapi/asm/errno.h

#include <asm-generic/errno.h>

继续查看asm-generic/errno.h

#include <asm-generic/errno-base.h>
... ...

这里定义了一些errno特定信息的值,但并没有errno的定义。

查看asm-generic/errno-base.h
这里定义了一些errno特定信息的值

没有找到errno定义的地方!!!

去网上看一下, errno是c库的机制,所以需要去glibc源码查找

glibc/stdlib/errno.h

#include <bits/errno.h>
... ...
/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */
#ifndef errno
extern int errno;
#endif

从上面可以看出,如果没有定义errno那么errno就会是extern int类型的全局变量,这样的errno无法应付多线程的应用场景。

在bits/errno.h中,找到关于errno定义部分:

# ifndef __ASSEMBLER__
/* Function to get address of global `errno' variable.  */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));

#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif
# endif /* !__ASSEMBLER__ */
#endif /* _ERRNO_H */

__THROW的含义
__attribute__的含义
在这里我们看到errno被重新定义为*__errno_location ()

记下来去寻找__errno_location ()的定义:
glibc/sysdeps/mach/hurd/errno-loc.c

#include <errno.h>
#include <hurd/threadvar.h>

int *
__errno_location (void)
{
  return (int *) __hurd_threadvar_location (_HURD_THREADVAR_ERRNO);
}
strong_alias (__errno_location, __hurd_errno_location)
libc_hidden_def (__errno_location)

继续查看__hurd_threadvar_location (_HURD_THREADVAR_ERRNO)的含义:
glibc/hurd/hurd/threadvar.h

#include <features.h>

/* The per-thread variables are found by ANDing this mask
   with the value of the stack pointer and then adding this offset.
   In the multi-threaded case, cthreads initialization sets
   __hurd_threadvar_stack_mask to ~(cthread_stack_size - 1), a mask which
   finds the base of the fixed-size cthreads stack; and
   __hurd_threadvar_stack_offset to a small offset that skips the data
   cthreads itself maintains at the base of each thread's stack.
   In the single-threaded case, __hurd_threadvar_stack_mask is zero, so the
   stack pointer is ignored; and __hurd_threadvar_stack_offset gives the
   address of a small allocated region which contains the variables for the
   single thread.  */

extern unsigned long int __hurd_threadvar_stack_mask;
extern unsigned long int __hurd_threadvar_stack_offset;

/* A special case must always be made for the signal thread.  Even when there
   is only one user thread and an allocated region can be used for the user
   thread's variables, the signal thread needs to have its own location for
   per-thread variables.  The variables __hurd_sigthread_stack_base and
   __hurd_sigthread_stack_end define the bounds of the stack used by the
   signal thread, so that thread can always be specifically identified.  */

extern unsigned long int __hurd_sigthread_stack_base;
extern unsigned long int __hurd_sigthread_stack_end;
extern unsigned long int *__hurd_sigthread_variables;


/* At the location described by the two variables above,
   there are __hurd_threadvar_max `unsigned long int's of per-thread data.  */
extern unsigned int __hurd_threadvar_max;

/* These values are the indices for the standard per-thread variables.  */
enum __hurd_threadvar_index
  {
    _HURD_THREADVAR_MIG_REPLY,  /* Reply port for MiG user stub functions.  */
    _HURD_THREADVAR_ERRNO,  /* `errno' value for this thread.  */
    _HURD_THREADVAR_SIGSTATE,   /* This thread's `struct hurd_sigstate'.  */
    _HURD_THREADVAR_DYNAMIC_USER, /* Dynamically-assigned user variables.  */
    _HURD_THREADVAR_MALLOC, /* For use of malloc.  */
    _HURD_THREADVAR_DL_ERROR,   /* For use of -ldl and dynamic linker.  */
    _HURD_THREADVAR_RPC_VARS,   /* For state of RPC functions.  */
    _HURD_THREADVAR_LOCALE, /* For thread-local locale setting.  */
    _HURD_THREADVAR_CTYPE_B,    /* Cache of thread-local locale data.  */
    _HURD_THREADVAR_CTYPE_TOLOWER, /* Cache of thread-local locale data.  */
    _HURD_THREADVAR_CTYPE_TOUPPER, /* Cache of thread-local locale data.  */
    _HURD_THREADVAR_MAX     /* Default value for __hurd_threadvar_max.  */
  };


#ifndef _HURD_THREADVAR_H_EXTERN_INLINE
#define _HURD_THREADVAR_H_EXTERN_INLINE __extern_inline
#endif

/* Return the location of the value for the per-thread variable with index
   INDEX used by the thread whose stack pointer is SP.  */

extern unsigned long int *__hurd_threadvar_location_from_sp
  (enum __hurd_threadvar_index __index, void *__sp);
_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location_from_sp (enum __hurd_threadvar_index __index,
                   void *__sp)
{
  unsigned long int __stack = (unsigned long int) __sp;
  return &((__stack >= __hurd_sigthread_stack_base &&
        __stack < __hurd_sigthread_stack_end)
       ? __hurd_sigthread_variables
       : (unsigned long int *) ((__stack & __hurd_threadvar_stack_mask) +
                    __hurd_threadvar_stack_offset))[__index];
}

#include <machine-sp.h>     /* Define __thread_stack_pointer.  */

/* Return the location of the current thread's value for the
   per-thread variable with index INDEX.  */

extern unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index) __THROW
     /* This declaration tells the compiler that the value is constant
    given the same argument.  We assume this won't be called twice from
    the same stack frame by different threads.  */
     __attribute__ ((__const__));

_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index)
{
  return __hurd_threadvar_location_from_sp (__index,
                        __thread_stack_pointer ());
}

看到这里调用流程已经很清晰了
errno ---> __errno_location ---->__hurd_threadvar_location ----> __hurd_threadvar_location_from_sp
那__hurd_threadvar_location_from_sp传入的第二个参数是什么意思呢?看源码:
glibc/sysdeps/generic/machine-sp.h

/* Return the current stack pointer.  */

#ifndef _EXTERN_INLINE
#define _EXTERN_INLINE __extern_inline
#endif

_EXTERN_INLINE void *
__thread_stack_pointer (void)
{
  register void *__sp__ ("{STACK-POINTER}");
  return __sp__;
}

这函数没有看明白,不过看函数注释

Return the current stack pointer.

应该返回的是当前线程的栈指针。

再来看__hurd_threadvar_location_from_sp函数,该函数返回一个线程栈地址中的一个特定偏移地址,至此,每个线程拥有自己独立的全局errno。

看到一篇讲的很好文章

相关文章

网友评论

      本文标题:errno详解

      本文链接:https://www.haomeiwen.com/subject/ahemmctx.html