1. 程式人生 > >Linux套接字與虛擬檔案系統(1):初始化和建立

Linux套接字與虛擬檔案系統(1):初始化和建立

引言
   在Unix的世界裡,萬物皆檔案,通過虛擬檔案系統VFS,程式可以用標準的Unix系統呼叫對不同的檔案系統,甚至不同介質上的檔案系統進行讀寫操作。對於網路套接字socket也是如此,除了專屬的Berkeley Sockets API,還支援一些標準的檔案IO系統呼叫如read(v)、write(v)和close等。那麼為什麼socket也支援檔案IO系統呼叫呢?在Linux上,這是通過套介面偽檔案系統sockfs來實現的,因為sockfs實現了VFS中的4種主要物件:超級塊super block、索引節點inode、目錄項物件dentry和檔案物件file,當執行檔案IO系統呼叫時,VFS就將請求轉發給sockfs,而sockfs就呼叫特定的協議實現,層次結構如下圖:
   本文以linux 2.6.34實現為基礎,本篇闡述初始化和Socket建立兩部分的實現,下篇闡述Socket操作和銷燬兩部分的實現。

初始化    在核心引導時初始化網路子系統,進而呼叫sock_init,該函式主要步驟如下:建立inode快取,註冊和裝載sockfs,定義在net/socket.c中。 1staticint __init sock_init(void)
2{
3    
4    init_inodecache();
5    register_filesystem(&sock_fs_type);
6    sock_mnt 
= kern_mount(&sock_fs_type);
7    
8}
   
   建立inode快取
   init_inodecache為socket_alloc物件建立SLAB快取,名稱為sock_inode_cachep,socket_alloc定義在include/net/sock.h中。
1struct socket_alloc {
2    struct socket socket;
3    struct inode vfs_inode;
4}
;
   socket_alloc由socket和inode結構2部分組成,這樣就方便了在套接字與inode物件間雙向定位。

   註冊sockfs

   呼叫VFS的函式register_filesystem實現註冊,sock_fs_type定義在net/socket.c中。 1staticstruct file_system_type sock_fs_type={
2    .name ="sockfs",
3    .get_sb =sockfs_get_sb,4    .kill_sb =    kill_anon_super,
5}
;    sock_fs_type包含了檔案系統sockfs的名稱、建立和銷燬super block的函式,其中sockfs_get_sb實現在net/socket.c中。 1staticintsockfs_get_sb(struct file_system_type *fs_type,int flags, constchar*dev_name, void*data,struct vfsmount *mnt)
2{
3    return get_sb_pseudo(fs_type, "socket:"&sockfs_ops, SOCKFS_MAGIC, mnt);
4}
   它在kern_mount內被執行,通過呼叫get_sb_pseudo建立了一個super block(包含一個對應dentry及一個關聯inode):操作物件為sockfs_ops,根目錄名稱為socket:,對應的根索引節點編號為1。
   sockfs_ops定義在net/socket.c中。 1staticconststruct super_operations sockfs_ops={
2    .alloc_inode =    sock_alloc_inode,3    .destroy_inode = sock_destroy_inode,4    .statfs =    simple_statfs,
5}
;    sock_alloc_inode用於分配inode物件,將在socket建立過程中被呼叫;sock_destroy_inode用於釋放inode物件,將在socket銷燬過程中被呼叫;simple_statfs用於獲取sockfs檔案系統的狀態資訊。
   
   裝載sockfs
   由kern_mount函式實現裝載一個偽檔案系統(當然,它沒有裝載點),返回一個static vfsmount物件sock_mnt。

   經過以上步驟後,所建立的VFS物件關係如下圖:
    對於根目錄項,不用進行路徑轉換,因此dentry的d_op為空(未畫出);對於偽檔案系統,操作索引物件沒有意義,所以inode的i_op為空(未畫出)。

   系統呼叫socket、accept和socketpair是使用者空間建立socket的幾種方法,其核心呼叫鏈如下圖:
   從上圖可知共同的核心就3個過程:先構造inode,再構造對應的file,最後安裝file到當前程序中(即關聯對映到一個未用的檔案描述符),下面就這3個過程進行詳細說明。

   
   由sock_alloc函式實現,定義在net/socket.c中。  1staticstruct socket *sock_alloc(void)
 2{
 3    struct inode *inode;
 4    struct socket *sock;
 5
 6    inode = new_inode(sock_mnt->mnt_sb);
 7        
 8    sock =SOCKET_I(inode);
 9            
10    inode->i_mode =S_IFSOCK | S_IRWXUGO;
11    inode->i_uid = current_fsuid();
12    inode->i_gid = current_fsgid();
13        
14    return sock;
15}
   先呼叫new_inode建立inode物件,再設定它的型別為S_IFSOCK,由此可知inode對應的檔案型別為套接字。new_inode是檔案系統的一個介面函式,用於建立一個inode物件,定義在fs/inode.c中,它呼叫了sockfs超級塊的操作物件即sockfs_ops的sock_alloc_inode方法,由於sock_alloc_inode實際建立的是socket_alloc複合物件,因此要使用SOCKET_I巨集從inode中取出關聯的socket物件用於返回。


   有了inode物件後,接下來就要構造對應的file物件了,由sock_alloc_file實現,定義在net/socket.c中。  1staticintsock_alloc_file(struct socket *sock, struct file **f, int flags)
 2{
 3    struct qstr name ={ .name ="" };
 4    struct path path;
 5    struct file *file;
 6    int fd;
 7
 8    fd = get_unused_fd_flags(flags);
 9        
10    path.dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
11        
12    path.mnt = mntget(sock_mnt);
13
14    path.dentry->d_op =&sockfs_dentry_operations;15    d_instantiate(path.dentry, SOCK_INODE(sock));16    SOCK_INODE(sock)->i_fop =&socket_file_ops;17
18    file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);
19    
20    sock->file = file;21    file->f_flags = O_RDWR | (flags & O_NONBLOCK);
22    file->f_pos =0;
23    file->private_data = sock;24
25    *= file;
26    return fd;
27}
   sock為上一過程返回的套接字物件,該函式主要做了以下幾件事:
   1)得到空閒的檔案描述符fd,實際上就是fd陣列的索引,準備作為返回值。
   2)先初始化路徑path:其目錄項的父目錄項為超級塊對應的根目錄,名稱為空,操作物件為sockfs_dentry_operations,對應的索引節點物件為sock套接字關聯的索引節點物件,即SOCK_INODE(sock);裝載點為sock_mnt。  
   sockfs_dentry_operations定義在net/socket.c中。
