Linux内核网络协议栈学习笔记(一)--分析glibc2.30中的socket函数调用过程

glibc\sysdeps\unix\sysv\linux\socket.c

此文件中定义了socket函数

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  return SOCKETCALL (socket, fd, type, domain);
#endif
}
libc_hidden_def (__socket)
weak_alias (__socket, socket) //给__socket起了个别名 socket

首先给出一个进行socket调用的例子:

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

linux网络协议栈从BSD socket layer开始就完全是内核部分,所以linux的网络编程接口在应用层几乎什么也没有做,例如这个socket函数的调用,仅仅在glibc中提供了一个系统调用的接口,下一步跟进SOCKETCALL宏定义,来分析工作模式(当然在mach系统下,socket函数的调用和linux系统下的工作方式区别很大,但目前只专注于分析linux的网络协议栈)

\glibc\sysdeps\unix\sysv\linux\socketcall.h

#define SOCKETCALL(name, args...)					\
  ({									\
    long int sc_ret = __SOCKETCALL (SOCKOP_##name, args);		\
    sc_ret;								\
  })

发现在代码块中又调用另一个__SOCKETCALL (SOCKOP_##name, args)并返回sc_ret
继续跟进__SOCKETCALL会发现下列的一系列定义

#define __SOCKETCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n
#define __SOCKETCALL_NARGS(...) \
  __SOCKETCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,)
#define __SOCKETCALL_CONCAT_X(a,b)     a##b
#define __SOCKETCALL_CONCAT(a,b)       __SOCKETCALL_CONCAT_X (a, b)
#define __SOCKETCALL_DISP(b,...) \
  __SOCKETCALL_CONCAT (b,__SOCKETCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

#define __SOCKETCALL(...) __SOCKETCALL_DISP (__SOCKETCALL, __VA_ARGS__)

#define __SOCKETCALL1(name, a1) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [1]) { (long int) (a1) }))
#define __SOCKETCALL2(name, a1, a2) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [2]) { (long int) (a1), (long int) (a2) }))
#define __SOCKETCALL3(name, a1, a2, a3) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [3]) { (long int) (a1), (long int) (a2), (long int) (a3) }))
#define __SOCKETCALL4(name, a1, a2, a3, a4) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [4]) { (long int) (a1), (long int) (a2), (long int) (a3), \
                       (long int) (a4) }))
#define __SOCKETCALL5(name, a1, a2, a3, a4, a5) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [5]) { (long int) (a1), (long int) (a2), (long int) (a3), \
                       (long int) (a4), (long int) (a5) }))
#define __SOCKETCALL6(name, a1, a2, a3, a4, a5, a6) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [6]) { (long int) (a1), (long int) (a2), (long int) (a3), \
                       (long int) (a4), (long int) (a5), (long int) (a6) }))

所以翻译__SOCKETCALL(SOCKOP_#name, args)可以得到:(按步骤)
1.__SOCKETCALL_DISP (__SOCKETCALL, SOCKOP__socket,args)
2.__SOCKETCALL_CONCAT (__SOCKETCALL,__SOCKETCALL_NARGS( SOCKOP__socket,fd, type, domain))( SOCKOP#name,args)
3.翻译__SOCKETCALL_NARGS
__SOCKETCALL_CONCAT (__SOCKETCALL,__SOCKETCALL_NARGS_X (SOCKOP__socket,fd, type, domain,7,6,5,4,3,2,1,0,))( SOCKOP__socket,fd, type, domain)
4.翻译__SOCKETCALL_NARGS_X
__SOCKETCALL_CONCAT (__SOCKETCALL,3)( SOCKOP__socket,fd, type, domain)
5.__SOCKETCALL_CONCAT_X (__SOCKETCALL,3)( SOCKOP__socket,fd, type, domain)
6.翻译__SOCKETCALL_CONCAT_X
__SOCKETCALL3(SOCKOP_socket,fd,type,domain)

这个时候代入我们的例子
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
其中:

#define AF_INET PF_INET
#define PF_INET 2

enum __socket_type
{
	SOCK_STREAM = 1,
#define SOCK_STREAM SOCK_STREAM
	...
}

enum {
	IPPROTO_TCP = 6,
#define IPPROTO_TCP IPPROTO_TCP
	...
}

#define SOCKOP_socket 1

这些定义都比较好找就不提供源代码位置了

最后得到
__SOCKETCALL3(1,2,1,6)
然后代入

#define __SOCKETCALL3(name, a1, a2, a3) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [3]) { (long int) (a1), (long int) (a2), (long int) (a3) }))

最后翻译得:

INLINE_SYSCALL(socketcall,2,1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)}))

别忘了这一步是从SOCKETCALL 得来的,也就是说:

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  return INLINE_SYSCALL(socketcall,2,1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)}))
#endif
}

现在应用层socket变成了这个样子
我们跟进INLINE_SYSCALL,到了这步,就离系统调用更加近了

glibc\sysdeps\unix\sysv\linux\i386\sysdep.h

#undef INLINE_SYSCALL
#if IS_IN (libc)
# define INLINE_SYSCALL(name, nr, args...) \
  ({									      \
    unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args);	      \
    __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar, ))		      \
    ? __syscall_error (-INTERNAL_SYSCALL_ERRNO (resultvar, ))		      \
    : (int) resultvar; })
#else
# define INLINE_SYSCALL(name, nr, args...) \
  ({									      \
    unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args);	      \
    if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar, )))	      \
      {									      \
	__set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, ));		      \
	resultvar = 0xffffffff;						      \
      }									      \
    (int) resultvar; })
#endif

对于#if SI_IN (libc)
官方的解释是这样的:to detect what component is being compiled

