Linux套接字與虛擬檔案系統(1):初始化和建立
阿新 • • 發佈:2018-12-22
引言
在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 *f = 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
在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就呼叫特定的協議實現,層次結構如下圖:
初始化 在核心引導時初始化網路子系統,進而呼叫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
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 *f = 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