1. 程式人生 > >Linux套接字與虛擬檔案系統(2):操作和銷燬

Linux套接字與虛擬檔案系統(2):操作和銷燬

   接上篇初始化與建立,本篇闡述Socket操作和銷燬兩部分的實現。

Socket操作    系統呼叫read(v)、write(v)是使用者空間讀寫socket的一種方法,為了弄清楚它們是怎麼通過VFS將請求轉發到特定協議的實現,下面以read為例(write同理),並假定檔案描述符對應的是IPv4 TCP型別的socket,來跟蹤它的執行流程。首先來看下sys_read的程式碼,定義在fs/read_write.c中。  1SYSCALL_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}
   先呼叫fget_light得到fd對應的file,再呼叫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        else11            ret =do_sync_read(file, buf, count, pos);
12        
13    }
14
15    return ret;
16}
   在上篇Socket建立一節已知,因為sockfs_file_ops沒有定義read(即read指標為空),所以這兒實際呼叫了do_sync_read,繼續跟蹤它的程式碼,定義在fs/read_write.c中。
 1ssize_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}
   顯而易見,這兒呼叫到了f_op->aio_read,使用非同步讀來實現同步讀,若非同步讀沒有完成,則呼叫wait_on_sync_kiocb等待。由上篇Socket建立一節可知sockfs_file_ops的aio_read設為sock_aio_read函式,定義在net/socket.c中,至此sys_read的實現完成了前一半(操作物件是file)而進入後一半(操作物件是socket),即socket層的實現。
   在socket層跟蹤sock_aio_read,可以得到最後呼叫的是sock->ops->recvmsg,由於socket型別為IPv4 TCP,因此sock->ops在socket建立過程中被設為inet_stream_ops,定義在net/ipv4/af_inet.c中。 1conststruct proto_ops inet_stream_ops={
2    .family    =  PF_INET,
3    
4    .release=inet_release,5    
6    .recvmsg=sock_common_recvmsg,7    
8}
;    從上可知recvmsg設為sock_common_recvmsg,跟蹤它的程式碼,定義在net/core/sock.c中。    1intsock_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表示套接字的網路介面層,它的成員sk_prot表示網路協議塊,在這它對應tcp_prot結構,定義在net/ipv4/tcp_ipv4.c中,由此可見進入到特定協議的實現。 1struct proto tcp_prot={
2    .name            ="TCP",
3    
4    .close=tcp_close,5    
6    .recvmsg=tcp_recvmsg,7    
8}
;    recvmsg設為tcp_recvmsg,至此跟蹤結束。對於sys_readv的實現,呼叫的是vfs_readv,後面的過程和sys_read相同,總結核心呼叫鏈如下圖:
   由此可知,sockfs_file_ops只須實現aio_read,就能支援普通和聚集兩種方式的讀操作。為了對比,這裡也給出Berkeley Sockets API中recv的核心呼叫鏈如下圖:
   顯而易見,recv內部實現呼叫的是sys_recvfrom,它沒有經過VFS,而是先呼叫sock_lookup_light從fd得到socket,再呼叫sock_recvmsg,後面的流程和recv就是一樣的了。

   Socket操作既可以呼叫檔案IO,也可以呼叫Berkeley Sockets API。但銷燬不同,系統呼叫close是使用者空間銷燬socket的唯一方法,它定義在fs/open.c中。
 1SYSCALL_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獲取對應的file,若file非空則設定程序描述符陣列對應項為空,並將fd從exec時關閉的檔案描述符連結串列和開啟的檔案描述符連結串列中移除;最後呼叫filp_close,跟蹤它的程式碼,定義在fs/open.c中。
 1intfilp_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        return0;
 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的引用計數,若為0則列印一個錯誤日誌(說明這是一個bug,因為file已經被釋放)並返回;由於sockfs_file_ops中的flush沒有定義即為空,因此跳過;dnotify_flush用於釋放任何相關的dnotify(一種檔案監控機制)資源,locks_remove_posix用於清除檔案鎖相關的資源,由於socket對應的inode沒有使用檔案鎖,因此它什麼也沒做。最後呼叫fput來釋放file,定義在fs/file_table.c中。
1voidfput(struct file *file)
2{
3    if (atomic_long_dec_and_test(&file->f_count))
4        __fput(file);
5}
   先遞減引用計數,若為0則呼叫__fput釋放file,它會呼叫到sockfs_file_ops定義的release函式即sock_close,它是sock_release的包裝函式,sock_release定義在net/socket.c中。
 1void