因为两个定义并没有太大的区别,而且皆借用了INTERNAL_SYSCALL,并返回宏定义的值
所以就按照上面那个宏去分析

现在应用层socket长这样:

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  return INTERNAL_SYSCALL (socketcall, , 2, 1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)}))#endif
}

分析INTERNAL_SYSCALL

\glibc\sysdeps\unix\sysv\linux\i386

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

我们先保留宏的样子,先翻译内部的INTERNAL_SYSCALL_MAIN_##nr (name, err, args)
即:
INTERNAL_SYSCALL_MAIN_2(socketcall, 1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)})
继续:
#define INTERNAL_SYSCALL_MAIN_2(name, err, args…)
INTERNAL_SYSCALL_MAIN_INLINE(name, err, 2, args)
得到:
INTERNAL_SYSCALL_MAIN_INLINE(socketcall, 2, 1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)})
所以此时函数块中的样子为:

({									      \
    register unsigned int resultvar;					      \
    INTERNAL_SYSCALL_MAIN_INLINE(socketcall,, 2, 1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)});			      \
    (int) resultvar; })

现在再来整理一下:
现在应用层长这样:

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  register unsigned int resultvar;
  INTERNAL_SYSCALL_MAIN_INLINE(socketcall,, 2, 1,((long int [3]){(long int)(2),(long int)(1),(long int)(6)});
  return (int)resultvar;
#endif
}

继续分析INTERNAL_SYSCALL_MAIN_INLINE:

\glibc\sysdeps\unix\sysv\linux\i386\sysdep.h

#if I386_USE_SYSENTER
# ifdef OPTIMIZE_FOR_GCC_5
#  ifdef PIC
#   define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    LOADREGS_##nr(args)							\
    asm volatile (							\
    "call *%%gs:%P2"							\
    : "=a" (resultvar)							\
    : "a" (__NR_##name), "i" (offsetof (tcbhead_t, sysinfo))		\
      ASMARGS_##nr(args) : "memory", "cc")
#   define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									\
    register unsigned int resultvar;					\
    LOADREGS_##nr(args)							\
    asm volatile (							\
    "call *%%gs:%P2"							\
    : "=a" (resultvar)							\
    : "a" (name), "i" (offsetof (tcbhead_t, sysinfo))			\
      ASMARGS_##nr(args) : "memory", "cc");				\
    (int) resultvar; })
#  else
#   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 INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									\
    register unsigned int resultvar;					\
    LOADREGS_##nr(args)							\
    asm volatile (							\
    "call *_dl_sysinfo"							\
    : "=a" (resultvar)							\
    : "a" (name) ASMARGS_##nr(args) : "memory", "cc");			\
    (int) resultvar; })
#  endif
# else /* GCC 5  */
#  ifdef PIC
#   define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "movl %1, %%eax\n\t"						      \
    "call *%%gs:%P2\n\t"						      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "i" (__NR_##name), "i" (offsetof (tcbhead_t, sysinfo))		      \
      ASMFMT_##nr(args) : "memory", "cc")
#   define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									      \
    register unsigned int resultvar;					      \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "call *%%gs:%P2\n\t"						      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "0" (name), "i" (offsetof (tcbhead_t, sysinfo))			      \
      ASMFMT_##nr(args) : "memory", "cc");				      \
    (int) resultvar; })
#  else
#   define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "movl %1, %%eax\n\t"						      \
    "call *_dl_sysinfo\n\t"						      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc")
#   define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									      \
    register unsigned int resultvar;					      \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "call *_dl_sysinfo\n\t"						      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "0" (name) ASMFMT_##nr(args) : "memory", "cc");			      \
    (int) resultvar; })
#  endif
# endif /* GCC 5  */
#else
# ifdef OPTIMIZE_FOR_GCC_5
#  define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    LOADREGS_##nr(args)							\
    asm volatile (							\
    "int $0x80"								\
    : "=a" (resultvar)							\
    : "a" (__NR_##name) ASMARGS_##nr(args) : "memory", "cc")
#  define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									\
    register unsigned int resultvar;					\
    LOADREGS_##nr(args)							\
    asm volatile (							\
    "int $0x80"								\
    : "=a" (resultvar)							\
    : "a" (name) ASMARGS_##nr(args) : "memory", "cc");			\
    (int) resultvar; })
# else /* GCC 5  */
#  define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "movl %1, %%eax\n\t"						      \
    "int $0x80\n\t"							      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc")
#  define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({									      \
    register unsigned int resultvar;					      \
    EXTRAVAR_##nr							      \
    asm volatile (							      \
    LOADARGS_##nr							      \
    "int $0x80\n\t"							      \
    RESTOREARGS_##nr							      \
    : "=a" (resultvar)							      \
    : "0" (name) ASMFMT_##nr(args) : "memory", "cc");			      \
    (int) resultvar; })
# endif /* GCC 5  */
#endif

可以看到这里边有很多对此宏的定义
emmmmm我还没有搞明白应该是使用哪一个。。。
所以先写到这里

但是可以确定的是,宏定义的汇编代码无非都是进入内核的软件中断指令,有利用sysenter去进入内核的,有利用系统调用号int $0x80进入内核的,这两者的区别在于cpu使用的资源不尽相同,int $0x80是通过中断门进行内核陷入,将在下一篇博客进行解释(开新坑)

这篇博客仅仅将应用层的socket函数的宏定义翻译了一遍,终于找到了他的汇编系统调用部分
可以知道socket的应用层接口没有做任何实质性的任务,仅仅将系统调用进行了封装

to be continue

发布了7 篇原创文章 · 获赞 1 · 访问量 703

猜你喜欢

转载自blog.csdn.net/qq_41957544/article/details/102528948