天天看點

glibc源碼分析之系統調用(二)

前文已經向大家介紹了用腳本封裝系統調用的方法。在本文中,我将向大家介紹使用.c檔案封裝系統調用的方法。

使用.c檔案封裝系統調用一般出于以下兩個原因:一,系統調用已過時,可以使用新的系統調用替換舊的系統調用。系統調用的語義不變。二,封裝後的函數改變了系統調用的語義。

stat系統調用的封裝便是原因一的一個例子。

stat系統調用是106号系統調用。使用stat系統調用可以擷取檔案的屬性,但是擷取的屬性值都是32位的,這對于現在的64位檔案系統來說是有問題的。是以linux又定義了stat64系統調用,用于擷取64位檔案屬性。是以,Linux核心中關于stat的系統調用有兩個,且stat64系統調用可以替換stat系統調用。而在glibc中,stat系統調用也是使用stat64來封裝的。

讓我們觀察stat系統調用的封裝代碼:

#undef stat
int
attribute_hidden
__stat (const char *file, struct stat *buf)
{
  return __xstat (_STAT_VER, file, buf);
}

weak_hidden_alias (__stat, stat)
           

stat函數調用了__xstat函數完成函數的封裝。

int
__xstat (int vers, const char *name, struct stat *buf)
{
  int result;

  if (vers == _STAT_VER_KERNEL)
    return INLINE_SYSCALL (stat, , name, buf);

  {
    struct stat64 buf64;

    INTERNAL_SYSCALL_DECL (err);
    result = INTERNAL_SYSCALL (stat64, err, , name, &buf64);
    if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result, err)))
      return INLINE_SYSCALL_ERROR_RETURN_VALUE (INTERNAL_SYSCALL_ERRNO (result,
                                    err));
    else
      return __xstat32_conv (vers, &buf64, buf);
  }
}
           
#undef INTERNAL_SYSCALL_DECL
#define INTERNAL_SYSCALL_DECL(err) do { } while (0)
           
# define __glibc_unlikely(cond) (cond)
           
#undef INTERNAL_SYSCALL_ERROR_P
#define INTERNAL_SYSCALL_ERROR_P(val, err) \
  ((unsigned int) (val) >= u)
           

如果傳回值大于0xfffff001u,則表示系統調用出錯了。

#undef INTERNAL_SYSCALL_ERRNO
#define INTERNAL_SYSCALL_ERRNO(val, err)    (-(val))
           
#define INLINE_SYSCALL_ERROR_RETURN_VALUE(resultvar) \
  __syscall_error (-(resultvar))
           

調用__syscall_error 處理出錯。

如果參數vers為_STAT_VER_KERNEL則直接調用stat系統調用,但此處vers為_STAT_VER。

__xstat先使用INTERNAL_SYSCALL宏調用了stat64系統調用,然後判斷是否出錯,如果出錯,則傳回-1,并設定errno。如果執行正确,則調用__xstat32_conv函數,轉換資料類型并傳回。

INTERNAL_SYSCAL宏定義如下:

#define INTERNAL_SYSCALL(name, err, nr, args...) \
  ({                                          \
    register unsigned int resultvar;                          \
    INTERNAL_SYSCALL_MAIN_##nr (name, err, args);                 \
    (int) resultvar; })
           

根據nr系統調用個數的不同調用不同的宏。

參數為0,調用INTERNAL_SYSCALL_MAIN_0宏

參數為1,調用INTERNAL_SYSCALL_MAIN_1宏

參數為2,調用INTERNAL_SYSCALL_MAIN_2宏

參數為3,調用INTERNAL_SYSCALL_MAIN_3宏

參數為4,調用INTERNAL_SYSCALL_MAIN_4宏

參數為5,調用INTERNAL_SYSCALL_MAIN_5宏

參數為6,調用INTERNAL_SYSCALL_MAIN_6宏

#undef INTERNAL_SYSCALL
#define INTERNAL_SYSCALL_MAIN_0(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
#define INTERNAL_SYSCALL_MAIN_1(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
#define INTERNAL_SYSCALL_MAIN_2(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
#define INTERNAL_SYSCALL_MAIN_3(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
#define INTERNAL_SYSCALL_MAIN_4(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
#define INTERNAL_SYSCALL_MAIN_5(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
# define INTERNAL_SYSCALL_MAIN_6(name, err, args...) \
    INTERNAL_SYSCALL_MAIN_INLINE(name, err, , args)
           

調用INTERNAL_SYSCALL_MAIN_INLINE宏

#   define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    LOADREGS_##nr(args)                         \
    asm volatile (                          \
    "call *_dl_sysinfo"                         \
    : "=a" (resultvar)                          \
    : "a" (__NR_##name) ASMARGS_##nr(args) : "memory", "cc")
           

使用嵌入式彙編,封裝系統調用。

# define LOADARGS_1
# define LOADARGS_2
# define LOADARGS_3
# define LOADARGS_4
# define LOADARGS_5
           
# define LOADREGS_0()
# define ASMARGS_0()
# define LOADREGS_1(arg1) \
    LOADREGS_0 ()
# define ASMARGS_1(arg1) \
    ASMARGS_0 (), "b" ((unsigned int) (arg1))
# define LOADREGS_2(arg1, arg2) \
    LOADREGS_1 (arg1)
# define ASMARGS_2(arg1, arg2) \
    ASMARGS_1 (arg1), "c" ((unsigned int) (arg2))
# define LOADREGS_3(arg1, arg2, arg3) \
    LOADREGS_2 (arg1, arg2)
# define ASMARGS_3(arg1, arg2, arg3) \
    ASMARGS_2 (arg1, arg2), "d" ((unsigned int) (arg3))
# define LOADREGS_4(arg1, arg2, arg3, arg4) \
    LOADREGS_3 (arg1, arg2, arg3)
# define ASMARGS_4(arg1, arg2, arg3, arg4) \
    ASMARGS_3 (arg1, arg2, arg3), "S" ((unsigned int) (arg4))
# define LOADREGS_5(arg1, arg2, arg3, arg4, arg5) \
    LOADREGS_4 (arg1, arg2, arg3, arg4)
# define ASMARGS_5(arg1, arg2, arg3, arg4, arg5) \
    ASMARGS_4 (arg1, arg2, arg3, arg4), "D" ((unsigned int) (arg5))
# define LOADREGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \
    register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6); \
    LOADREGS_5 (arg1, arg2, arg3, arg4, arg5)
# define ASMARGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \
    ASMARGS_5 (arg1, arg2, arg3, arg4, arg5), "r" (_a6)
           

設定寄存器的值。

系統調用個數為0,不設定寄存器。

系統調用個數為1,設定ebx寄存器。

系統調用個數為2,設定ebx,ecx寄存器。

系統調用個數為3,設定ebx,ecx,edx寄存器。

系統調用個數為4,設定ebx,ecx,edx,esi寄存器。

系統調用個數為5,設定ebx,ecx,edx,esi,edi寄存器。

系統調用個數為6,設定ebx,ecx,edx,esi,edi,ebp寄存器。

brk系統調用的封裝便是原因二的一個例子。

void *__curbrk = ;

int
__brk (void *addr)
{
  INTERNAL_SYSCALL_DECL (err);
  void *newbrk = (void *) INTERNAL_SYSCALL (brk, err, , addr);
  __curbrk = newbrk;
  if (newbrk < addr)
    return INLINE_SYSCALL_ERROR_RETURN_VALUE (ENOMEM);
  return ;
}

weak_alias (__brk, brk)
           

brk函數在調用brk系統調用的同時設定了全局變量__curbrk 。