Linux 用戶態與內核態的交互【轉載】

Linux 用戶態與內核態的交互
  在 Linux 2.4 版之後版本的內核中,幾乎所有的中斷過程與用戶態進程的通訊都是使用 netlink 套接字實現的,例如iprote2網絡管理工具,它與內核的交互就所有使用了netlink,著名的內核包過濾框架Netfilter在與用戶空間的通 讀,也在最新版本中改變爲netlink,無疑,它將是Linux用戶態與內核態交流的主要方法之一。它的通訊依據是一個對應於進程的標識,通常定爲該進 程的 ID。當通訊的一端處於中斷過程時,該標識爲 0。當使用 netlink 套接字進行通訊,通訊的雙方都是用戶態進程,則使用方法相似於消息隊列。但通訊雙方有一端是中斷過程,使用方法則不一樣。netlink 套接字的最大特色是對中斷過程的支持,它在內核空間接收用戶空間數據時再也不須要用戶自行啓動一個內核線程,而是經過另外一個軟中斷調用用戶事先指定的接收函 數。
《UNIX Network Programming Volume 1 - 3rd Edition》第18章
講到BSD UNIX系統中routing socket的應用,這種套接字是按下面方式生成的:
rt_socket = socket(AF_ROUTE, SOCK_RAW, 0);
然 後就能夠用它跟內核交互,進行網絡環境管理的操做,如讀取/設置/刪除路由表信息,更改網關等等,但書中所列代碼只在4.3BSD及之後版本的原始 UNIX系統下可用,Linux雖然實現了AF_ROUTE族套接字,但用法卻徹底不一樣。因爲網上這方面知識的資料想對匱乏,現對Linux下 routing socket的使用作一介紹。
因爲我如今在Magic Linux1.0下工做,因此如下的講解所有基於2.4.10內核。Linux從v2.2開始引入這一機制,所以能夠確定從v2.2到v2.4的內核都是適用的,更新的v2.6我沒有試過。

Linux下雖然也有AF_ROUTE族套接字,可是這個定義只是個別名,請看
/usr/include/linux/socket.h, line 145:
#define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
可 見在Linux內核當中真正實現routing socket的是AF_NETLINK族套接字。AF_NETLINK族套接字像一個鏈接用戶空間和內核的雙工管道,經過它,用戶進程能夠修改內核運行參 數、讀取和設置路由信息、控制特定網卡的up/down狀態等等,能夠說是一個管理網絡資源的絕佳途徑。

1 生成所需套接字,並綁定一個sockaddr結構

先來看如何生成一個AF_NETLINK族套接字:
sockfd = socket(AF_NETLINK, socket_type, netlink_faimly);
這裏socket_type可選SOCK_DGRAM或SOCK_RAW;由於AF_NETLINK族是面向數據報的套接字,因此不能使用SOCK_STREAM。
netlink_family指定要和內核中的哪一個子系統進行交互,目前支持:
NETLINK_ROUTE 與路由信息相關,包括查詢、設置和刪除路由表中的條目等。待會兒咱們將以這類family舉個實際的例子;
NETLINK_FIREWALL 接收由IPv4防火牆代碼發送的包;
NETLINK_ARPD 能夠在用戶空間進行arp緩存的管理;
NETLINK_ROUTE6 在用戶空間發送和接收路由表信息更新;
還有幾種雖然沒有實現,但已經有了定義,爲之後擴展作好了準備。

接下來要給該套接字綁定一個sockaddr結構,其實是一個sockaddr_nl結構:
struct sockaddr_nl {
sa_family_t nl_family; /*AF_NETLINK*/
unsigned short nl_pad; /* 0 */
pid_t nl_pid; /* 進程pid */
u_32 nl_groups; /* 多播組掩碼*/
}nl;
這個結構通常按照註釋填好就能夠了,nl_groups我也不知道怎麼用,通常填零了,表示沒有多播。綁定:
bind(sockfd, (struct sockaddr*) &nl, sizeof(nl));

2 填充所需數據結構,並經過sendmsg()/send()等函數寫到套接字裏去

到 此爲止,與內核通訊的準備工做就完成了,下面要作的工做是,選取適當的數據結構進行填充,並做爲sendmsg()的參數發送出去,並recv()收到的 消息。這個數據結構就是nlmsghdr,它只是一個信息頭,後面能夠接任意長的數據,這些數據實際上又是針對某一需求所採用的特定數據結構。先來看 nlmsghdr:
struct nlmsghdr {
_u32 nlmsg_len; /* Length of msg including header */
_u32 nlmsg_type; /* 操做命令 */
_u16 nlmsg_flags; /* various flags */
_u32 nlmsg_seq; /* Sequence number */
_u32 nlmsg_pid; /* 進程PID */
};
/* 緊跟着是實際要發送的數據,長度能夠任意 */

