日期:2014-05-16  浏览次数:20763 次

Linux协议栈代码阅读笔记(一)

Linux协议栈代码阅读笔记(一)
(基于linux-2.6.21.7)

(一)用户态通过诸如下面的C库函数访问协议栈服务

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
……

(二)上述C库函数如何与内核交互
C库代码准备好相应的工作后(例如,设置系统调用号啦、参数构造啦、栈啦、寄存器设置啦),通过系统调用指令,进入内核态。从内核返回后,C库函数再做相应的善后工作,然后将结果返回给用户程序。

这部分代码,不同架构的处理器,有不同的实现。
可以参考Glibc的源码。
下面以X86为例,简要描述一下这个过程。
另外,后续的内容,如无特殊说明,均是针对X86架构。

对于X86架构,一般是通过“int  $0x80”指令进入内核,即触发128号中断。
内核中断向量表的定义如下(源码文件arch\i386\kernel\ Traps.c):

struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };

函数trap_init(源码文件arch\i386\kernel\ Traps.c)对此表进行了初始化。
其中,对128号中断的初始化方式为:
set_system_gate(SYSCALL_VECTOR,&system_call);
SYSCALL_VECTOR宏的值为0x80,即128。

因此,128号中断,即对应中断向量表的第128个条目,其中断服务程序为system_call这段代码。
system_call这段代码,是用汇编实现的。
其代码在arch\i386\kernel\entry.S中。
这个代码,主要是根据系统调用号,索引系统调用表中的一个条目进行执行。

(三)内核态如何处理用户的网络通讯请求
上一步,C库发起了系统调用,进入了内核128号中断,即系统调用软中断。
128号中断处理程序,根据系统调用号,进入系统调用表的相应表目。系统调用表如下,每个表目是一个函数指针。(源码文件:arch\i386\kernel\ syscall_table.S)

ENTRY(sys_call_table)
 .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
 .long sys_exit
 .long sys_fork
 .long sys_read
 .long sys_write
 .long sys_open  /* 5 */
 .long sys_close
 .long sys_waitpid
 .long sys_creat
 .long sys_link
 .long sys_unlink /* 10 */
 .long sys_ni_syscall /* old lock syscall holder */
 …
 .long sys_statfs
 .long sys_fstatfs /* 100 */
 .long sys_ioperm
.long sys_socketcall
 .long sys_syslog
 …
 .long sys_tee   /* 315 */
 .long sys_vmsplice
 .long sys_move_pages
 .long sys_getcpu
 .long sys_epoll_pwait

对于上述的几个socket库函数,全部对应同一个系统调用,即102号系统调用,即sys_socketcall函数。

sys_socketcall函数如何处理用户的socket请求
所有的socket相关的C库函数,如socket、bind、connect、listen、accept、send、recv、sendto、sendmsg等,全部都属于同一个系统调用(即102号系统调用),全部由这一个函数处理。
此函数的代大致如下(源码文件net\Socket.c)
long sys_socketcall(int call, unsigned long __user *args)
{
    ……
 switch (call) {
 case SYS_SOCKET:
  err = sys_socket(a0, a1, a[2]);
  break;
 case SYS_BIND:
  err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
  break;
 case SYS_CONNECT:
  err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
  break;
 case SYS_LISTEN:
  err = sys_listen(a0, a1);
  break;
 case SYS_ACCEPT:
  err =
      sys_accept(a0, (struct sockaddr __user *)a1,
          (int __user *)a[2]);
  break;
 case SYS_GETSOCKNAME:
  err =
      sys_getsockname(a0, (struct sockaddr __user *)a1,
        (int __user *)a[2]);
  break;
 case SYS_GETPEERNAME:
  err =
      sys_getpeername(a0, (struct sockaddr __user *)a1,
        (int __user *)a[2]);
  break;
 case SYS_SOCKETPAIR:
  err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
  break;
 case SYS_SEND:
  err = sys_send(a0, (void __user *)a1, a[2], a[3]);
  break;
 case SYS_SENDTO:
  err = sys_sendto(a0, (void __user *)a1, a[2], a[3],
     (struct sockaddr __user *)a[4], a