linux socket bind 内核详解,Socket与系统调用深度分析(示例代码)
1、 什么是系統調用
操作系統通過系統調用為運行于其上的進程提供服務。當用戶態進程發起一個系統調用,?CPU?將切換到?內核態?并開始執行一個?內核函數?。 內核函數負責響應應用程序的要求,例如操作文件、進行網絡通訊或者申請內存資源等。在Linux中系統調用是有Linux內核提供的各種功能服務,為了便于調用Linux提供了一個底層C語言庫libc(glibc是GUN版本的libc,其他類似庫還有uclibc、klibc),目前glibc是linux標準函數庫,這些都對系統系統接口打包成了標準C函數,這些函數一般就成為系統調用。系統調用可以通過syscall()函數發起,或者調用每個對應的一個C函數,這些函數定義在 或者 頭文件中。Linux系統中通過軟中斷0x80調用實現控制權轉移給內核,內容執行完成后返回結果。所有系統調用在linux內核的源文件目錄" arch/x86/kernel"中的各種文件中定義。
內核實現了很多的系統調用函數, 這些函數會有自己的名字, 以及編號. 用戶要調用系統調用, 首先需要使用 ?int 0x80 觸發軟中斷. 這個指令會在0x80代表十進制的128, 所以這個指令會找終端向量表的128項, 找到以后, 跳轉到相應的函數, 這個處理函數就是system_call. 這個中斷向量表的設置, 是在操作系統初始化的時候, 通過trap_init()函數設置的. 在進入中斷處理函數system_call以后, 首先要進行一般的中斷處理流程, 即保護現場. 這個體現在指令SAVE_ALL(494行)上. 然后有一個重要的函數調用 call *sys_call_table(,%eax,4). 這個表示查找系統調用函數表(), 然后調用相應的系統調用函數. 對于32位的系統, 函數位置存了4個Bytes, eax中是我們傳入的系統調用號, 所以4*eax,就可以找到對應的系統調用函數, 執行函數. 之后還需要進行返回值的保存等工作.
2、 socket相關系統調用的內核處理函數深入分析
1)??????? 因為上一次實驗是在shiyanlou環境下完成,所以此次實驗需要重新下載下載linux-5.0.1的內核并編譯內核,并制作根文件系統。
2)
解Linux內核中socket接口層的代碼,找出112號系統調用socketcall的內核處理函數sys_socketcall,理解socket接口函數編號和對應的socket接口內核處理函數 通過前面構建MenuOS實驗環境使得我們有方法跟蹤socket接口通過系統調用進入內核代碼,在我們的環境中socket接口通過112號系統調用socketcall進入內核的
System call vectors.
Argument checking cleaned up. Saved 20% in size.
This function doesn‘t need to set the kernel lock because
it is set by the callees.
2490 */
2491
2492SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
2493{
...
2517 switch (call) {
2518 case SYS_SOCKET:
2519?????? err = sys_socket(a0, a1, a[2]);
2520?????? break;
2521 case SYS_BIND:
2522?????? err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
2523?????? break;
2524 case SYS_CONNECT:
2525?????? err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
2526?????? break;
2527 case SYS_LISTEN:
2528?????? err = sys_listen(a0, a1);
2529?????? break;
2530 case SYS_ACCEPT:
2531?????? err = sys_accept4(a0, (struct sockaddr __user *)a1,
2532???????????????????? ? (int __user *)a[2], 0);
2533?????? break;
2534 case SYS_GETSOCKNAME:
2535?????? err =
2536?????? ??? sys_getsockname(a0, (struct sockaddr __user *)a1,
2537???????????????????? ??? (int __user *)a[2]);
2538?????? break;
2539 case SYS_GETPEERNAME:
2540?????? err =
2541?????? ??? sys_getpeername(a0, (struct sockaddr __user *)a1,
2542???????????????????? ??? (int __user *)a[2]);
2543?????? break;
2544 case SYS_SOCKETPAIR:
2545?????? err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
2546?????? break;
2547 case SYS_SEND:
2548?????? err = sys_send(a0, (void __user *)a1, a[2], a[3]);
2549?????? break;
2550 case SYS_SENDTO:
2551?????? err = sys_sendto(a0, (void __user *)a1, a[2], a[3],
2552???????????????????? (struct sockaddr __user *)a[4], a[5]);
2553?????? break;
2554 case SYS_RECV:
2555?????? err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
2556?????? break;
2557 case SYS_RECVFROM:
2558?????? err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
2559???????????????????? ?? (struct sockaddr __user *)a[4],
2560???????????????????? ?? (int __user *)a[5]);
2561?????? break;
2562 case SYS_SHUTDOWN:
2563?????? err = sys_shutdown(a0, a1);
2564?????? break;
2565 case SYS_SETSOCKOPT:
2566?????? err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
2567?????? break;
2568 case SYS_GETSOCKOPT:
2569?????? err =
2570?????? ??? sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
2571???????????????????? ?? (int __user *)a[4]);
2572?????? break;
2573 case SYS_SENDMSG:
2574?????? err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
2575?????? break;
2576 case SYS_SENDMMSG:
2577?????? err = sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3]);
2578?????? break;
2579 case SYS_RECVMSG:
2580?????? err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);
2581?????? break;
2582 case SYS_RECVMMSG:
2583?????? err = sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3],
2584???????????????????? ?? (struct timespec __user *)a[4]);
2585?????? break;
2586 case SYS_ACCEPT4:
2587?????? err = sys_accept4(a0, (struct sockaddr __user *)a1,
2588???????????????????? ? (int __user *)a[2], a[3]);
2589?????? break;
2590 default:
2591?????? err = -EINVAL;
2592?????? break;
2593 }
2594 return err;
2595}
2596
在我們的實驗環境中,socket接口的調用是通過給socket接口函數編號的方式通過112號系統調用來處理的。這些socket接口函數編號的宏定義見/linux-3.18.6/include/uapi/linux/net.h#26
26#define SYS_SOCKET 1??????????? /* sys_socket(2)?????????? */
27#define SYS_BIND???? 2??????????? /* sys_bind(2)????????????? */
28#define SYS_CONNECT??? 3??????????? /* sys_connect(2)???????? */
29#define SYS_LISTEN? 4??????????? /* sys_listen(2)???????????? */
30#define SYS_ACCEPT 5??????????? /* sys_accept(2)?????????? */
31#define SYS_GETSOCKNAME? 6??????????? /* sys_getsockname(2)??????? */
32#define SYS_GETPEERNAME?? 7??????????? /* sys_getpeername(2)??????? */
33#define SYS_SOCKETPAIR 8??????????? /* sys_socketpair(2)???????????? */
34#define SYS_SEND??? 9??????????? /* sys_send(2)????????????? */
35#define SYS_RECV??? 10????????? /* sys_recv(2)?????????????? */
36#define SYS_SENDTO????? 11????????? /* sys_sendto(2)?????????? */
37#define SYS_RECVFROM? 12????????? /* sys_recvfrom(2)??????? */
38#define SYS_SHUTDOWN 13????????? /* sys_shutdown(2)???????????? */
39#define SYS_SETSOCKOPT????? 14????????? /* sys_setsockopt(2)??????????? */
40#define SYS_GETSOCKOPT???? 15????????? /* sys_getsockopt(2)?????????? */
41#define SYS_SENDMSG??? 16????????? /* sys_sendmsg(2)??????? */
42#define SYS_RECVMSG??? 17????????? /* sys_recvmsg(2)???????? */
43#define SYS_ACCEPT4???? 18????????? /* sys_accept4(2)???????? */
44#define SYS_RECVMMSG 19????????? /* sys_recvmmsg(2)???????????? */
45#define SYS_SENDMMSG 20????????? /* sys_sendmmsg(2)??????????? */
接下來我們根據TCP server程序調用socket接口的順序依次看一下socket、bind、listen、accept等socket接口的內核處理函數。
socket接口函數的內核處理函數sys_socket
1377SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
1378{
1379 int retval;
1380 struct socket *sock;
...
1397 retval = sock_create(family, type, protocol, &sock);
...
socket接口函數主要作用是建立socket套接字描述符,Unix-like系統非常成功的設計是將一切都抽象為文件,socket套接字也是一種特殊的文件,sock_create內部就是使用文件系統中的數據結構inode為socket套接字分配了文件描述符。socket套接字與普通的文件在內部存儲結構上是一致的,甚至文件描述符和套接字描述符是通用的,但是套接字和文件還是特殊之處,因此定義了結構體struct socket,struct socket的結構體定義見/linux-3.18.6/include/linux/net.h#105,具體代碼摘錄如下:
95/**
96 *? struct socket - general BSD socket
97 *? @state: socket state (%SS_CONNECTED, etc)
98 *? @type: socket type (%SOCK_STREAM, etc)
99 *? @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
100 *? @ops: protocol specific socket operations
101 *? @file: File back pointer for gc
102 *? @sk: internal networking protocol agnostic socket representation
103 *? @wq: wait queue for several uses
104 */
105struct socket {
106? socket_state???????? state;
107
108? kmemcheck_bitfield_begin(type);
109? short??????????????????? type;
110? kmemcheck_bitfield_end(type);
111
112? unsigned long???????????? flags;
113
114? struct socket_wq __rcu *wq;
115
116? struct file?????? *file;
117? struct sock??????????? *sk;
118? const struct proto_ops *ops;
119};
sock_create內部還根據指定的網絡協議族family和protocol初始化了相關協議的處理接口到結構體struct socket中,結構體struct socket在后續的分析和理解中還會用到,這里簡單略過用到時再具體研究。
bind接口函數的內核處理函數sys_bind
1519/*
1520 *???? Bind a name to a socket. Nothing much to do here since it‘s
1521 *???? the protocol‘s responsibility to handle the local address.
1522 *
1523 *???? We move the socket address to kernel space before we call
1524 *???? the protocol layer (having also checked the address is ok).
1525 */
1526
1527SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
1528{
1529 struct socket *sock;
1530 struct sockaddr_storage address;
1531 int err, fput_needed;
1532
1533 sock = sockfd_lookup_light(fd, &err, &fput_needed);
1534 if (sock) {
1535?????? err = move_addr_to_kernel(umyaddr, addrlen, &address);
1536?????? if (err >= 0) {
1537????????????? err = security_socket_bind(sock,
1538?????????????????????????????????? ?? (struct sockaddr *)&address,
1539?????????????????????????????????? ?? addrlen);
1540????????????? if (!err)
1541???????????????????? err = sock->ops->bind(sock,
1542?????????????????????????????????? ????? (struct sockaddr *)
1543?????????????????????????????????? ????? &address, addrlen);
1544?????? }
1545?????? fput_light(sock->file, fput_needed);
1546 }
1547 return err;
1548}
如上代碼可以看到,move_addr_to_kernel將用戶態的struct sockaddr結構體數據拷貝到內核里的結構體變量struct sockaddr_storage address,然后使用sock->ops->bind將該網絡地址綁定到之前創建的套接字。這里用到了通過套接字描述符fd找到之前分配的套接字struct socket *sock,利用該套接字中的成員const struct proto_ops *ops找到對應網絡協議的bind函數指針即sock->ops->bind。這里即是一個socket接口層通往具體協議處理的接口。
listen接口函數的內核處理函數sys_listen
1550/*
1551 *???? Perform a listen. Basically, we allow the protocol to do anything
1552 *???? necessary for a listen, and if that works, we mark the socket as
1553 *???? ready for listening.
1554 */
1555
1556SYSCALL_DEFINE2(listen, int, fd, int, backlog)
1557{
1558 struct socket *sock;
1559 int err, fput_needed;
1560 int somaxconn;
1561
1562 sock = sockfd_lookup_light(fd, &err, &fput_needed);
1563 if (sock) {
1564?????? somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
1565?????? if ((unsigned int)backlog > somaxconn)
1566????????????? backlog = somaxconn;
1567
1568?????? err = security_socket_listen(sock, backlog);
1569?????? if (!err)
1570????????????? err = sock->ops->listen(sock, backlog);
1571
1572?????? fput_light(sock->file, fput_needed);
1573 }
1574 return err;
1575}
listen接口的主要作用是通知網絡底層開始監聽套接字并接收網絡連接請求,listen接口正常處理完TCP服務就已經啟動了,只是這時網絡連接請求都會暫存在緩沖區,等調用accept建立連接,listen接口函數的參數backlog就是用來配置支持的連接數。
我們發現實際處理的工作是由sock->ops->listen完成的,這也是一個socket接口層通往具體協議處理的接口。
accept接口函數的內核處理函數sys_accept
內核處理函數sys_accept的主要功能是調用sys_accept4完成的,sys_accept4見/linux-3.18.6/net/socket.c#1589,具體代碼摘錄如下:
1577/*
1578 *???? For accept, we attempt to create a new socket, set up the link
1579 *???? with the client, wake up the client, then return the new
1580 *???? connected fd. We collect the address of the connector in kernel
1581 *???? space and move it to user at the very end. This is unclean because
1582 *???? we open the socket then return an error.
...
1589SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
1590?????? int __user *, upeer_addrlen, int, flags)
1591{
...
1608 newsock = sock_alloc();
...
1612 newsock->type = sock->type;
1613 newsock->ops = sock->ops;
...
1621 newfd = get_unused_fd_flags(flags);
...
1627 newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
...
1639 err = sock->ops->accept(sock, newsock, sock->file->f_flags);
...
1643 if (upeer_sockaddr) {
1644?????? if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
...
1649?????? err = move_addr_to_user(&address,
...
1657 fd_install(newfd, newfile);
1658 err = newfd;
...
1668}
在TCP的服務器端通過socket函數創建的套接字描述符只是用來監聽客戶連接請求,accept函數內部會為每一個請求連接的客戶創建一個新的套接字描述符專門負責與該客戶端進行網絡通信,并將該客戶的網絡地址和端口等地址信息返回到用戶態。這里涉及更多的網絡協議處理的接口如sock->ops->accept、ewsock->ops->getname。
send和recv接口的內核處理函數類似也是通過調用網絡協議處理的接口來將具體的工作交給協議層來完成,比如sys_recv最終調用了sock->ops->recvmsg,sys_send最終調用了sock->ops->sendmsg,但send和recv接口涉及網絡數據流,是理解網絡部分的關鍵內容
總結
以上是生活随笔為你收集整理的linux socket bind 内核详解,Socket与系统调用深度分析(示例代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 加速秒传统燃油 法拉利为何不愿推出纯电跑
- 下一篇: d3设置line长度_万物皆可Embed