最近在開發一個內核模塊,主要的功能是在集群的節點之間遷移TCP連接,從而實現基於內容的調用。因此,花了很多時間和精力研究linux的網絡協議棧,但是還是有很多地方沒有串起來。網絡協議棧是為用戶層的應用開發服務的,因此決定從用戶層常用的編程接口入手,通過學習這些接口的實現,來理清整個過程,加深對網絡協議棧的理解。
網絡編程通常是基於客戶端-服務端模型。首先啟動服務器,稍後的某個時刻啟動客戶,它要連接到此服務器上。假設客戶給服務器發送一個請求,服務器處理這個請求,並且給客戶發送一個響應。為了執行網絡I/O,第一件事情就是調用socket()函數來創建套接字。socket()函數對應的系統調用時sys_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);
/*
* 從linux 2.6.27開始,參數type除了指定套接字類型外,還
* 可以通過或運算來指定SOCK_CLOEXEC和SOCK_NONBLOCK標志
* 來改變套接字的行為。可以通過man socket命令查看詳情。
* 首先通過SOCK_TYPE_MASK掩碼來獲取type中設置的標志(當然
* 也可能沒有設置)。如果type中有標志設置,但是不是
* SOCK_CLOEXEC和SOCK_NONBLOCK對應的位,則返回EINVAL錯誤。
*/
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
/*
* 獲取套接字類型
*/
type &= SOCK_TYPE_MASK;
/*
* 如果SOCK_NONBLOCK不等於O_NONBLOCK並且設置了SOCK_NONBLOCK
* 標志,則將flags中的SOCK_NONBLOCK替換為O_NONBLOCK。
*/
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
/*
* 根據協議族類型和套接字類型創建套接字。
*/
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
/*
* 創建一個新的文件描述符,將新創建的
* socket實例關聯上去。
*/
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}