接上篇
初始化與創(chuàng)建,本篇闡述Socket操作和
銷毀兩部分的實(shí)現(xiàn)。
Socket操作
系統(tǒng)調(diào)用read(v)、write(v)是用戶空間讀寫socket的一種方法,為了弄清楚它們是怎么通過VFS將請(qǐng)求轉(zhuǎn)發(fā)到特定協(xié)議的實(shí)現(xiàn),下面以read為例(write同理),并假定文件描述符對(duì)應(yīng)的是IPv4 TCP類型的socket,來跟蹤它的執(zhí)行流程。首先來看下sys_read的代碼,定義在fs/read_write.c中。
1
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
2

{
3
struct file *file;
4
ssize_t ret = -EBADF;
5
int fput_needed;
6
7
file = fget_light(fd, &fput_needed);
8
if (file)
{
9
loff_t pos = file_pos_read(file);
10
ret = vfs_read(file, buf, count, &pos);
11

12
}
13
14
return ret;
15
}
先調(diào)用fget_light得到fd對(duì)應(yīng)的file,再調(diào)用vfs_read。接著跟蹤vfs_read的代碼,定義在fs/read_write.c中。
1
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
2

{
3
ssize_t ret;
4

5
ret = rw_verify_area(READ, file, pos, count);
6
if (ret >= 0)
{
7
count = ret;
8
if (file->f_op->read)
9
ret = file->f_op->read(file, buf, count, pos);
10
else
11
ret = do_sync_read(file, buf, count, pos);
12

13
}
14
15
return ret;
16
}
在
上篇Socket創(chuàng)建一節(jié)已知,因?yàn)閟ockfs_file_ops沒有定義read(即read指針為空),所以這兒實(shí)際調(diào)用了do_sync_read,繼續(xù)跟蹤它的代碼,定義在fs/read_write.c中。
1
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
2

{
3
struct iovec iov =
{ .iov_base = buf, .iov_len = len };
4
struct kiocb kiocb;
5
ssize_t ret;
6
7

8
for (;;)
{
9
ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
10
if (ret != -EIOCBRETRY)
11
break;
12
wait_on_retry_sync_kiocb(&kiocb);
13
}
14
15
if (-EIOCBQUEUED == ret)
16
ret = wait_on_sync_kiocb(&kiocb);
17
*ppos = kiocb.ki_pos;
18
return ret;
19
}
顯而易見,這兒調(diào)用到了f_op->aio_read,使用異步讀來實(shí)現(xiàn)同步讀,若異步讀沒有完成,則調(diào)用wait_on_sync_kiocb等待。由
上篇Socket創(chuàng)建一節(jié)可知sockfs_file_ops的aio_read設(shè)為sock_aio_read函數(shù),定義在net/socket.c中,至此sys_read的實(shí)現(xiàn)完成了前一半(操作對(duì)象是file)而進(jìn)入后一半(操作對(duì)象是socket),即socket層的實(shí)現(xiàn)。
在socket層跟蹤sock_aio_read,可以得到最后調(diào)用的是sock->ops->recvmsg,由于socket類型為IPv4 TCP,因此sock->ops在socket創(chuàng)建過程中被設(shè)為inet_stream_ops,定義在net/ipv4/af_inet.c中。
1
const struct proto_ops inet_stream_ops =
{
2
.family = PF_INET,
3

4
.release = inet_release,
5

6
.recvmsg = sock_common_recvmsg,
7

8
};
從上可知recvmsg設(shè)為sock_common_recvmsg,跟蹤它的代碼,定義在net/core/sock.c中。
1
int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size, int flags)
2

{
3
struct sock *sk = sock->sk;
4
int addr_len = 0;
5
int err;
6
7
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,flags & ~MSG_DONTWAIT, &addr_len);
8

9
return err;
10
}
struct sock表示套接字的網(wǎng)絡(luò)接口層,它的成員sk_prot表示網(wǎng)絡(luò)協(xié)議塊,在這它對(duì)應(yīng)tcp_prot結(jié)構(gòu),定義在net/ipv4/tcp_ipv4.c中,由此可見進(jìn)入到特定協(xié)議的實(shí)現(xiàn)。
1
struct proto tcp_prot =
{
2
.name = "TCP",
3

4
.close = tcp_close,
5

6
.recvmsg = tcp_recvmsg,
7

8
};
recvmsg設(shè)為tcp_recvmsg,至此跟蹤結(jié)束。對(duì)于sys_readv的實(shí)現(xiàn),調(diào)用的是vfs_readv,后面的過程和sys_read相同,總結(jié)核心調(diào)用鏈如下圖:
由此可知,sockfs_file_ops只須實(shí)現(xiàn)aio_read,就能支持普通和聚集兩種方式的讀操作。為了對(duì)比,這里也給出Berkeley Sockets API中recv的核心調(diào)用鏈如下圖:
顯而易見,recv內(nèi)部實(shí)現(xiàn)調(diào)用的是sys_recvfrom,它沒有經(jīng)過VFS,而是先調(diào)用sock_lookup_light從fd得到socket,再調(diào)用sock_recvmsg,后面的流程和recv就是一樣的了。
Socket銷毀
Socket操作既可以調(diào)用文件IO,也可以調(diào)用Berkeley Sockets API。但銷毀不同,系統(tǒng)調(diào)用close是用戶空間銷毀socket的唯一方法,它定義在fs/open.c中。
1
SYSCALL_DEFINE1(close, unsigned int, fd)
2

