本文在上文基礎上進一步全面分析socket底層的相關實現。
一、socket與inode
socket在Linux中對應的文件系統叫Sockfs,每創建一個socket,就在sockfs中創建了一個特殊的文件,同時創建了sockfs文件系統中的inode,該inode唯一標識當前socket的通信。
如下圖所示,左側窗口使用nc工具創建一個TCP連接;右側找到該進程id(3384),通過查看該進程下的描述符,可以看到”3 ->socket:[86851]”,socket表示這是一個socket類型的fd,[86851]表示這個一個inode號,能夠唯一標識當前的這個socket通信連接,進一步在該inode下查看”grep -i “86851” /proc/net/tcp”可以看到該TCP連接的所有信息(連接狀態、IP地址等),只不過是16進制顯示。
在分析socket與inode之前,先通過ext4文件系統舉例:
在VFS層,即抽象層,所有的文件系統都使用struct inode結構體描述indoe,然而分配inode的方式都不同,如ext4文件系統的分配inode函數是ext4_alloc_inode,如下所示:
static?Struct?inode?*ext4_alloc_inode(struct?super_block?*sb) { ?struct?ext4_inode_info?*ei; ?ei?=?kmem_cache_alloc(ext4_inode_cachep,?GFP_NOFS); ?if?(!ei) ??return?NULL; ?ei->vfs_inode.i_version?=?1; ?spin_lock_init(&ei->i_raw_lock); ?INIT_LIST_HEAD(&ei->i_prealloc_list); ?spin_lock_init(&ei->i_prealloc_lock); ?ext4_es_init_tree(&ei->i_es_tree); ?rwlock_init(&ei->i_es_lock); ?INIT_LIST_HEAD(&ei->i_es_list); ?ei->i_es_all_nr?=?0; ?ei->i_es_shk_nr?=?0; ?ei->i_es_shrink_lblk?=?0; ?ei->i_reserved_data_blocks?=?0; ?ei->i_da_metadata_calc_len?=?0; ?ei->i_da_metadata_calc_last_lblock?=?0; ?spin_lock_init(&(ei->i_block_reservation_lock)); #ifdef?CONFIG_QUOTA ?ei->i_reserved_quota?=?0; ?memset(&ei->i_dquot,?0,?sizeof(ei->i_dquot)); #endif ?ei->jinode?=?NULL; ?INIT_LIST_HEAD(&ei->i_rsv_conversion_list); ?spin_lock_init(&ei->i_completed_io_lock); ?ei->i_sync_tid?=?0; ?ei->i_datasync_tid?=?0; ?atomic_set(&ei->i_unwritten,?0); ?INIT_WORK(&ei->i_rsv_conversion_work,?ext4_end_io_rsv_work); ?return?&ei->vfs_inode; }
從函數中可以看出來,函數其實是調用kmem_cache_alloc分配了 ext4_inode_info結構體(結構體如下所示),然后進行了一系列的初始化,最后返回的卻是struct inode結構體(如上面代碼的return &ei->vfs_inode)。如下結構體ext4_inode_info(ei)所示,vfs_inode是其struct inode結構體成員。
struct?ext4_inode_info?{ ?__le32?i_data[15];?/*?unconverted?*/ ?__u32?i_dtime; ?ext4_fsblk_t?i_file_acl; ?...... ?struct?rw_semaphore?i_data_sem; ?struct?rw_semaphore?i_mmap_sem; ?struct?inode?vfs_inode; ?struct?jbd2_inode?*jinode; ??...... };
再看一下:ext4_inode、ext4_inode_info、inode之間的關聯,
ext4_inode如下所示,是磁盤上inode的結構
struct?ext4_inode?{ ?__le16?i_mode;??/*?File?mode?*/ ?__le16?i_uid;??/*?Low?16?bits?of?Owner?Uid?*/ ?__le32?i_size_lo;?/*?Size?in?bytes?*/ ?__le32?i_atime;?/*?Access?time?*/ ?__le32?i_ctime;?/*?Inode?Change?time?*/ ?__le32?i_mtime;?/*?Modification?time?*/ ?__le32?i_dtime;?/*?Deletion?Time?*/ ?__le16?i_gid;??/*?Low?16?bits?of?Group?Id?*/ ?__le16?i_links_count;?/*?Links?count?*/ ?__le32?i_blocks_lo;?/*?Blocks?count?*/ ?__le32?i_flags;?/*?File?flags?*/ ?...... }
ext4_inode_info是ext4文件系統的inode在內存中管理結構體:
struct?ext4_inode_info?{ ?__le32?i_data[15];?/*?unconverted?*/ ?__u32?i_dtime; ?ext4_fsblk_t?i_file_acl; ?...... };
inode是文件系統抽象層:
struct?inode?{ ????umode_t?????????????????i_mode; ????unsigned?short??????????i_opflags; ????kuid_t??????????????????i_uid; ????kgid_t??????????????????i_gid; ????unsigned?int????????????i_flags; ? ????/*?對inode操作的具體方法 ?????*?不同的文件系統會注冊不同的函數方法即可 ?????*/ ????const?struct?inode_operations???*i_op; ????struct?super_block??????*i_sb; ????struct?address_space????*i_mapping; ? ????unsigned?long???????????i_ino; ???? ????union?{ ????????const?unsigned?int?i_nlink; ????????unsigned?int?__i_nlink; ????}; ????dev_t???????????????????i_rdev; ????/*?文件大小?*/ ????loff_t??????????????????i_size; ????/*?文件最后訪問時間?*/ ????struct?timespec?????????i_atime; ????/*?文件最后修改時間?*/ ????struct?timespec?????????i_mtime; ????/*?文件創建時間?*/ ????struct?timespec?????????i_ctime; ????spinlock_t??????????????i_lock;?/*?i_blocks,?i_bytes,?maybe?i_size?*/ ????unsigned?short??????????i_bytes; ????unsigned?int????????????i_blkbits; ????enum?rw_hint????????????i_write_hint; ????blkcnt_t????????????????i_blocks; ????/*?Misc?*/ ????unsigned?long???????????i_state; ????struct?rw_semaphore?????i_rwsem; ????unsigned?long???????????dirtied_when;???/*?jiffies?of?first?dirtying?*/ ????unsigned?long???????????dirtied_time_when; ? ????/*?inode通過以下結構被加入到的各種鏈表?*/ ????struct?hlist_node???????i_hash; ????struct?list_head????????i_io_list;??????/*?backing?dev?IO?list?*/ ? ????struct?list_head????????i_lru;??????????/*?inode?LRU?list?*/ ????struct?list_head????????i_sb_list; ????struct?list_head????????i_wb_list;??????/*?backing?dev?writeback?list?*/ ????union?{ ????????struct?hlist_head???????i_dentry; ????????struct?rcu_head?????????i_rcu; ????}; ????atomic64_t??????????????i_version; ????atomic_t????????????????i_count; ????atomic_t????????????????i_dio_count; ????atomic_t????????????????i_writecount; ? ????/*?對文件操作(如文件讀寫等)的具體方法 ?????*?實現虛擬文件系統的核心結構 ?????*?不同的文件系統只需要注冊不同的函數方法即可 ?????*/ ????const?struct?file_operations????*i_fop;?/*?former?->i_op->default_file_ops?*/ ????struct?file_lock_context????????*i_flctx; ????struct?address_space????i_data; ????struct?list_head????????i_devices; ????union?{ ????????struct?pipe_inode_info??*i_pipe; ????????struct?block_device?????*i_bdev; ????????struct?cdev?????????????*i_cdev; ????????char????????????????????*i_link; ????????unsigned????????????????i_dir_seq; ????}; ????__u32???????????????????i_generation; ????void????????????????????*i_private;?/*?fs?or?device?private?pointer?*/ }?__randomize_layout;
三者的關系如下圖,struct inode是VFS抽象層的表示,ext4_inode_info是ext4文件系統inode在內存中的表示,struct ext4_inode是文件系統inode在磁盤中的表示。
VFS采用c語言的方式實現了struct inode和struct ext4_inode_info繼承關系,inode與ext4_inode_info是父類與子類的關系,并且Linux內核實現了inode與ext4_inode_info父子類的互相轉換,如下EXT4_I所示:
static?inline?struct?ext4_inode_info?*EXT4_I(struct?inode?*inode) { ?return?container_of(inode,?struct?ext4_inode_info,?vfs_inode); }
以上是以ext4為例進行了分析,下面將開始從socket與inode進行分析:
sockfs是虛擬文件系統,所以在磁盤上不存在inode的表示,在內核中有struct socket_alloc來表示內存中sockfs文件系統inode的相關結構體:
struct?socket_alloc?{ ?struct?socket?socket; ?struct?inode?vfs_inode; };
struct socket與struct inode的關系如下圖,正如ext4文件系統中struct ext4_inode_info與struct inode的關系類似,inode和socket_alloc結構體是父類與子類的關系。
從上面分析ext4文件系統分配inode時,是通過ext4_alloc_inode函數分配了ext4_inode_info結構體,并初始化結構體成員,函數最后返回的是ext4_inode_info中的struct inode成員。sockfs文件系統也類似,sockfs文件系統分配inode時,創建的是socket_alloc結構體,在函數最后返回的是struct inode。
從上篇文章中,分析了sockfs文件系統注冊與掛載,初始化了超級塊的函數操作集,如下所示alloc_inode是分配inode結構體的回調函數接口。
static?const?struct?super_operations?sockfs_ops?=?{ ?.alloc_inode?=?sock_alloc_inode, ?.destroy_inode?=?sock_destroy_inode, ?.statfs??=?simple_statfs, }
sockfs文件系統的inode分配函數是sock_alloc_inode,如下所示:
static?struct?inode?*sock_alloc_inode(struct?super_block?*sb) { ?struct?socket_alloc?*ei; ?struct?socket_wq?*wq; ?ei?=?kmem_cache_alloc(sock_inode_cachep,?GFP_KERNEL); ?if?(!ei) ??return?NULL; ?wq?=?kmalloc(sizeof(*wq),?GFP_KERNEL); ?if?(!wq)?{ ??kmem_cache_free(sock_inode_cachep,?ei); ??return?NULL; ?} ?init_waitqueue_head(&wq->wait); ?wq->fasync_list?=?NULL; ?wq->flags?=?0; ?RCU_INIT_POINTER(ei->socket.wq,?wq); ?ei->socket.state?=?SS_UNCONNECTED; ?ei->socket.flags?=?0; ?ei->socket.ops?=?NULL; ?ei->socket.sk?=?NULL; ?ei->socket.file?=?NULL; ?return?&ei->vfs_inode; }
sock_alloc_inode函數分配了socket_alloc結構體,也就意味著分配了struct socket和struct inode,并最終返回了socket_alloc結構體成員inode。
故struct socket這個字段出生的時候其實就和一個struct inode結構體伴生出來的,它們倆共同封裝在struct socket_alloc中,由sockfs的sock_alloc_inode函數分配的,函數返回的是struct inode結構體.和ext4文件系統類型類似。sockfs文件系統也實現了struct inode與struct socket的轉換:
static?inline?struct?socket?*SOCKET_I(struct?inode?*inode) { ?return?&container_of(inode,?struct?socket_alloc,?vfs_inode)->socket; }
二、socket的創建與初始化
首先看一下struct socket在內核中的定義:
struct?socket?{ ?socket_state??state;//socket狀態 ?short???type;?//socket類型 ?unsigned?long??flags;//socket的標志位 ?struct?socket_wq?__rcu?*wq; ?struct?file??*file;//與socket關聯的文件指針 ?struct?sock??*sk;//套接字的核心,面向底層網絡具體協議 ?const?struct?proto_ops?*ops;//socket函數操作集 };
在內核中還有struct sock結構體,在struct socket中可以看到那么它們的關系是什么?
1、socket面向上層,sock面向下層的具體協議
2、socket是內核抽象出的一個通用結構體,主要是設置了一些跟fs相關的字段,而真正跟網絡通信相關的字段結構體是struct sock
3、struct sock是套接字的核心,是對底層具體協議做的一層抽象封裝,比如TCP協議,struct sock結構體中的成員sk_prot會賦值為tcp_prot,udp協議會賦值為udp_prot。
(關于更多struct sock的分析將在以后的文章中分析)
創建socket的系統調用:在用戶空間創建了一個socket后,返回值是一個文件描述符。在SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)最后調用sock_map_fd進行關聯,其中返回的就是用戶空間獲取的文件描述符fd,sock就是調用sock_create創建成功的socket.
SYSCALL_DEFINE3(socket,?int,?family,?int,?type,?int,?protocol) { ?int?retval; ?struct?socket?*sock; ?int?flags; ?/*?Check?the?SOCK_*?constants?for?consistency.??*/ ?BUILD_BUG_ON(SOCK_CLOEXEC?!=?O_CLOEXEC); ?BUILD_BUG_ON((SOCK_MAX?|?SOCK_TYPE_MASK)?!=?SOCK_TYPE_MASK); ?BUILD_BUG_ON(SOCK_CLOEXEC?&?SOCK_TYPE_MASK); ?BUILD_BUG_ON(SOCK_NONBLOCK?&?SOCK_TYPE_MASK); ?flags?=?type?&?~SOCK_TYPE_MASK; ?if?(flags?&?~(SOCK_CLOEXEC?|?SOCK_NONBLOCK)) ??return?-EINVAL; ?type?&=?SOCK_TYPE_MASK; ?if?(SOCK_NONBLOCK?!=?O_NONBLOCK?&&?(flags?&?SOCK_NONBLOCK)) ??flags?=?(flags?&?~SOCK_NONBLOCK)?|?O_NONBLOCK; ?retval?=?sock_create(family,?type,?protocol,?&sock); ?if?(retval?return?retval; ?return?sock_map_fd(sock,?flags?&?(O_CLOEXEC?|?O_NONBLOCK)); }
socket的創建將調用sock_create函數:
int?sock_create(int?family,?int?type,?int?protocol,?struct?socket?**res) { ?return?__sock_create(current->nsproxy->net_ns,?family,?type,?protocol,?res,?0); }
__sock_create函數調用sock_alloc函數分配socket結構和文件節點:
int?__sock_create(struct?net?*net,?int?family,?int?type,?int?protocol, ????struct?socket?**res,?int?kern) { ?int?err; ?struct?socket?*sock; ?const?struct?net_proto_family?*pf; ??//檢查family的字段范圍 ?if?(family?=?NPROTO) ??return?-EAFNOSUPPORT; ?if?(type?type?>=?SOCK_MAX) ??return?-EINVAL; ?...... ?sock?=?sock_alloc();//分配socket和inode,返回sock ?if?(!sock)?{ ??net_warn_ratelimited("socket:?no?more?sockets "); ??return?-ENFILE;?/*?Not?exactly?a?match,?but?its?the ???????closest?posix?thing?*/ ?} ?sock->type?=?type; ??...... ?rcu_read_lock(); ?pf?=?rcu_dereference(net_families[family]);//獲取協議族family對應的操作表 ?err?=?-EAFNOSUPPORT; ?if?(!pf) ??goto?out_release; ?if?(!try_module_get(pf->owner)) ??goto?out_release; ?/*?Now?protected?by?module?ref?count?*/ ?rcu_read_unlock(); ?err?=?pf->create(net,?sock,?protocol,?kern);//調用family協議族的socket創建函數 ?if?(err?if?(!try_module_get(sock->ops->owner)) ??goto?out_module_busy; ?...... }
socket結構體的創建在sock_alloc()函數中:
struct?socket?*sock_alloc(void) { ?struct?inode?*inode; ?struct?socket?*sock; ?inode?=?new_inode_pseudo(sock_mnt->mnt_sb); ?if?(!inode) ??return?NULL; ?sock?=?SOCKET_I(inode); ?inode->i_ino?=?get_next_ino(); ?inode->i_mode?=?S_IFSOCK?|?S_IRWXUGO; ?inode->i_uid?=?current_fsuid(); ?inode->i_gid?=?current_fsgid(); ?inode->i_op?=?&sockfs_inode_ops; ?this_cpu_add(sockets_in_use,?1); ?return?sock; }
new_inode_pseudo中通過繼續調用sockfs文件系統中的sock_alloc_inode函數完成struct socket_alloc的創建并返回其結構體成員struct inode。
然后調用SOCKT_I函數返回對應的struct socket。
在_sock_create中:pf->create(net, sock, protocol, kern);
通過相應的協議族,進一步調用不同的socket創建函數。pf是struct net_proto_family結構體,如下所示:
struct?net_proto_family?{ ?int??family; ?int??(*create)(struct?net?*net,?struct?socket?*sock, ??????int?protocol,?int?kern); ?struct?module?*owner; };
net_families[]數組里存放的是各個協議族的信息,以family字段作為下標,對應的值為net_pro_family結構體。此處我們針對TCP協議分析,因此我們family字段是AF_INET,pf->create將調用inet_create函數繼續完成底層struct sock等創建和初始化。
inet_create函數完成struct socket、struct inode、struct sock的創建與初始化后,調用sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));完成socket與文件系統的關聯,負責分配文件,并與socket進行綁定:
1、調用sock_alloc_file,分配一個struct file,并將私有數據指針指向socket結構
2、fd_install 對應文件描述符和file
static?int?sock_map_fd(struct?socket?*sock,?int?flags) { ?struct?file?*newfile; ?int?fd?=?get_unused_fd_flags(flags);//為socket分配文件號和文件結構 ?if?(unlikely(fd?return?fd; ?} ?newfile?=?sock_alloc_file(sock,?flags,?NULL);//分配file對象 ?if?(likely(!IS_ERR(newfile)))?{ ??fd_install(fd,?newfile);//使文件號與文件結構掛鉤 ??return?fd; ?} ?put_unused_fd(fd); ?return?PTR_ERR(newfile); }
get_unused_fd_flags(flags)繼續調用alloc_fd完成文件描述符的分配。
sock_alloc_file(sock, flags, NULL)分配一個struct file結構體
struct?file?*sock_alloc_file(struct?socket?*sock,?int?flags,?const?char?*dname) { ?...... ?file?=?alloc_file(&path,?FMODE_READ?|?FMODE_WRITE, ????&socket_file_ops);//分配struct?file結構體 ?if?(IS_ERR(file))?{ ??/*?drop?dentry,?keep?inode?for?a?bit?*/ ??ihold(d_inode(path.dentry)); ??path_put(&path); ??/*?...?and?now?kill?it?properly?*/ ??sock_release(sock); ??return?file; ?} ?sock->file?=?file;?//socket通過其file字段進行關聯 ?file->f_flags?=?O_RDWR?|?(flags?&?O_NONBLOCK); ?file->private_data?=?sock;//file通過private_data與socket關聯 ?return?file;?//返回初始化、關聯后的file結構體 }
其中file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);分配了file結構體并進行初始化:
struct?file?*alloc_file(const?struct?path?*path,?fmode_t?mode, ??const?struct?file_operations?*fop) { ?struct?file?*file; ?file?=?get_empty_filp(); ?if?(IS_ERR(file)) ??return?file; ?file->f_path?=?*path; ?file->f_inode?=?path->dentry->d_inode; ?file->f_mapping?=?path->dentry->d_inode->i_mapping; ?file->f_wb_err?=?filemap_sample_wb_err(file->f_mapping); ?if?((mode?&?FMODE_READ)?&& ??????likely(fop->read?||?fop->read_iter)) ??mode?|=?FMODE_CAN_READ; ?if?((mode?&?FMODE_WRITE)?&& ??????likely(fop->write?||?fop->write_iter)) ??mode?|=?FMODE_CAN_WRITE; ?file->f_mode?=?mode; ?file->f_op?=?fop; ?if?((mode?&?(FMODE_READ?|?FMODE_WRITE))?==?FMODE_READ) ??i_readcount_inc(path->dentry->d_inode); ?return?file; }
其中file->f_op = fop,將socket_file_ops傳遞給文件操作表
static?const?struct?file_operations?socket_file_ops?=?{ ?.owner?=?THIS_MODULE, ?.llseek?=?no_llseek, ?.read_iter?=?sock_read_iter, ?.write_iter?=?sock_write_iter, ?.poll?=??sock_poll, ?.unlocked_ioctl?=?sock_ioctl, #ifdef?CONFIG_COMPAT ?.compat_ioctl?=?compat_sock_ioctl, #endif ?.mmap?=??sock_mmap, ?.release?=?sock_close, ?.fasync?=?sock_fasync, ?.sendpage?=?sock_sendpage, ?.splice_write?=?generic_splice_sendpage, ?.splice_read?=?sock_splice_read, };
以上操作完成了struct socket、struct sock、struct file等的創建、初始化、關聯,并最終返回socket描述符fd
socket描述符fd和我們平時操作文件的文件描述符相同,那么會有一個疑問,可以看到struct file_operations socket_file_ops函數表中并沒有提供write()和read()接口,只是看到read_iter,write_iter等接口,那么系統是如何處理的呢?
以write()為例:
sys_write()->__vfs_write()
ssize_t?__vfs_write(struct?file?*file,?const?char?__user?*p,?size_t?count, ??????loff_t?*pos) { ?if?(file->f_op->write)//如果文件函數表結構體提供了write接口函數 ??return?file->f_op->write(file,?p,?count,?pos);//調用它的write函數 ?else?if?(file->f_op->write_iter) ??return?new_sync_write(file,?p,?count,?pos);//否則調用new_sync_write函數 ?else ??return?-EINVAL; }
從__vfs_write函數中可以看出來,如果socket函數表中沒有提供write接口函數,則調用new_sync_write:
static?ssize_t?new_sync_write(struct?file?*filp,?const?char?__user?*buf,?size_t?len,?loff_t?*ppos) { ?...... ?ret?=?call_write_iter(filp,?&kiocb,?&iter); ?...... }
call_write_iter:
static?inline?ssize_t?call_write_iter(struct?file?*file,?struct?kiocb?*kio,struct?iov_iter?*iter) { ?return?file->f_op->write_iter(kio,?iter);//調用socket文件函數表的aio_write函數 }
從以上__vfs_write()分析,如果文件函數表結構提供了write接口函數則調用write函數,如果文件函數表結構沒有提供write接口函數(如socket操作函數表中沒有提供write接口),則調用write_iter接口,即調用socket操作函數表中的sock_write_iter。就這樣通過socket fd進行普通文件系統那樣通過描述符進行讀寫等。
用戶得到socket fd,可以進行地址綁定、發送以及接收數據等操作,在Linux內核中有相關的函數完成從socket fd到struct socket、struct file的轉換:
static?struct?socket?*sockfd_lookup_light(int?fd,?int?*err,?int?*fput_needed) { ?struct?fd?f?=?fdget(fd);//通過socket?fd獲取struct?fd結構體,struct?fd結構體中有struct?file結構 ?struct?socket?*sock; ?*err?=?-EBADF; ?if?(f.file)?{ ??sock?=?sock_from_file(f.file,?err);//通過獲取的struct?file結構體獲取相應的struct?socket指針 ??if?(likely(sock))?{ ???*fput_needed?=?f.flags; ???return?sock; ??} ??fdput(f); ?} ?return?NULL; }
fdget()函數從當前進程的files_struct結構中找到網絡文件系統中的file文件指針,并封裝在struct fd結構體中。sock_from函數通過得到的file結構體得到對應的socket結構指針。sock_from函數如下:
struct?socket?*sock_from_file(struct?file?*file,?int?*err) { ?if?(file->f_op?==?&socket_file_ops) ??return?file->private_data;?/*?set?in?sock_map_fd?*/ ?*err?=?-ENOTSOCK; ?return?NULL; }
至此,socket底層來龍去脈的大體結構大概就分析到這,最為核心的struct sock相關的聯系以及底層協議的初始化等將在以后的文章進行分析。