Socket并不是TCP/IP協議的一部份,從廣義上來講,socket是Unix/Linux抽像的進程間通訊的一種方法
網絡socket通訊僅僅是其若干協議中的一類,而tcp/ip又是網絡協議各類中的一種
從tcp/ip的角度看socket,它更多地體現了用戶API與協議棧的一個中間層接口層
用戶通過調用socket API將報文遞交給協議棧,或者從協議棧中接收報文
系統總入口Linux內核為所有的與socket有關操作的API,提供了一個統一的系統調用入口,其代碼在net/socket.c中
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
...
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
a0=a[0];
a1=a[1];
switch(call)
{
case SYS_SOCKET:
err = sys_socket(a0,a1,a[2]);
break;
case SYS_BIND:
err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
break;
case SYS_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_LISTEN:
err = sys_listen(a0,a1);
break;
case SYS_ACCEPT:
err = sys_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
break;
case SYS_GETSOCKNAME:
err = sys_getsockname(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
break;
case SYS_GETPEERNAME:
err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
break;
case SYS_SOCKETPAIR:
err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
break;
case SYS_SEND:
err = sys_send(a0, (void __user *)a1, a[2], a[3]);
break;
case SYS_SENDTO:
err = sys_sendto(a0,(void __user *)a1, a[2], a[3],
(struct sockaddr __user *)a[4], a[5]);
break;
case SYS_RECV:
err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
break;
case SYS_RECVFROM:
err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
(struct sockaddr __user *)a[4], (int __user *)a[5]);
break;
case SYS_SHUTDOWN:
err = sys_shutdown(a0,a1);
break;
case SYS_SETSOCKOPT:
err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
break;
case SYS_GETSOCKOPT:
err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
break;
case SYS_SENDMSG:
err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
break;
case SYS_RECVMSG:
err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
break;
default:
err = -EINVAL;
break;
}
return err;
}
首先調用 copy_from_user將用戶態參數拷貝至數組a,但是問題在于每個被調用的 API 的參數不盡相同,
那么每次拷貝的字節在小如何判斷.來看其第三個參數 nargs[call],其中 call 是操作碼,后面有個大大的
switch...case 就是判斷它。對應的操作碼定義在 include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define
SYS_BIND 2 /*
sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
而數組nargs則根據操作碼的不同,計算對應的參數的空間大小:
/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
#undef AL
當拷貝完成參數后,就進入一個switch...case...判斷操作碼,跳轉至對應的系統接口.
sys_socket函數 操作碼 SYS_SOCKET 是由 sys_socket()實現的:
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241 int retval;
1242 struct socket *sock;
1243
1244 retval = sock_create(family, type, protocol, &sock);
1245 if (retval < 0)
1246 goto out;
1247
1248 retval = sock_map_fd(sock);
1249 if (retval < 0)
1250 goto out_release;
1251
1252 out:
1253 /* It may be already another descriptor 8) Not kernel problem. */
1254 return retval;
1255
1256 out_release:
1257 sock_release(sock);
1258 return retval;
1259 }
在分析這段代碼之前, 首先來看創建一個Socket, 對內核而言,究竟意味著什么?究竟需要內核干什么事?
當用戶空間要創建一個 socke 接口時,會調用 API 函數
int socket(int domain, int type, int protocol)
其三個參數分別表示協議族,協議類型(面向連接或無連接)以及協議
對于用戶態而言, 一個Socket, 就是一個特殊的已經打開的文件,為了對socket抽像出文件的概念,
內核中為socket定義了一個專門的文件系統類型sockfs.
344 static struct vfsmount *sock_mnt __read_mostly;
345
346 static struct file_system_type sock_fs_type = {
347 .name = "sockfs",
348 .get_sb = sockfs_get_sb,
349 .kill_sb = kill_anon_super,
350 };
在模塊初始化的時候,安裝該文件系統:
void __init sock_init(void)
{
……
register_filesystem(&sock_fs_type);
sock_mnt = kern_mount(&sock_fs_type);
}
稍后還要回來繼續分析安裝中的一點細節
有了文件系統后,對內核而言,創建一個socket,就是在sockfs文件系統中創建一個文件節點(inode),并建立起為了實現
socket功能所需的一整套數據結構,包括struct inode和struct socket結構.
struct socket結構在內核中,就代表了一個"Socket",當一個struct socket數據結構被分配空間后,再將其與一個已打開
的文件“建立映射關系”.這樣,用戶態就可以用抽像的文件的概念來操作socket了
——當然由于網絡的特殊性,至少就目前而言,這種抽像,并不如其它模塊的抽像那么完美.
文件系統struct vfsmount中有一個成員指針mnt_sb指向該文件系統的超級塊,而超級塊結構struct super_lock
有一個重要的成員s_op指向了超級塊的操作函數表,其中有函數指針alloc_inode()即為在給定的超級塊下創建并初始化
一個新的索引節點對像. 也就是調用:
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
當然,連同相關的處理細節一起,這一操作被層層封裝至一個上層函數new_inode()
那如何分配一個struct socket結構呢?
如前所述,一個socket總是與一個inode 密切相關的.當然,在 inode 中設置一個socket成員是完全可行的,
但是這貌似浪費了空間——畢竟更多的文件系統沒有socket這個東東.
所以,內核引入了另一個socket_alloc結構
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
顯而易見,該結構實現了inode和socket的封裝.已知一個inode可以通過宏SOCKET_I來獲取
與之對應的 socket:
sock = SOCKET_I(inode);
static inline struct socket *SOCKET_I(struct inode *inode)
{
return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}
但是這樣做也同時意味著在分配一個inode后,必須再分配一個socket_alloc結構,并實現對應的封裝.
否則container_of又能到哪兒去找到socket呢?
現在來簡要地看一個這個流程——這是文件系統安裝中的一個重要步驟
881 struct vfsmount *kern_mount(struct file_system_type *type)
882 {
883 return vfs_kern_mount(type, 0, type->name, NULL);
884 }
817 struct vfsmount *
818 vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
819 {
820 struct vfsmount *mnt;
821 char *secdata = NULL;
822 int error;
....
828 mnt = alloc_vfsmnt(name);
829 if (!mnt)
830 goto out;
841 ....
842 error = type->get_sb(type, flags, name, data, mnt);
843 if (error < 0)
844 goto out_free_secdata;
849
850 mnt->mnt_mountpoint = mnt->mnt_root;
851 mnt->mnt_parent = mnt;
852 up_write(&mnt->mnt_sb->s_umount);
853 free_secdata(secdata);
854 return mnt;
855 .....
865 }
申請文件系統mnt結構, 調用之前注冊的sock_fs_type的get_sb成員函數指針, 獲取相應的超級塊sb.
并將mnt->mnt_sb指向sock_fs_type中的超級塊
337 static int sockfs_get_sb(struct file_system_type *fs_type,
338 int flags, const char *dev_name, void *data, struct vfsmount *mnt)
339 {
340 return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC,
341 mnt);
342 }
注意其第三個參數 sockfs_ops,它封裝了 sockfs 的功能函數表
331 static struct super_operations sockfs_ops = {
332 .alloc_inode = sock_alloc_inode,
333 .destroy_inode =sock_destroy_inode,
334 .statfs = simple_statfs,
335 };
struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,
struct super_operations *ops, unsigned long magic)
{
struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
……
s->s_op = ops ? ops : &default_ops;
}
這里就是先獲取/分配一個超級塊,然后初始化超級塊的各成員,包括s_op,它封裝了對應的功能函數表.
s_op自然就指向了sockfs_ops,那前面提到的new_inode()函數分配inode時調用的
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
這個alloc_inode函數指針也就是sockfs_ops的sock_alloc_inode()函數——轉了一大圈,終于指到它了.
來看看sock_alloc_inode是如何分配一個inode節點的
283 static kmem_cache_t * sock_inode_cachep __read_mostly;
284
285 static struct inode *sock_alloc_inode(struct super_block *sb)
286 {
287 struct socket_alloc *ei;
288 ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL);
289 if (!ei)
290 return NULL;
291 init_waitqueue_head(&ei->socket.wait);
292
293 ei->socket.fasync_list = NULL;
294 ei->socket.state = SS_UNCONNECTED;
295 ei->socket.flags = 0;
296 ei->socket.ops = NULL;
297 ei->socket.sk = NULL;
298 ei->socket.file = NULL;
299 ei->socket.flags = 0;
300
301 return &ei->vfs_inode;
302 }
函數先分配了一個用于封裝socket和inode的 ei,然后在高速緩存中為之申請了一塊空間.
這樣inode和socket就同時都被分配了,接下來初始化socket的各個成員,這些成員在后面都會一一提到
96 /**
97 * struct socket - general BSD socket
98 * @state: socket state (%SS_CONNECTED, etc)
99 * @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
100 * @ops: protocol specific socket operations
101 * @fasync_list: Asynchronous wake up list
102 * @file: File back pointer for gc
103 * @sk: internal networking protocol agnostic socket representation
104 * @wait: wait queue for several uses
105 * @type: socket type (%SOCK_STREAM, etc)
106 */
107 struct socket {
108 socket_state state;
109 unsigned long flags;
110 const struct proto_ops *ops;
111 struct fasync_struct *fasync_list;
112 struct file *file;
113 struct sock *sk;
114 wait_queue_head_t wait;
115 short type;
116 };
至目前為止,分配inode,socket以及兩者如何關聯,都已一一分析了.
最后一個關鍵問題,就是如何把socket與一個已打開的文件,建立映射關系.
在內核中,用struct file結構描述一個已經打開的文件,指向該結構的指針內核中通常用file或filp來描述.
我們知道,內核中可以通過全局項current來獲得當前進程,它是一個struct task_struct類型的指針.
tastk_struct有一個成員:
struct files_struct *files;指向一個已打開的文件.
當然,由于一個進程可能打開多個文件,所以,struct files_struct 結構有
struct file *fd_array[NR_OPEN_DEFAULT]成員,
這是個數組,以文件描述符為下標,即current->files->fd[fd],可以找到與當前進程指定文件描述符的文件
有了這些基礎,如果要把一個socket與一個已打開的文件建立映射,首先要做的就是為socket分配一個struct file,
并申請分配一個相應的文件描述符fd. 因為socket并不支持open方法,所以不能期望用戶界面通過調用open() API
來分配一個struct file,而是通過調用get_empty_filp來獲取
struct file *file = get_empty_filp()
fd = get_unused_fd();獲取一個空間的文件描述符
然后讓current的files指針的fd數組的fd索引項指向該file
void fastcall fd_install(unsigned int fd, struct file * file)
{
struct files_struct *files = current->files;
spin_lock(&files->file_lock);
if (unlikely(files->fd[fd] != NULL))
BUG();
files->fd[fd] = file;
spin_unlock(&files->file_lock);
}
做到這一步,有了一個文件描述符fd和一個打開的文件file,它們與當前進程相連,但是好像與創建的socket并無任何瓜葛.
要做的映射還是沒有進展,struct file或者文件描述述fd或current都沒有任何能夠與 inode或者是socket相關的東東
這需要一個中間的橋梁,目錄項:struct dentry結構
因為一個文件都有與其對應的目錄項:
struct file {
struct list_head f_list;
struct dentry *f_dentry;
……
而一個目錄項:
struct dentry {
……
struct inode *d_inode; /* Where the name belongs to - NULL is negative */
d_inode 成員指向了與之對應的 inode節點
之前已經創建了一個inode節點和與之對應的 socket. 所以現在要做的就是:
“先為當前文件分配一個對應的目錄項,再將已創建的 inode節點安裝至該目錄項”
這樣一個完成的映射關系:
進程,文件描述符,打開文件,目錄項,inode節點,socket就完整地串起來了
基本要分析的一些前導的東東都一一羅列了,雖然已盡量避免陷入文件系統的細節分析,但是還是不可避免地進入其中,
因為它們關系實現太緊密了,現在可以來看套接字的創建過程了
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241 int retval;
1242 struct socket *sock;
1243
1244 retval = sock_create(family, type, protocol, &sock);
1245 if (retval < 0)
1246 goto out;
1247
1248 retval = sock_map_fd(sock);
1249 if (retval < 0)
1250 goto out_release;
1251
1252 out:
1253 /* It may be already another descriptor 8) Not kernel problem. */
1254 return retval;
1255
1256 out_release:
1257 sock_release(sock);
1258 return retval;
1259 }
1229 int sock_create(int family, int type, int protocol, struct socket **res)
1230 {
1231 return __sock_create(family, type, protocol, res, 0);
1232 }
AF_INET協議簇的協議封裝 接下來,函數調用之前已經注冊的inet_family_ops的函數指針create,也就是inet_create()函數.
前面可以說一個通用的socket已經創建好了,這里要完成與協議本身相關的一些創建socket的工作.
這一部份的工作比較復雜,還是先來看看af_inet.c中的模塊初始化時候,做了哪些與此相關的工作.
要引入的第一個數據結構是struct inet_protosw,它封裝了一個協議類型(如 SOCK_STREAM,SOCK_DGRAM等)
與IP協議中對應的傳輸層協議.
68 /* This is used to register socket interfaces for IP protocols. */
69 struct inet_protosw {
70 struct list_head list;
71
72 /* These two fields form the lookup key. */
73 unsigned short type; /* This is the 2nd argument to socket(2). */
74 int protocol; /* This is the L4 protocol number. */
75
76 struct proto *prot;
77 const struct proto_ops *ops;
78
79 int capability; /* Which (if any) capability do we need to use this socket interface*/
83 char no_check; /* checksum on rcv/xmit/none? */
84 unsigned char flags; /* See INET_PROTOSW_* below. */
85 };
type是協議類型,對于 ipv4 而言就是SOCK_STREAM,SOCK_DGRAM或者是SOCK_RAW之一.
protocol是傳輸層的協議號,prot用于描述一個具體的傳輸層協議,而ops指向對應的當前協議類型的操作函數集
針對不同的協議類型,定義了不同的 ops:
791 const struct proto_ops inet_stream_ops = {
792 .family = PF_INET,
793 .owner = THIS_MODULE,
794 .release = inet_release,
795 .bind = inet_bind,
796 .connect = inet_stream_connect,
797 .socketpair = sock_no_socketpair,
798 .accept = inet_accept,
799 .getname = inet_getname,
800 .poll = tcp_poll,
801 .ioctl = inet_ioctl,
802 .listen = inet_listen,
803 .shutdown = inet_shutdown,
804 .setsockopt = sock_common_setsockopt,
805 .getsockopt = sock_common_getsockopt,
806 .sendmsg = inet_sendmsg,
807 .recvmsg = sock_common_recvmsg,
808 .mmap = sock_no_mmap,
809 .sendpage = tcp_sendpage,
810 #ifdef CONFIG_COMPAT
811 .compat_setsockopt = compat_sock_common_setsockopt,
812 .compat_getsockopt = compat_sock_common_getsockopt,
813 #endif
814 };
815
816 const struct proto_ops inet_dgram_ops = {
817 .family = PF_INET,
818 .owner = THIS_MODULE,
819 .release = inet_release,
820 .bind = inet_bind,
821 .connect = inet_dgram_connect,
822 .socketpair = sock_no_socketpair,
823 .accept = sock_no_accept,
824 .getname = inet_getname,
825 .poll = udp_poll,
826 .ioctl = inet_ioctl,
827 .listen = sock_no_listen,
828 .shutdown = inet_shutdown,
829 .setsockopt = sock_common_setsockopt,
830 .getsockopt = sock_common_getsockopt,
831 .sendmsg = inet_sendmsg,
832 .recvmsg = sock_common_recvmsg,
833 .mmap = sock_no_mmap,
834 .sendpage = inet_sendpage,
835 #ifdef CONFIG_COMPAT
836 .compat_setsockopt = compat_sock_common_setsockopt,
837 .compat_getsockopt = compat_sock_common_getsockopt,
838 #endif
839 };
840
841 /*
842 * For SOCK_RAW sockets; should be the same as inet_dgram_ops but without
843 * udp_poll
844 */
845 static const struct proto_ops inet_sockraw_ops = {
846 .family = PF_INET,
847 .owner = THIS_MODULE,
848 .release = inet_release,
849 .bind = inet_bind,
850 .connect = inet_dgram_connect,
851 .socketpair = sock_no_socketpair,
852 .accept = sock_no_accept,
853 .getname = inet_getname,
854 .poll = datagram_poll,
855 .ioctl = inet_ioctl,
856 .listen = sock_no_listen,
857 .shutdown = inet_shutdown,
858 .setsockopt = sock_common_setsockopt,
859 .getsockopt = sock_common_getsockopt,
860 .sendmsg = inet_sendmsg,
861 .recvmsg = sock_common_recvmsg,
862 .mmap = sock_no_mmap,
863 .sendpage = inet_sendpage,
864 #ifdef CONFIG_COMPAT
865 .compat_setsockopt = compat_sock_common_setsockopt,
866 .compat_getsockopt = compat_sock_common_getsockopt,
867 #endif
868 };
從各個函數指針的名稱,我們就可以大約知道它們是做什么事的了.進一步進以看到,
它們的函數指針指向的函數差不多都是相同的.除了一些細節上的區別,例如后面兩種協議類型并不支持listen.
socket()API第二個參數是協議類型,第三個參數是該協議類型下的協議——不過對于ipv4而言,
它們都是一一對應的,但是從抽像封裝的角度看,數據結構的設計本身應該滿足一個協議類型下邊可能存在多個不同的協議,
即一對多的情況.而一一對應,僅是它們的特例:
876 /* Upon startup we insert all the elements in inetsw_array[] into
877 * the linked list inetsw.
878 */
879 static struct inet_protosw inetsw_array[] =
880 {
881 {
882 .type = SOCK_STREAM,
883 .protocol = IPPROTO_TCP,
884 .prot = &tcp_prot,
885 .ops = &inet_stream_ops,
886 .capability = -1,
887 .no_check = 0,
888 .flags = INET_PROTOSW_PERMANENT |
889 INET_PROTOSW_ICSK,
890 },
892 {
893 .type = SOCK_DGRAM,
894 .protocol = IPPROTO_UDP,
895 .prot = &udp_prot,
896 .ops = &inet_dgram_ops,
897 .capability = -1,
898 .no_check = UDP_CSUM_DEFAULT,
899 .flags = INET_PROTOSW_PERMANENT,
900 },
903 {
904 .type = SOCK_RAW,
905 .protocol = IPPROTO_IP, /* wild card */
906 .prot = &raw_prot,
907 .ops = &inet_sockraw_ops,
908 .capability = CAP_NET_RAW,
909 .no_check = UDP_CSUM_DEFAULT,
910 .flags = INET_PROTOSW_REUSE,
911 }
912 };
數組的每一個元素,就是支持的一種協議名稱,例如IPOROTO_TCP,但是由于IPV4本身協議類型跟協議是一一對應的,
所以沒有更多的.type= SOCK_xxx 了.這樣數組實現了對PF_INET協議族下支持的協議類型,
以及協議類型下邊的協議進行了封裝,雖然事實上它們是一一對應的關系,不過理論上完全可能存在一對多的可能.
數組內,封裝的一個具體的協議,由 struct proto 結構來描述
以 TCP協議為例,TCP協議的 sokcet 操作函數都被封裝在這里了。
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = tcp_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v4_do_rcv,
.hash = tcp_v4_hash,
.unhash = tcp_unhash,
.get_port = tcp_v4_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
}
分配struct sock 看完了PF_INET的協議簇,協議類型和協議(也就是socket調用的三個參數)的封裝關系,它們通過了兩個數據結構
inet_protosw,struct proto來描述,被一個數組inetsw_array所封裝.接下來看它的初始化工作:
static struct list_head inetsw[SOCK_MAX];
static int __init inet_init(void)
{
……
/* Register the socket-side information for inet_create. */
for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
……
}
inetsw是一個數組,其每一個元素都是一個鏈表首部,前面一個循環初始化之.后一個循環就值得注意了,
也就是函數
916 void inet_register_protosw(struct inet_protosw *p)
917 {
918 struct list_head *lh;
919 struct inet_protosw *answer;
920 int protocol = p->protocol;
921 struct list_head *last_perm;
922
923 spin_lock_bh(&inetsw_lock);
925 if (p->type >= SOCK_MAX)
926 goto out_illegal;
928 /* If we are trying to override a permanent protocol, bail. */
929 answer = NULL;
930 last_perm = &inetsw[p->type];
931 list_for_each(lh, &inetsw[p->type]) {
932 answer = list_entry(lh, struct inet_protosw, list);
934 /* Check only the non-wild match. */
935 if (INET_PROTOSW_PERMANENT & answer->flags) {
936 if (protocol == answer->protocol)
937 break;
938 last_perm = lh;
939 }
941 answer = NULL;
942 }
943 if (answer)
944 goto out_permanent;
943 if (answer)
944 goto out_permanent;
945
946 /* Add the new entry after the last permanent entry if any, so that
947 * the new entry does not override a permanent entry when matched with
948 * a wild-card protocol. But it is allowed to override any existing
949 * non-permanent entry. This means that when we remove this entry, the
950 * system automatically returns to the old behavior.
951 */
952 list_add_rcu(&p->list, last_perm);
953 out:
954 spin_unlock_bh(&inetsw_lock);
955
956 synchronize_net();
957
958 return;
.....................
這個函數完成的工作就是把inetsw_array數組中相同的協議類型下邊的協議,
加入到inetsw對應的協議類型的鏈表中去,因為事實上一對一的關系,所以這個函數要簡單得多
因為不存在其它成員,所以每一次list_entry都為空值,所以不存在覆蓋和追加的情況,直接調用
list_add_rcu(&p->list, last_perm);
把協議類型節點(struct inet_protosw類型的數組的某個元素)添加到鏈表(鏈表首部本身是一個數組,
數組索引是協議對應的協議類型的值)的第一個成員.
OK,繞了這么大一圈子,了解了協議的封裝及鏈表的注冊. 現在回到inet_create中來
220 /*
221 * Create an inet socket.
222 */
223
224 static int inet_create(struct socket *sock, int protocol)
225 {
226 struct sock *sk;
227 struct list_head *p;
228 struct inet_protosw *answer;
229 struct inet_sock *inet;
230 struct proto *answer_prot;
231 unsigned char answer_flags;
232 char answer_no_check;
233 int try_loading_module = 0;
234 int err;
235
236 sock->state = SS_UNCONNECTED;
socket的初始狀態設置為“未連接”,這意味著面向連接的協議類型,如 tcp,在使用之前必須建立連接, 修改狀態位.
237
238 /* Look for the requested type/protocol pair. */
239 answer = NULL;
240 lookup_protocol:
241 err = -ESOCKTNOSUPPORT;
242 rcu_read_lock();
243 list_for_each_rcu(p, &inetsw[sock->type]) {
244 answer = list_entry(p, struct inet_protosw, list);
245
246 /* Check the non-wild match. */
247 if (protocol == answer->protocol) {
248 if (protocol != IPPROTO_IP)
249 break;
250 } else {
251 /* Check for the two wild cases. */
252 if (IPPROTO_IP == protocol) {
253 protocol = answer->protocol;
254 break;
255 }
256 if (IPPROTO_IP == answer->protocol)
257 break;
258 }
259 err = -EPROTONOSUPPORT;
260 answer = NULL;
261 }
這個循環根據socket(2)調用的protocol把之前在鏈表中注冊的協議節點找出來.
一個問題是,因為一一對應關系的存在,用戶態調用socket(2)的時候,常常第三個參數直接就置 0 了.
也就是這里protocol為 0.那內核又如何處理這一默認值呢?
也就是protocol != answer->protocol,而是被if (IPPROTO_IP == protocol) 所匹配了.
這樣將protocol置為鏈表中第一個協議,而當循環結束時,answer自然也是指向這個鏈表中的第一個注冊節點.
假設SOCK_STREAM下同時注冊了TCP和123,那么這里默認就取TCP了.當然如果把123在inetsw_array數組中的
位置調前,那么就 默認取123了.
將創建的socket的ops函數指針集指向具體協議類型的.例如創建的是SOCK_STREAM,
那么就指向了inet_stream_ops.
289 sock->ops = answer->ops;
answer_prot指針指向當前要創建的socket的協議類型下邊的協議,如上例它就是IPPROTO_TCP的tcp_prot結構
290 answer_prot = answer->prot;
291 answer_no_check = answer->no_check;
292 answer_flags = answer->flags;
293 rcu_read_unlock();
294
295 BUG_TRAP(answer_prot->slab != NULL);
接下來一個重要的工作,就是為 socket 分配一個sock,并初始化它
298 sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
299 if (sk == NULL)
300 goto out;
301
302 err = 0;
303 sk->sk_no_check = answer_no_check;
304 if (INET_PROTOSW_REUSE & answer_flags)
305 sk->sk_reuse = 1;
306
307 inet = inet_sk(sk);
308 inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;
309
310 if (SOCK_RAW == sock->type) {
311 inet->num = protocol;
312 if (IPPROTO_RAW == protocol)
313 inet->hdrincl = 1;
314 }
315
316 if (ipv4_config.no_pmtu_disc)
317 inet->pmtudisc = IP_PMTUDISC_DONT;
318 else
319 inet->pmtudisc = IP_PMTUDISC_WANT;
321 inet->id = 0;
322
323 sock_init_data(sock, sk);
324
325 sk->sk_destruct = inet_sock_destruct;
326 sk->sk_family = PF_INET;
327 sk->sk_protocol = protocol;
328 sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
329
330 inet->uc_ttl = -1;
331 inet->mc_loop = 1;
332 inet->mc_ttl = 1;
333 inet->mc_index = 0;
334 inet->mc_list = NULL;
335
336 sk_refcnt_debug_inc(sk);
337
338 if (inet->num) {
339 /* It assumes that any protocol which allows
340 * the user to assign a number at socket
341 * creation time automatically
342 * shares.
343 */
344 inet->sport = htons(inet->num);
345 /* Add to protocol hash chains. */
346 sk->sk_prot->hash(sk);
347 }
348
349 if (sk->sk_prot->init) {
350 err = sk->sk_prot->init(sk);
351 if (err)
352 sk_common_release(sk);
353 }
354 out:
355 return err;
356 out_rcu_unlock:
357 rcu_read_unlock();
358 goto out;
359 }
雖然create的代碼就到這兒了,不過要說清楚sk的分配,還得費上大力氣.每一個 Socket 套接字,
都有一個對應的struct socket結構來描述(內核中一般使用名稱為sock),但是同時又有一個
struct sock結構(內核中一般使用名稱為 sk).兩者之間是一一對應的關系.在后面的sock_init_data函數中可以看到
sk->sk_socket=sock;
sock->sk=sk;
這樣的代碼.
socket結構和sock結構實際上是同一個事物的兩個方面.不妨說socket結構是面向進程和系統調用界面的側面,
而sock結構則是面向底層驅動程序的側面.設計者把socket套接字中與文件系統關系比較密切的那一部份放在
socket結構中而把與通信關系比較密切的那一部份,則單獨成為一個數結結構,那就是sock結構.
由于這兩部份邏輯上本來就是一體的,所以要通過指針互相指向對方形成一對一的關系.
再暫時回到inet_init中來,初始化工作中有如下代碼:
1262 rc = proto_register(&tcp_prot, 1);
1263 if (rc)
1264 goto out;
1265
1266 rc = proto_register(&udp_prot, 1);
1267 if (rc)
1268 goto out_unregister_tcp_proto;
1269
1270 rc = proto_register(&raw_prot, 1);
1271 if (rc)
1272 goto out_unregister_udp_proto;
這里為每個protocol都調用了proto_register函數,其重要功能之一就是根據協議的obj_size成員的大小,
為協議創建高速緩存.
1701 static DEFINE_RWLOCK(proto_list_lock);
1702 static LIST_HEAD(proto_list);
1703
1704 int proto_register(struct proto *prot, int alloc_slab)
1705 {
1706 char *request_sock_slab_name = NULL;
1707 char *timewait_sock_slab_name;
1708 int rc = -ENOBUFS;
1709
1710 if (alloc_slab) {
可以看到函數最重要的功能就是根據prot的obj_size成員的大小為協議創建高速緩存
1711 prot->slab = kmem_cache_create(prot->name, prot->obj_size, 0,
1712 SLAB_HWCACHE_ALIGN, NULL, NULL);
1713
1714 if (prot->slab == NULL) {
1715 printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",
1716 prot->name);
1717 goto out;
1718 }
1719
順便看到它的另一個重要的功能是維護一個以proto_list為首的鏈表
1758 write_lock(&proto_list_lock);
1759 list_add(&prot->node, &proto_list);
1760 write_unlock(&proto_list_lock);
這里要注意的是prot->obj_size的大小,它它非僅僅是一個sk的大小,以 TCP為例:
.obj_size = sizeof(struct tcp_sock)。稍后再來分析這個東東
回到inet_create()函數中來,其調用sk_alloc()分配一個sk
sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
840 struct sock *sk_alloc(int family, gfp_t priority,
841 struct proto *prot, int zero_it)
842 {
843 struct sock *sk = NULL;
844 kmem_cache_t *slab = prot->slab;
845
846 if (slab != NULL)
847 sk = kmem_cache_alloc(slab, priority);
848 else
849 sk = kmalloc(prot->obj_size, priority);
850
851 if (sk) {
852 if (zero_it) {
853 memset(sk, 0, prot->obj_size);
854 sk->sk_family = family;
855 /*
856 * See comment in struct sock definition to understand
857 * why we need sk_prot_creator -acme
858 */
859 sk->sk_prot = sk->sk_prot_creator = prot;
860 sock_lock_init(sk);
861 }
862
863 if (security_sk_alloc(sk, family, priority))
864 goto out_free;
865
866 if (!try_module_get(prot->owner))
867 goto out_free;
868 }
869 return sk;
870
871 out_free:
872 if (slab != NULL)
873 kmem_cache_free(slab, sk);
874 else
875 kfree(sk);
876 return NULL;
877 }
在之前創建的高速緩存中申請分配一個slab緩存項并清零,然后設置協議族,并把sk中的sk_prot與對應的協議關聯起來
初始化sk 分配完成sk后另一個重要的功能就是初始化它,sk的成員相當復雜,其主要的初始化工作是在函數sock_init_data()
中完成的.
1477 void sock_init_data(struct socket *sock, struct sock *sk)
1478 {
1479 skb_queue_head_init(&sk->sk_receive_queue);
1480 skb_queue_head_init(&sk->sk_write_queue);
1481 skb_queue_head_init(&sk->sk_error_queue);
sock結構中有三個重要的雙向隊列分別是sk_receive_queue,sk_write_queue和sk_error_queue
從它們的名字就可以看出來其作用了
隊列并非采用通用的list_head來維護而是使用skb_buffer隊列
// 109 struct sk_buff_head {
// 110 /* These two members must be first. */
// 111 struct sk_buff *next;
// 112 struct sk_buff *prev;
// 113
// 114 __u32 qlen;
// 115 spinlock_t lock;
// 116 };
這樣隊列中指向的每一個skb_buffer就是一個數據包,分別是接收、發送和投遞錯誤
剩余的就是初始化其它成員變量了,后面再來專門分析這些成員的作用
1482 #ifdef CONFIG_NET_DMA
1483 skb_queue_head_init(&sk->sk_async_wait_queue);
1484 #endif
1485
1486 sk->sk_send_head = NULL;
1487
1488 init_timer(&sk->sk_timer);
1489
1490 sk->sk_allocation = GFP_KERNEL;
1491 sk->sk_rcvbuf = sysctl_rmem_default;
1492 sk->sk_sndbuf = sysctl_wmem_default;
1493 sk->sk_state = TCP_CLOSE;
1494 sk->sk_socket = sock;
1495
1496 sock_set_flag(sk, SOCK_ZAPPED);
1497
1498 if(sock)
1499 {
1500 sk->sk_type = sock->type;
1501 sk->sk_sleep = &sock->wait;
1502 sock->sk = sk;
1503 } else
1504 sk->sk_sleep = NULL;
1505
1506 rwlock_init(&sk->sk_dst_lock);
1507 rwlock_init(&sk->sk_callback_lock);
1508 lockdep_set_class(&sk->sk_callback_lock,
1509 af_callback_keys + sk->sk_family);
1510
1511 sk->sk_state_change = sock_def_wakeup;
1512 sk->sk_data_ready = sock_def_readable;
1513 sk->sk_write_space = sock_def_write_space;
1514 sk->sk_error_report = sock_def_error_report;
1515 sk->sk_destruct = sock_def_destruct;
1516
1517 sk->sk_sndmsg_page = NULL;
1518 sk->sk_sndmsg_off = 0;
1519
1520 sk->sk_peercred.pid = 0;
1521 sk->sk_peercred.uid = -1;
1522 sk->sk_peercred.gid = -1;
1523 sk->sk_write_pending = 0;
1524 sk->sk_rcvlowat = 1;
1525 sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
1526 sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
1527
1528 sk->sk_stamp.tv_sec = -1L;
1529 sk->sk_stamp.tv_usec = -1L;
1530
1531 atomic_set(&sk->sk_refcnt, 1);
1532 }
inet_create函數中除了初始化sk成員的值還有一部份代碼是初始化一個inet的東東
307 inet = inet_sk(sk);
308 inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;
inet是一個struct inet_sock結構類型來看它的定義
struct inet_sock {
/* sk and pinet6 has to be the first two members of inet_sock */
struct sock sk;
……
}
我們說sock是面向用戶態調用而sk是面向內核驅動調用的,那sk是如何與協議棧交互的呢?
對于每一個類型的協議,為了與 sk 聯系起來都定義了一個struct XXX_sock結構XXX是協議名
struct tcp_sock {
/* inet_sock has to be the first member of tcp_sock */
struct inet_sock inet;
int tcp_header_len; /* Bytes of tcp header to send */
……
}
struct udp_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
int pending; /* Any pending frames ? */
unsigned int corkflag; /* Cork is required */
__u16 encap_type; /* Is this an Encapsulation socket? */
/*
* Following member retains the infomation to create a UDP header
* when the socket is uncorked.
*/
__u16 len; /* total length of pending frames */
};
struct raw_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
struct icmp_filter filter;
};
很明顯它們的結構定義是“af_inet一般屬性+自己的私有屬性”, 因為它們的第一個成員總是inet
現在回頭來找一下起初在af_inet.c中封裝協議注冊的時候size成員, 對于 tcp 而言:
.obj_size = sizeof(struct tcp_sock),
其它協議類似.
以obj_size來確定每個slab緩存項分配的大小,所以我們就可說每次申請分配的實際上是一個struct XXX_sock
結構大小的結構.因為都是定義于上層結構的第一個成員,可以使用強制類型轉換來使用這塊分配的內存空間.
例如inet = inet_sk(sk);
static inline struct inet_sock *inet_sk(const struct sock *sk)
{
return (struct inet_sock *)sk;
}
struct tcp_sock *tp = tcp_sk(sk);
static inline struct tcp_sock *tcp_sk(const struct sock *sk)
{
return (struct tcp_sock *)sk;
}
OK,inet_create()運行完,一個socket套接字基本上就創建完畢了,剩下的就是與文件系統掛鉤,回到最初的sys_socket()
函數中來,它在調用完sock_create()后,緊接著調用sock_map_fd()函數
422 int sock_map_fd(struct socket *sock)
423 {
424 struct file *newfile;
425 int fd = sock_alloc_fd(&newfile);
426
427 if (likely(fd >= 0)) {
428 int err = sock_attach_fd(sock, newfile);
429
430 if (unlikely(err < 0)) {
431 put_filp(newfile);
432 put_unused_fd(fd);
433 return err;
434 }
435 fd_install(fd, newfile);
436 }
437 return fd;
438 }
這個函數的核心思想在一開始就已經分析過了.從進程的角度來講一個socket套接字就是一個特殊的已打開的文件.
前面分配好一個socket 后,這里要做的就是將它與文件系統拉上親戚關系.
首先獲取一個空閑的文件描述符號和file結構,然后在文件系統中分配一個目錄項(d_alloc),使其指向已經分配的inode節
點(d_add),然后把其目錄項掛在sockfs文件系統的根目錄之下,并且把目錄項的指針d_op設置成
指向sockfs_dentry_operati,這個數據結構通過函數指針提供他與文件路徑有關的操作.
static struct dentry_operations sockfs_dentry_operations = {
.d_delete = sockfs_delete_dentry,
};
最后一步就是將file結構中的f_op和sock結構中的i_fop都指向socket_file_ops,它是一個函數指針集,
指向了socket面向文件系統的用戶態調用的一些接口函數.
static struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.readv = sock_readv,
.writev = sock_writev,
.sendpage = sock_sendpage
};
OK,到這里整個socket套接字的創建工作就宣告完成了
寫到這里,可以為 socket 的創建下一個小結了:
- 所謂創建socket,對內核而言最重要的工作就是分配sock與sk
- sock面向上層系統調用,主要是與文件系統交互.通過進程的current指針的files,結合創建socket
時返回的文件描符述,可以找到內 核中對應的struct file,再根據file的f_dentry可以找到對應的目
錄項,而目錄項struct dentry中,有d_inode指針,指向與sock封裝在一起的inode.sock又與
sk指針互指一一對應.在這串結構中有兩個重要的函數集指針,一個是文件系統struct file中的
f_op指針,它指向了對應的用戶態調用的read,write等操調用,但不支持open,
另一個是struct socket結構,即sock的ops指針,它在inet_create()中被置為
sock->ops = answer->ops指向具體協議類型的ops
例如inet_stream_ops,inet_dgram_ops或者是inet_sockraw_ops等等
它用來支持上層的socket的其它API調用
- sk面向內核協議棧,協議棧與它的接口數據結構是struct protoname_sock,該結構中包含了一般性
的inet結構和自己的私有成員,struct inet_sock的第一個成員就是一個 sk 指針,而分配的sk實
際上空間大小是struct protoname_sock,所以這三者可以通過強制類型轉換來獲取需要的指針
- 由于水平有限,文件系統的一些細節被我跳過了,sk 中的大多數成員變量的作用,也被我跳出過了.
呵呵,還好,終于還是把這塊流程給初步分析出來了.另外當時寫的時候,沒有想到會寫這么長,
大大超出了每貼的字限制。所以,每個小節內容跟標題可能會有點對不上號。