nlmsg_type 決定此次要執行的操做,如查詢當前路由表信息,所使用的就是RTM_GETROUTE。標準nlmsg_type包括:NLMSG_NOOP, NLMSG_DONE, NLMSG_ERROR等。根據採用的nlmsg_type不一樣,還要選取不一樣的數據結構來填充到nlmsghdr後面:
操做 數據結構
RTM_NEWLINK ifinfomsg
RTM_DELLINK
RTM_GETLINK
RTM_NEWADDR ifaddrmsg
RTM_DELADDR
RTM_GETADDR
RTM_NEWROUTE rtmsg
RTM_DELROUTE
RTM_GETROUTE
RTM_NEWNEIGH ndmsg/nda_chcheinfo
RTM_DELNEIGH
RTM_GETNEIGH
RTM_NEWRULE rtmsg
RTM_DELRULE
RTM_GETRULE
RTM_NEWQDISC tcmsg
RTM_DELQDISC
RTM_GETQDISC
RTM_NEWTCLASS tcmsg
RTM_DELTCLASS
RTM_GETTCLASS
RTM_NEWTFILTER tcmsg
RTM_DELTFILTER
RTM_GETTFILTER
因爲情形衆多,我從如今開始將用一個特定的例子來講明問題。咱們的目的是從內核讀取IPV4路由表信息。從上面表看,nlmsg_type必定使用RTM_xxxROUTE操做,對應的數據結構是rtmsg。既然是讀取,那麼應該是RTM_GETROUTE了。
struct rtmsg {
unsigned char rtm_family; /* 路由表地址族 */
unsigned char rtm_dst_len; /* 目的長度 */
unsigned char rtm_src_len; /* 源長度 */ (2.4.10頭文件的註釋標反了?)
unsigned char rtm_tos; /* TOS */

unsigned char rtm_table; /* 路由表選取 */
unsigned char rtm_protocol; /* 路由協議 */
unsigned char rtm_scope;
unsigned char rtm_type;

unsigned int rtm_flags;
};
對於RTM_GETROUTE操做來講,咱們只需指定兩個成員:rtm_family:AF_INET, rtm_table: RT_TABLE_MAIN。其餘成員都初始化爲0便可。將這個結構體跟nlmsghdr結合起來,獲得咱們本身的新結構體:
struct {
struct nlmsghdr nl;
struct rtmsg rt;
}req;
填充好rt結構以後,還要調整nl結構相應成員的值。Linux定義了多個宏來處理nlmsghdr成員的值,咱們這裏用到的是NLMSG_LENGTH(size_t len);
req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
這將計算nlmsghdr長度與rtmsg長度的和(其中包括了將rtmsg進行4字節邊界對齊的調整),並存儲到nlmsghdr的nlmsg_len成員中。接下來要作的就是將這個新結構體req放到sendmsg()函數的msghdr.iov處,並調用函數。
sendmsg(sockfd, &msg, 0);

3 接收數據,並進行分析

接下來的操做是recv()操做,從該套接字讀取內核返回的數據,並進行分析處理。
recv(sockfd, p, sizeof(buf) - nll, 0);
其中p是指向一個緩衝區buf的指針,nll是已接收到的nlmsghdr數據的長度。
因爲內核返回信息是一個字節流,須要調用者檢查消息結尾。這是經過檢查返回的nlmsghdr的nlmsg_type是否等於NLMSG_DONE來完成的。返回的數據格式以下:
-----------------------------------------------------------
| nlmsghdr+route entry | nlmsghdr+route entry | ......... 
-----------------------------------------------------------
| 解出route entry
V
-----------------------------------------------------------
| dst_addr | gateway | Output interface| ...............
-----------------------------------------------------------
可 以看出,返回消息由多個(nlmsghdr + route entry)組成,當某個nlmsghdr的nlmsg_type == NLMSG_DONE時就表示信息輸出已經完畢。而每個route entry由多個rtattr結構體組成,每一個結構體表示該路由項的某個屬性,如目的地址,網關等等。根據這個示意圖咱們就可以輕鬆解析須要的數據了。linux

相關文章
相關標籤/搜索