1staticconststruct dentry_operations sockfs_dentry_operations ={
2    .d_dname  = sockfs_dname,
3}
;    sockfs_dname會被d_path呼叫,用於計算socket物件的目錄項名稱。
   3)設定索引節點的檔案操作物件為socket_file_ops,定義在net/socket.c中。
1staticconststruct file_operations socket_file_ops ={
2    
3    .aio_read =    sock_aio_read,
4    .aio_write =    sock_aio_write,
5    
6    .open =        sock_no_open,    /* special open code to disallow open via /proc */7    .release =    sock_close,
8    
9}
;    4)呼叫alloc_file,以path和socket_file_ops為輸入引數,這樣返回得到的file便與sock的inode關聯上了,並且操作物件為socket_file_ops,最後設定到輸出引數f中。
   5)建立file與socket的一一對映關係。
   
   安裝file
   由fd_install實現,定義在fs/open.c中。
 1voidfd_install(unsigned int fd, struct file *file)
 2{
 3    struct files_struct *files = current->files;
 4    struct fdtable *fdt;
 5    spin_lock(&files->file_lock);
 6    fdt =files_fdtable(files);
 7    BUG_ON(fdt->fd[fd] != NULL);
 8    rcu_assign_pointer(fdt->fd[fd], file);
 9    spin_unlock(&files->file_lock);
10}
   fd和file分別為上一過程返回的空閒檔案描述符和檔案物件,使RCU技術來設定file到當前程序的fd陣列中。
 
   經過以上過程後,所建立的VFS物件關係圖如下
   fd為file*陣列的索引而不是成員欄位;vfsmount與初始化之VFS物件關係圖中的vfsmount是同一個物件,即sock_mnt;對於偽檔案系統,操作索引物件沒有意義,所以inode的i_op為空(未畫出)。 posted on 2015-05-03 16:31 春秋十二月 閱讀(6464) 評論(0)  編輯 收藏 引用 所屬分類: Network