- 论坛徽章:
- 0
|
今天我们继续内核中的TCP/IP的socket的分析,同样按照我们第一节socket初始化(
http://blog.chinaunix.net/u2/64681/showart.php?id=1680618
)那节的分析流程图和服务器端的练习代码继续我们的分析过程,我们看到声明了二个用于地址的结构变量,下边我们会看到这个地址数据结构的定义,下边练习程序要执行的路线是对socket地址绑定,我们来分析一下这个过程,我们看到在那节的练习中通过下面的代码来绑定地址给socket
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
首先也是进入系统调用的总入口处sys_socketcall()系统调用处
case SYS_BIND:
err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
break;
很明显我们要进入sys_bind()函数去执行绑定,朋友们注意我们分析的内核版本是2.6.26。
sys_socketcall()-->sys_bind()
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err, fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, address);
if (err >= 0) {
err = security_socket_bind(sock,
(struct sockaddr *)address,
addrlen);
if (!err)
err = sock->ops->bind(sock,
(struct sockaddr *)
address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
这个函数的开头要找到上一节我们创建的socket。这是通过sockfd_lookup_light()来找到的,我们分析一下这个函数
sys_socketcall()-->sys_bind()-->sockfd_lookup_light()
static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
struct file *file;
struct socket *sock;
*err = -EBADF;
file = fget_light(fd, fput_needed);
if (file) {
sock = sock_from_file(file, err);
if (sock)
return sock;
fput_light(file, *fput_needed);
}
return NULL;
}
这里我们看到参数fd这实际上是从应用程序中传递过来的server_sockfd,那是从c库到系统调用一路传过来的,而server_sockfd是我们创建socket时的文件标识号,所以这里调用fget_light()从当前进程的files_struct结构中找到我们创建socket时的file文件指针并增加他的使用计数,关于找到file的指针,请朋友们参考相关的文件系统资料,我们这里重点围绕着socket的分析,我们看到接着调用sock_from_file函数,从名称上可以看出是根据file找到已经创建的socket
sys_socketcall()-->sys_bind()-->sockfd_lookup_light()-->sock_from_file()
static struct socket *sock_from_file(struct file *file, int *err)
{
if (file->f_op == &socket_file_ops)
return file->private_data; /* set in sock_map_fd */
*err = -ENOTSOCK;
return NULL;
}
这里我们看一下上一节socket的创建(
http://blog.chinaunix.net/u2/64681/showart.php?id=1685664
)文章的最后我们提到了非常重要的是file结构中的private_data指向了服务器端的socket,既然这里要对socket进行地址绑定所以首先是重新获取服务器端创建的socket
file->private_data = sock;
这样上面的找到socket的函数非常容易理解了,就是找到file是最关键的,而file结构中有指针指向我们创建的socket,可能有朋友被上面这里的名称sock所迷惑,这个结构变量是socket不是sock,内核中声名的这里sock的结构变量名为sk。回到sys_bind函数中,我们找到了socket下边继续往下看,看到执行了move_addr_to_kernel函数
sys_socketcall()-->sys_bind()-->move_addr_to_kernel()
int move_addr_to_kernel(void __user *uaddr, int ulen, void *kaddr)
{
if (ulen 0 || ulen > MAX_SOCK_ADDR)
return -EINVAL;
if (ulen == 0)
return 0;
if (copy_from_user(kaddr, uaddr, ulen))
return -EFAULT;
return audit_sockaddr(ulen, kaddr);
}
我们又看到了copy_from_user函数,并且kaddr就是kernel address的意思,同理,uaddr就是user address的意思,ulen就是user length的意思,这样copy_from_user的函数的作用我就不用说了,这里函数就是将我们在应用程序中创建的(struct sockaddr *)&server_address结构复制到内核空间里来,有朋友可能对内核空间和用户空间的概念模糊,请查看有关操作系统理论,这里我们看到我们在用户空间中声明的地址转换成了sockaddr结构指针,我们的server_address初始声明为struct sockaddr_in 类型,我们需要看一下相关的结构
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
上面的过程把我们练习中的地址结构变量通过move_addr_to_kernel()拷贝到局部数组变量char address[MAX_SOCK_ADDR]中。MAX_SOCK_ADDR定义为128,我们看一下练习中的声明的struct sockaddr_in server_address
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
typedef unsigned short sa_family_t;
这个结构正象注释中说明的那样用于IP的socket地址使用。而在练习中我们看到
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("192.168.1.1");
server_address.sin_port = 9734;
对照上面的结构我们发现还有一个__pad数组我们没看到对他的赋值和操作,这个数组主要作用填充保障我们的地址数据结构与通用的sockaddr数据结构保持相同的大小。此后我们在应用程序中设置的地址结构的数据都复制到了sys_bind()函数中的数组变量address中了,接着我们看到会执行
sock->ops->bind(sock,(struct sockaddr *) address, addrlen);
在这里应该明白了上面__pad的作用了,确实是为了将我们的数据能够正确对应到sockaddr结构的大小起填充作用的。我们回忆一下昨天讲到的socket的创建过程中对ops钩子结构的操作,在那里将socket的ops通过answer结构变量转接入了inet_stream_ops,所以这里会跳入这个钩子结构去执行,我们先看一下这个结构
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = tcp_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
结构中其他的部分我们暂且不要关心,只注意.bind= inet_bind这一句,也就是说会执行钩子函数inet_bind。篇幅所限,接下一篇
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/64681/showart_1709869.html |
|