linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)
Linux協議棧代碼閱讀筆記(一)
(基于linux-2.6.21.7)
(一)用戶態通過諸如下面的C庫函數訪問協議棧服務
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr,? socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr,? socklen_t addrlen);
……
(二)上述C庫函數如何與內核交互
C庫代碼準備好相應的工作后(例如,設置系統調用號啦、參數構造啦、棧啦、寄存器設置啦),通過系統調用指令,進入內核態。從內核返回后,C庫函數再做相應的善后工作,然后將結果返回給用戶程序。
這部分代碼,不同架構的處理器,有不同的實現。
可以參考Glibc的源碼。
下面以X86為例,簡要描述一下這個過程。
另外,后續的內容,如無特殊說明,均是針對X86架構。
對于X86架構,一般是通過“int? $0x80”指令進入內核,即觸發128號中斷。
內核中斷向量表的定義如下(源碼文件archi386kernel Traps.c):
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
函數trap_init(源碼文件archi386kernel Traps.c)對此表進行了初始化。
其中,對128號中斷的初始化方式為:
set_system_gate(SYSCALL_VECTOR,&system_call);
SYSCALL_VECTOR宏的值為0x80,即128。
因此,128號中斷,即對應中斷向量表的第128個條目,其中斷服務程序為system_call這段代碼。
system_call這段代碼,是用匯編實現的。
其代碼在archi386kernelentry.S中。
這個代碼,主要是根據系統調用號,索引系統調用表中的一個條目進行執行。
(三)內核態如何處理用戶的網絡通訊請求
上一步,C庫發起了系統調用,進入了內核128號中斷,即系統調用軟中斷。
128號中斷處理程序,根據系統調用號,進入系統調用表的相應表目。系統調用表如下,每個表目是一個函數指針。(源碼文件:archi386kernel syscall_table.S)
ENTRY(sys_call_table)
.long sys_restart_syscall?/* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open??/* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink?/* 10 */
.long sys_ni_syscall?/* old lock syscall holder */
…
.long sys_statfs
.long sys_fstatfs?/* 100 */
.long sys_ioperm
.long sys_socketcall
.long sys_syslog
…
.long sys_tee???/* 315 */
.long sys_vmsplice
.long sys_move_pages
.long sys_getcpu
.long sys_epoll_pwait
對于上述的幾個socket庫函數,全部對應同一個系統調用,即102號系統調用,即sys_socketcall函數。
sys_socketcall函數如何處理用戶的socket請求
所有的socket相關的C庫函數,如socket、bind、connect、listen、accept、send、recv、sendto、sendmsg等,全部都屬于同一個系統調用(即102號系統調用),全部由這一個函數處理。
此函數的代大致如下(源碼文件netSocket.c)
long sys_socketcall(int call, unsigned long __user *args)
{
……
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;
}
(四)socket的創建
使用上述C庫函數,第一步當然是使用socket庫函數創建一個socket。后續的操作,則都是針對這一步創建出的socket而進行了。因此,我們先來看看socket的創建。
創建socket,主要就是分配一個struct socket結構變量,并適當的初始化。
這個工作由sys_socket 通過如下形式調用sock_create完成。其中的參數family、type、protocol都是用戶調用socket庫函數時傳入的。
sock_create(family, type, protocol, &sock);
sock_create成功返回后,就創建了一個struct socket結構變量。
后續的數據收發,狀態維護,差不多都基于這個結構變量了。
struct socket結構如下(源碼文件:netSocket.c)
struct socket {
socket_state??state;
unsigned long??flags;
const struct proto_ops?*ops;
struct fasync_struct?*fasync_list;
struct file??*file;
struct sock??*sk;
wait_queue_head_t?wait;
short???type;
};
這個結構的初始化,主要依賴于family, type, protocol這三項信息,查找到相應的協議棧模塊,填充struct socket結構中相應的成員。
這個初始化過程的層次比較深,涉及較多細節。在下也沒有深入閱讀理解。
不過,我們可以簡單看看大的數據結構。
內核中的協議棧,也是按family, type, protocol這三項信息進行了組織。
a)?固定的協議信息(netipv4 Af_inet.c)
Socket的創建,需要根據family, type, protocol確定一個協議。
內核中固定的協議信息,都在inetsw_array中進行了登記。
Static? struct? inet_protosw? inetsw_array[] =
{
{
.type =?????? SOCK_STREAM,
.protocol =?? IPPROTO_TCP,
.prot =?????? &tcp_prot,
.ops =??????? &inet_stream_ops,
.capability = -1,
.no_check =?? 0,
.flags =????? INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type =?????? SOCK_DGRAM,
.protocol =?? IPPROTO_UDP,
.prot =?????? &udp_prot,
.ops =??????? &inet_dgram_ops,
.capability = -1,
.no_check =?? UDP_CSUM_DEFAULT,
.flags =????? INET_PROTOSW_PERMANENT,
},
{
.type =?????? SOCK_RAW,
.protocol =?? IPPROTO_IP,?/* wild card */
.prot =?????? &raw_prot,
.ops =??????? &inet_sockraw_ops,
.capability = CAP_NET_RAW,
.no_check =?? UDP_CSUM_DEFAULT,
.flags =????? INET_PROTOSW_REUSE,
}
};
數組中的每個元素,對應一個協議。
其中,每個元素的prot成員,指向相應的協議(如TCP、UDP等)提供的proto結構變量,其中含有大量的函數指針,指向相應的函數,這些函數用于實現各種協議的交互、收發、控制等。這樣一來,每一個協議,在這個數組中都能查到了,如何操作使用他們,也都有了相應的信息。
每個協議的ops成員,也指向一個proto_ops結構變量,其中也包含大量函數指針。這些操作,可以認為是包裝后的,更抽象的操作。是更接近用戶的socket操作函數。例如,這些操作函數包括:bind、connect、listen等。
具體包含哪些操作,是由family, type決定的(例如,family=PF_INET,type=SOCK_DGRAM時,則使用sendmsg接收數據)。對于多個協議,即使實現不同,但是如果他們的family, type相同,那么對于用戶來說,操作都是一樣的。
初始化完成后,最終的情況是:
a)??? socket.sk.__sk_common.skc_prot指向具體的協議提供的proto結構變量。內含大量函數,實現具體的協議操作。
b)??? socket. ops 指向相應的proto_ops結構變量,實現各種socket操作。
c)?? 最終的流程是:socket. ops包裝了socket操作,但是socket. ops中的函數是利用socket.sk.__sk_common.skc_prot中的函數完成最終的操作。
最后,inetsw_array中的元素(協議),不是遍歷查找的。他們被按照type分類組織到中inetsw了。Inetsw包含了PF_INET協議族中的全部協議。
Inetsw是個鏈表數組,定義如下(源碼文件netipv4 Af_inet.c)。
static? struct? list_head? inetsw[SOCK_MAX];
(五)使用socket進行收發
上一步已經完成了相關的初始化工作。
后續的建鏈、收發、斷鏈等操作,也還都是由socketcall這一個函數完成的。
有興趣的朋友可以自己研習研習相關的代碼了。
在下對這方面也沒有深入閱讀理解:)
總結
以上是生活随笔為你收集整理的linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下mknod的作用,Linux
- 下一篇: linux io体系结构,Linux I