一切操作网络的应用都是通过 socket ,所以我们从最上层的 BSD socket 开始。以 socket 函数 accpet 为例。
Linux 系统使用 0x80 软中断支持系统调用,同样 socket 也是使用这个中断从用户态进入到内核态。一般而言,对于每个系统调用都有一个内核如何函 数与之对应,比如代开文件 open ,对应的系统调用入口函数是 sys_open ,同理, read 对应 sys_read , write 对应 sys_write 。但是, socket 的系统调用可不是这样的,它有一个统一的内核入口函数 sys_socketcall 。
贴段代码碎片:
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;
... ...
}
明白了吧,实际上里面还是有条件判断的,最终还是调用对应的 sys_xxx 了。但无论如何,内核入口函数就一个,确实是节省了不少系统调用号。那应用层调用了 accept ,它是如何找到 sys_socketcall 这个函数的呢? call 这个又是怎么被传进去的呢?下面我们一一解决。
首先看看,当应用层调用 accept 时,是如何进入到内核入口函数 sys_socketcall 的。
第一层入口: accepts.S(glibc 里面的 socket 实现 ) /glib/sysdeps/unix/sysv/linux/accept.S
当应用层调用 accept 时,它会先跑到这里面来,下面看看它的部分源码:
#define socket accept
#define NARGS 3 // 表明 accept 系统调用的参数个数