您的位置:首页 > 其它

6-socket的实践到内核--Unix中socket的地址绑定 .

2012-12-11 17:42 369 查看
/article/7610481.html

这节开始探讨unix中的socket地址的绑定,昨天我们把socket的创建过程讲述了,socket创建之后必须要与一个地址绑定才能使用,为其“设置电话号码”有了这个“电话号码”,远方的“朋友”socket可以直接打电话给他进行连接了。我们下面看在内核中怎么设置的这个“电话号码”。还是从应用程序界面看起
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

并且我们在第二节服务器的代码中注释过
将“电话号码”赋值给“电话”,即将设定的地址结构与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]);

可以看出他到达了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()函数需要分析一下
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的指针,我们以后还会谈到因为他与文件系统太紧密,我们看到接着调用sock_from_file函数,从名称上可以看出是根据file找到已经创建的socket
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的创建到最后有一句
file->private_data = sock;

这样上面的找到socket的函数非常容易理解了,就是找到file是最关键的,而file结构中有指针指向我们创建的socket,可能有朋友被上面这里的名称sock所迷惑,这个结构变量是socket不是sock,内核中声名的这里sock的结构变量名为sk。回到sys_bind函数中,我们找到了socket下边继续往下看,看到执行了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_un 类型,而这个结构我们上一节看到过,我们需要看一下sockaddr结构
struct sockaddr {

sa_family_t    sa_family;    /* address family, AF_xxx    */

char        sa_data[14];    /* 14 bytes of protocol address    */

};

我们看到结构中sa_data[14]的长度是14个字节,而我们上一节看到的sockaddr_un的结构,我们再贴出来对比
struct sockaddr_un {

sa_family_t sun_family; /* AF_UNIX */

char sun_path[UNIX_PATH_MAX]; /* pathname */

};

#define UNIX_PATH_MAX 108

看到数组sun_path长度是108个“梁山好汉”的个数,但是上一节我们也说过在unix_address结构中声明的这个sockaddr_un变量为
struct unix_address {

atomic_t refcnt;

int len;

unsigned hash;

struct sockaddr_un name[0];

};

name数组的个数为0即节省空间所想,同时我们还在进入复制函数传递过来一个参数addrlen,也就是进入函数中的ulen,由它决定了我们从用户空间复制到内核空间的长度,我们看到是从应用程序中传递过来的server_len,我们看一他的长度在应用程序中我们看到server_len = sizeof(server_address);所以这里复制的长度以sockaddr_un大小为准,我们看一下他复制到了address数组中
char address[MAX_SOCK_ADDR];

#define MAX_SOCK_ADDR    128

这样我们相信,我们应用程序中的变量server_address能够全部装在这个足够大的数组中了。再回到sys_bind函数中,下边是执行关键的
err = sock->ops->bind(sock,

(struct sockaddr *)

address, addrlen);

我们在上一节看到过socket的ops指针是一个proto_ops结构,我们在上一节看到他被赋值为
sock->ops = &unix_stream_ops;

static const struct proto_ops unix_stream_ops = {

.family = PF_UNIX,

.owner = THIS_MODULE,

.release = unix_release,

.bind = unix_bind,

.connect = unix_stream_connect,

.socketpair = unix_socketpair,

.accept = unix_accept,

.getname = unix_getname,

.poll = unix_poll,

.ioctl = unix_ioctl,

.listen = unix_listen,

.shutdown = unix_shutdown,

.setsockopt = sock_no_setsockopt,

.getsockopt = sock_no_getsockopt,

.sendmsg = unix_stream_sendmsg,

.recvmsg = unix_stream_recvmsg,

.mmap = sock_no_mmap,

.sendpage = sock_no_sendpage,

};

从上面的定义中我们可以看到要执行挂在钩子上的函数unix_bind()了,追踪它,这个函数在/net/unix/Af_unix.c的765行处,需要分段分析
static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)