{
3
struct file * filp;
4
struct files_struct *files = current->files;
5
struct fdtable *fdt;
6
int retval;
7
8
spin_lock(&files->file_lock);
9
fdt = files_fdtable(files);
10

11
filp = fdt->fd[fd];
12

13
rcu_assign_pointer(fdt->fd[fd], NULL);
14
FD_CLR(fd, fdt->close_on_exec);
15
__put_unused_fd(files, fd);
16
spin_unlock(&files->file_lock);
17
retval = filp_close(filp, files);
18

19
}
首先從fd獲取對(duì)應(yīng)的file,若file非空則設(shè)置進(jìn)程描述符數(shù)組對(duì)應(yīng)項(xiàng)為空,并將fd從exec時(shí)關(guān)閉的文件描述符鏈表和打開的文件描述符鏈表中移除;最后調(diào)用filp_close,跟蹤它的代碼,定義在fs/open.c中。
1
int filp_close(struct file *filp, fl_owner_t id)
2

{
3
int retval = 0;
4
5
if (!file_count(filp))
{
6
printk(KERN_ERR "VFS: Close: file count is 0\n");
7
return 0;
8
}
9
10
if (filp->f_op && filp->f_op->flush)
11
retval = filp->f_op->flush(filp, id);
12
13
dnotify_flush(filp, id);
14
locks_remove_posix(filp, id);
15
fput(filp);
16
return retval;
17
}
首先判斷file的引用計(jì)數(shù),若為0則打印一個(gè)錯(cuò)誤日志(說明這是一個(gè)bug,因?yàn)閒ile已經(jīng)被釋放)并返回;由于sockfs_file_ops中的flush沒有定義即為空,因此跳過;dnotify_flush用于釋放任何相關(guān)的dnotify(一種文件監(jiān)控機(jī)制)資源,locks_remove_posix用于清除文件鎖相關(guān)的資源,由于socket對(duì)應(yīng)的inode沒有使用文件鎖,因此它什么也沒做。最后調(diào)用fput來釋放file,定義在fs/file_table.c中。
1
void fput(struct file *file)
2

{
3
if (atomic_long_dec_and_test(&file->f_count))
4
__fput(file);
5
}
先遞減引用計(jì)數(shù),若為0則調(diào)用__fput釋放file,它會(huì)調(diào)用到sockfs_file_ops定義的release函數(shù)即sock_close,它是sock_release的包裝函數(shù),sock_release定義在net/socket.c中。
1
void sock_release(struct socket *sock)
2

{
3
if (sock->ops)
{
4
struct module *owner = sock->ops->owner;
5
6
sock->ops->release(sock);
7
sock->ops = NULL;
8
module_put(owner);
9
}
10
if (sock->fasync_list)
11
printk(KERN_ERR "sock_release: fasync list not empty!\n");
12
13
percpu_sub(sockets_in_use, 1);
14
if (!sock->file)
{
15
iput(SOCK_INODE(sock));
16
return;
17
}
18
sock->file = NULL;
19
}
先調(diào)用ops->release即特定協(xié)議的釋放操作,對(duì)于IPv4 TCP,就是inet_stream_ops中定義的inet_release函數(shù),它又會(huì)調(diào)用到tcp_prot中定義的close即tcp_close;對(duì)于關(guān)聯(lián)inode的釋放,這里要分2種情況:如果sock->file為空,就調(diào)用iput釋放,否則返回到__fput中,會(huì)調(diào)用dput釋放dentry,而dentry又關(guān)聯(lián)著inode,最終調(diào)用iput釋放inode;當(dāng)最后一個(gè)iput被調(diào)用時(shí),sockfs_ops中定義的sock_destroy_inode就會(huì)被調(diào)用,歸還由sock_alloc_inode分配的struct socket_alloc對(duì)象到SALB緩存中。總結(jié)核心調(diào)用鏈如下圖:
在
上篇初始化一節(jié),我們已知sockfs文件系統(tǒng)被裝載,然而實(shí)際上沒有卸載它的方式。由于TCP/IP協(xié)議棧和sockfs被靜態(tài)編譯到內(nèi)核中,而不是一個(gè)內(nèi)核模塊。因此沒必要提供一個(gè)卸載函數(shù),sockfs偽文件系統(tǒng)在啟動(dòng)到關(guān)閉期間,總是被裝載著的。
posted on 2015-05-03 16:55
春秋十二月 閱讀(5301)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
Network