OS: redhat linux 5.0;
内核版本: 2.6.18;
之前在写socket 程序时都是简单的调用 socket 函数,不明白这个函数都会调用哪些程序,内核又是如何执行程序的,最近自己也在看 linux内核关于socket部分的程序,大致明白了一些过程,现在写上我对socket创建过程的理解,由于篇幅较大所以会不定时更新,如果自己博客中的东西有什么不对的请指出来,我现在仍然是在一边学习一边记录的过程,所以如果有什么不对的请多多指教,谢谢。
特别感谢 linux 系统内核的无私开源!!!
我们知道在写socket 程序时 socket 程序是这么调用的,
socketfd = socket(AF_INET, SOCK_STREAM, 0);
创建完socket 就可以监听和手法数据了,至于socket 程序时如何调用系统函数,请耐心看后面的文章···
socket函数会调用系统函数 sys_socketcall()
* System call vectors.
*
* Argument checking cleaned up. Saved 20% in size.
* This function doesn't need to set the kernel lock because
* it is set by the callees.
*/
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0,a1;
int err;
if(call<1||call>SYS_RECVMSG)
return -EINVAL;
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
err = audit_socketcall(nargs[call]/sizeof(unsigned long), a);
if (err)
return err;
a0=a[0];
a1=a[1];
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[5]);
break;
case SYS_RECV:
err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
break;
case SYS_RECVFROM:
err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
(struct sockaddr __user *)a[4], (int __user *)a[5]);
break;
case SYS_SHUTDOWN:
err = sys_shutdown(a0,a1);
break;
case SYS_SETSOCKOPT:
err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
break;
case SYS_GETSOCKOPT:
err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
break;
case SYS_SENDMSG:
err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
break;
case SYS_RECVMSG:
err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
break;
default:
err = -EINVAL;
break;
}
return err;
}
参数call是socket的调用号,从linux头文件中我们可以看到 call 对应值的意思
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
由于我们的程序时创建socket 所以sys_socketcall的参数call 值为1 即SYS_SOCKET,
之后
看一下程序中 copy_from_user这个函数,由于linux在内存的访问过程中分为内核态和用户态 这个程序的作用就是将用户态的参数拷贝到内核态来,其参数为nargs,其为需要从内核态复制到用户态的参数,再来看一下nargs的定义。
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
可以看到当call为1时 nargs[1] = AL(3)。
转换一下得到 copy_from_user函数的变形为
if (copy_from_user(a, args, AL(3)))
其中 a是内核的空间, 而 args是参数在用户空间的地址, AL(3) 代表是3个字节的长度,copy_from_user的原型为
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)
{
BUG_ON((long) n < 0);
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else
memset(to, 0, n);
return n;
}
其函数在执行成功时会返回0,失败返回没有被拷贝的字节数,这个函数的详细分析就先不说了, 涉及到很多细节,我现在也没搞清楚呢, 现代过,以后有时间再好好研究研究总之这个函数就是将socket的三个参数从用户空间复制到内核空间。
之后通过switch 来确定程序的走向~~
由于call为1 所以会走到程序的第一个分支 执行sys_socket函数流程为 sys_socketcall->sys_socket,
asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock);
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
此刻程序进入到了比较重要的地方, socket_create这个函数,