{

struct sock *sk = sock->sk;

struct net *net = sock_net(sk);

struct unix_sock *u = unix_sk(sk);

struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;

struct dentry * dentry = NULL;

struct nameidata nd;

int err;

unsigned hash;

struct unix_address *addr;

struct hlist_head *list;

err = -EINVAL;

if (sunaddr->sun_family != AF_UNIX)

goto out;

if (addr_len==sizeof(short)) {

err = unix_autobind(sock);

goto out;

}

err = unix_mkname(sunaddr, addr_len, &hash);

首先前边要对网域进行一下判断,因为我们要执行的是关于unix域的socket所以会执行这里的检查,addr_len要对其检查是否需要自动分配一个地址给socket,关键的是执行unix_mkname
static int unix_mkname(struct sockaddr_un * sunaddr, int len, unsigned *hashp)

{

if (len <= sizeof(short) || len > sizeof(*sunaddr))

return -EINVAL;

if (!sunaddr || sunaddr->sun_family != AF_UNIX)

return -EINVAL;

if (sunaddr->sun_path[0]) {

/*

* This may look like an off by one error but it is a bit more

* subtle. 108 is the longest valid AF_UNIX path for a binding.

* sun_path[108] doesnt as such exist. However in kernel space

* we are guaranteed that it is a valid memory location in our

* kernel address buffer.

*/

((char *)sunaddr)[len]=0;

len = strlen(sunaddr->sun_path)+1+sizeof(short);

return len;

}

*hashp = unix_hash_fold(csum_partial((char*)sunaddr, len, 0));

return len;

}

这里我们对地址参数再次进行检查,接着将地址转换成以0结尾的字符串,同时最后还能地址进行了hash,即杂凑计算。我们不细看这个杂凑函数了,以后有关重要的时候再研究类似的hash计算。这里先放下,先看我们的重点,继续回到unix_bind函数中
if (err < 0)

goto out;

addr_len = err;

mutex_lock(&u->readlock);

err = -EINVAL;

if (u->addr)

goto out_up;

err = -ENOMEM;

addr = kmalloc(sizeof(*addr)+addr_len, GFP_KERNEL);

if (!addr)

goto out_up;

memcpy(addr->name, sunaddr, addr_len);

addr->len = addr_len;

addr->hash = hash ^ sk->sk_type;

atomic_set(&addr->refcnt, 1);

if (sunaddr->sun_path[0]) {

unsigned int mode;

err = 0;

/*

* Get the parent directory, calculate the hash for last

* component.

*/

err = path_lookup(sunaddr->sun_path, LOOKUP_PARENT, &nd);

if (err)

goto out_mknod_parent;

dentry = lookup_create(&nd, 0);

err = PTR_ERR(dentry);

if (IS_ERR(dentry))

goto out_mknod_unlock;

/*

* All right, let's create it.

*/

mode = S_IFSOCK |

(SOCK_INODE(sock)->i_mode & ~current->fs->umask);

err = mnt_want_write(nd.path.mnt);

if (err)

goto out_mknod_dput;

err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);

mnt_drop_write(nd.path.mnt);

if (err)

goto out_mknod_dput;

mutex_unlock(&nd.path.dentry->d_inode->i_mutex);

dput(nd.path.dentry);

nd.path.dentry = dentry;

addr->hash = UNIX_HASH_SIZE;

}

spin_lock(&unix_table_lock);

if (!sunaddr->sun_path[0]) {

err = -EADDRINUSE;

if (__unix_find_socket_byname(net, sunaddr, addr_len,

sk->sk_type, hash)) {

unix_release_addr(addr);

goto out_unlock;

}

list = &unix_socket_table[addr->hash];

} else {

list = &unix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)];

u->dentry = nd.path.dentry;

u->mnt = nd.path.mnt;

}

err = 0;

__unix_remove_socket(sk);

u->addr = addr;

__unix_insert_socket(list, sk);

out_unlock:

spin_unlock(&unix_table_lock);

out_up:

mutex_unlock(&u->readlock);

out:

return err;

out_mknod_dput:

dput(dentry);

out_mknod_unlock:

mutex_unlock(&nd.path.dentry->d_inode->i_mutex);

path_put(&nd.path);

out_mknod_parent:

if (err==-EEXIST)

err=-EADDRINUSE;

unix_release_addr(addr);

goto out_up;

}

上面的这些代码对于没有文件系统知识的朋友可能感觉到压力很大,我们暂且不跟进了,只想解释一下这段代友总体是根据我们上面初始好的地址在文件系统中创建inode文件节点以及目录项,具体的创建过程根据我们使用的哪种文件系统相关,创建了好文件系统的节点就要与socket相挂钩,这里我们看出给一个socket赋地址就是在内存中创建一个文件系统的节点,并且将socket当成文件进行处理。因此我们就不难理解这个"电话号码"为什么能够被其他的socket所找到并操作。本来想分析文件系统后再来完成这里的socket内容,无奈正好再次看到这些内容,况且项目用到了,所以提前写了一些socket的分析,固然不合初学朋友的胃口,我也尽量说的浅显易懂,可是有些内容还真的是越说越深,越看越多,所以计划以后在完成了其他章节的分析和练习后,再回到这里修改完善这些内核的通信机制,朋友们暂且只要有个大概的印象,凭印象先学习,我支持印象学习法,就象电视演员与电影名星的区别,电视演员的好处是“先混个脸熟”,暂且初学的朋友也与这里的文件系统混个“脸熟”罢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: