五、初始化 sk
分配完成 sk 后,另一个重要的功能就是初始化它,sk 的成员相当复杂,其主要的初始化工作是在函数 sock_init_data()中完成的:
void sock_init_data(struct socket *sock, struct sock *sk)
{
/* 初始化其三个队列 */
skb_queue_head_init(&sk->sk_receive_queue);
skb_queue_head_init(&sk->sk_write_queue);
skb_queue_head_init(&sk->sk_error_queue);
sk->sk_send_head = NULL;
/* 初始化数据包发送定时器 */
init_timer(&sk->sk_timer);
sk->sk_allocation = GFP_KERNEL;
sk->sk_rcvbuf = sysctl_rmem_default;
sk->sk_sndbuf = sysctl_wmem_default;
sk->sk_state = TCP_CLOSE;
/* 指向对应的 socket 结构 */
sk->sk_socket = sock;
sock_set_flag(sk, SOCK_ZAPPED);
if(sock)
{
sk->sk_type = sock->type;
sk->sk_sleep = &sock->wait;
/* 回指对应的 scok 结构 */
sock->sk = sk;
} else
sk->sk_sleep = NULL;
rwlock_init(&sk->sk_dst_lock);
rwlock_init(&sk->sk_callback_lock);
sk->sk_state_change = sock_def_wakeup;
sk->sk_data_ready = sock_def_readable;
sk->sk_write_space = sock_def_write_space;
sk->sk_error_report = sock_def_error_report;
sk->sk_destruct = sock_def_destruct;
sk->sk_sndmsg_page = NULL;
sk->sk_sndmsg_off = 0;
sk->sk_peercred.pid = 0;
sk->sk_peercred.uid = -1;
sk->sk_peercred.gid = -1;
sk->sk_write_pending = 0;
sk->sk_rcvlowat = 1;
sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
sk->sk_stamp.tv_sec = -1L;
sk->sk_stamp.tv_usec = -1L;
atomic_set(&sk->sk_refcnt, 1);
}
sock 结构中,有三个重要的双向队列,分别是 sk_receive_queue、sk_write_queue和 sk_error_queue。从它们的名字就可以看出来其作用了。 队列并非采用通用的 list_head 来维护,而是使用 skb_buffer 队列:
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen;
spinlock_t lock;
};
这样,队列中指向的每一个 skb_buffer,就是一个数据包,分别是接收、发送和投递错误。
剩余的就是初始化其它成员变量了。后面再来专门分析这些成员的作用。
inet_create 函数中,除了初始化 sk 成员的值,还有一部份代码,是初始化一个 inet的东东:
inet = inet_sk(sk);
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_index = 0;
inet->mc_list = NULL;[/code]
inet 是一个 struct inet_sock 结构类型,来看它的定义:
struct inet_sock {
/* sk and pinet6 has to be the first two members of inet_sock */
struct sock sk;
……
}
只留意它的第一个成员就足够了。
我们说 sock 是面向用户态调用,而 sk 是面向内核驱动调用的,那 sk 是如何与协议栈交互的呢?对于每一个类型的协议,为了与 sk 联系起来,都定义了一个 struct XXX_sock 结构,XXX是协议名,例如:
struct tcp_sock {
/* inet_sock has to be the first member of tcp_sock */
struct inet_sock inet;
int tcp_header_len; /* Bytes of tcp header to send */
……
}
struct udp_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
int pending; /* Any pending frames ? */
unsigned int corkflag; /* Cork is required */
__u16 encap_type; /* Is this an Encapsulation socket? */
/*
* Following member retains the infomation to create a UDP header
* when the socket is uncorked.
*/
__u16 len; /* total length of pending frames */
};
struct raw_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
struct icmp_filter filter;
};
很明显,它们的结构定构是“af_inet一般属性+自己的私有属性”,因为它们的第一个成员总是 inet。
呵呵,现在回头来照一下起初在 af_inet.c 中,封装协议注册的时候,size成员,对于 tcp 而言:
.obj_size = sizeof(struct tcp_sock),
其它协议类似。
以 obj_size 来确定每个 slab 缓存项分配的大小,所以,我们就可说,每次申请分配的,实际上是一个 struct XXX_sock 结构大小的结构。因为都是定义于上层结构的第一个成员,可以使用强制类型转换来使用这块分配的内存空间。例如:
inet = inet_sk(sk);
static inline struct inet_sock *inet_sk(const struct sock *sk)
{
return (struct inet_sock *)sk;
}
struct tcp_sock *tp = tcp_sk(sk);
static inline struct tcp_sock *tcp_sk(const struct sock *sk)
{
return (struct tcp_sock *)sk;
}
OK,inet_create()运行完,一个 socket 套接字基本上就创建完毕了,剩下的就是与文件系统挂钩,回到最初的 sys_socket()函数中来,它在调用完 sock_create()后,紧接着调用 sock_map_fd()函数:
int sock_map_fd(struct socket *sock)
{
int fd;
struct qstr this;
char name[32];
/*
* Find a file descriptor suitable for return to the user.
*/
fd = get_unused_fd();
if (fd >= 0) {
struct file *file = get_empty_filp();
if (!file) {
put_unused_fd(fd);
fd = -ENFILE;
goto out;
}
sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino);
this.name = name;
this.len = strlen(name);
this.hash = SOCK_INODE(sock)->i_ino;
file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
if (!file->f_dentry) {
put_filp(file);
put_unused_fd(fd);
fd = -ENOMEM;
goto out;
}
file->f_dentry->d_op = &sockfs_dentry_operations;
d_add(file->f_dentry, SOCK_INODE(sock));
file->f_vfsmnt = mntget(sock_mnt);
file->f_mapping = file->f_dentry->d_inode->i_mapping;
sock->file = file;
file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;
file->f_mode = FMODE_READ | FMODE_WRITE;
file->f_flags = O_RDWR;
file->f_pos = 0; fd_install(fd, file);
}
out:
return fd;
}
这个函数的核心思想,在一开始,就已经分析过了。从进程的角度来讲,一个socket 套接字就是一个特殊的,已打开的文件。前面分配好一个 socket 后,这里要做的就是将它与文件系统拉上亲戚关系。首先获取一个空闲的文件描述符号和 file 结构。然后在文件系统中分配一个目录项(d_alloc),使 其指向已经分配的 inode 节点(d_add),然后把其目录项挂在 sockfs 文件系统的根目录之下。并且把目录项的指针 d_op设置成指向 sockfs_dentry_operati,这个数据结构通过函数指针提供他与文件路径有关的操作:
static struct dentry_operations sockfs_dentry_operations = {
.d_delete = sockfs_delete_dentry,
};
最后一步,就是将 file 结构中的 f_op 和 sock 结构中的 i_fop 都指向 socket_file_ops,它是一个函数指针集,指向了 socket面向文件系统的用户态调用的一些接口函数:
static struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.readv = sock_readv,
.writev = sock_writev,
.sendpage = sock_sendpage
};
OK,到这里,整个 socket 套接字的创建工作,就宣告完成了。
写到这里,可以为 socket 的创建下一个小结了:
1、所谓创建socket,对内核而言,最重要的工作就是分配 sock 与 sk;
2、 sock 面向上层系统调用,主要是与文件系统交互。通过进程的 current 指针的 files,结合创建 socket时返回的文件描符述,可以找到内 核中对应的 struct file,再根据 file的 f_dentry可以找到对应的目录项,而目录项 struct dentry 中,有 d_inode 指针,指向与 sock 封装在一起的 inode。sock 又与 sk指针互指,一一对应。在这串结构中,有两个重要的函数集 指针,一个是文件系统 struct file 中的f_op指针,它指向了,对应的用户态调用的read,write等操调用,但不支持open,另一个是struct socket结构,即 sock 的ops 指针,它在 inet_create()中被置为 sock->ops = answer->ops; 指向具体协议类型的 ops;例如,inet_stream_ops、inet_dgram_ops 或者是 inet_sockraw_ops 等等。它用来支持上层的 socket 的其它 API 调用。
3、sk 面向内核协议栈,协议栈与它的接口数据结构是 struct protoname_sock,该结构中包含了一般性的 inet 结构和自己的私有成员,struct inet_sock 的第一个成员就是一个 sk 指针,而分配的 sk,实际上空间大小是 struct protoname_sock,所以,这三者可以通过强制类型转换来获取需要的指针。