- 論壇徽章:
- 169
|
使用netlink通訊時(shí)需要注意的一些問題 原作者:duanjigang
之前發(fā)過一個(gè)用戶態(tài)通過netlink從內(nèi)核中獲取網(wǎng)絡(luò)卡列表以及每個(gè)網(wǎng)卡狀態(tài)信息的例子
大概的原理就是內(nèi)核創(chuàng)建netlink socket,然后用戶態(tài)調(diào)用應(yīng)用程序發(fā)送查詢命令,或者獲取所有網(wǎng)卡列表,或者獲取某一個(gè)網(wǎng)卡的狀態(tài)信息。
當(dāng)時(shí)做的比較簡(jiǎn)單,也就過去了,最近要用到這個(gè)通訊,傳輸比較大量數(shù)據(jù),遇到了一些問題,今天剛剛解決,稍微小結(jié)下,發(fā)上來。
希望能對(duì)大家有點(diǎn)用(估計(jì)很多高手早都注意這個(gè)問題了^_^)
首先列舉下問題:
其一,內(nèi)核多次發(fā)送數(shù)據(jù)的問題。
在上篇文章中,我們看到,kernel是收到一個(gè)命令,就獲取數(shù)據(jù),然后簡(jiǎn)單的完成一次發(fā)送,代碼片段如下:
- nlhdr->nlmsg_pid = 0;
- nlhdr->nlmsg_flags = 0;
- NETLINK_CB(skb).pid = 0;
- NETLINK_CB(skb).dst_pid = pid;
- NETLINK_CB(skb).dst_group = 0;
- memset(nlhdr, 0, NLMSG_SPACE(nlhdr->nlmsg_len));
- strcpy(NLMSG_DATA(nlhdr), szBuff);
- netlink_unicast(netlink_exam_sock, skb, pid, MSG_DONTWAIT);
復(fù)制代碼
當(dāng)時(shí)沒太注意,后來遇到情況是,數(shù)據(jù)有多條,內(nèi)核需要多次發(fā)送,怎么辦??結(jié)果我嘗試用netlink_unicast多次發(fā)送,比如
- for (int i = 0; i < n ;i++)
- {
- //make data for record i
- netlink_unicast(netlink_exam_sock, skb, pid, MSG_DONTWAIT);
- }
復(fù)制代碼
結(jié)果一運(yùn)行,就崩潰,后來知道netlink_unicast發(fā)送后會(huì)把skb釋放掉,所以第二次調(diào)用是無效的了,這才會(huì)崩潰。
大體上感覺在每次發(fā)送的時(shí)候,可能需要clone一個(gè)或者自己構(gòu)造一個(gè)包發(fā)送,上文的例子中的是直接利用從隊(duì)列中拿出來的
skb做為負(fù)載發(fā)送的,所以沒問題,但是還不能偷懶。就在網(wǎng)上找資料。
終于還是找到說法了,居然也是在CU的帖子,就是另外寫一個(gè)函數(shù),自己構(gòu)造包,填數(shù)據(jù),然后發(fā)送,就能多次發(fā)送了。
參考“執(zhí)一”的博文:
通過Netlink與TC進(jìn)行通信
(讓我們?cè)俅螌?duì)九賤兄和執(zhí)一表示感謝!)修改了下,寫(基本上是copy,只不過修改了參數(shù))了個(gè)發(fā)送的函數(shù),如下:
- static int send_to_user(struct sock * ps, int pid, const char* szdata, unsigned int len)
- {
- int ret;
- int size;
- unsigned char *old_tail;
- struct sk_buff *skb;
- struct nlmsghdr *nlhdr;
- struct cha *packet;
- /*計(jì)算消息總長(zhǎng):消息首部加上數(shù)據(jù)加度*/
- size = NLMSG_SPACE(len);
- /*分配一個(gè)新的套接字緩存*/
- skb = alloc_skb(size, GFP_ATOMIC);
- old_tail = skb->tail;
- /*初始化一個(gè)netlink消息首部*/
- nlhdr = NLMSG_PUT(skb, 0, 0, NETLINK_CME, size - sizeof(*nlhdr));
- /*跳過消息首部,指向數(shù)據(jù)區(qū)*/
- packet = NLMSG_DATA(nlhdr);
- /*初始化數(shù)據(jù)區(qū)*/
- memset(packet, 0, len);
- memcpy(packet, szdata, len);
- nlhdr->nlmsg_len = skb->tail - old_tail;
- /*設(shè)置控制字段*/
- nlhdr->nlmsg_pid = 0;
- nlhdr->nlmsg_flags = 0;
- NETLINK_CB(skb).pid = 0;
- NETLINK_CB(skb).dst_pid = pid;
- NETLINK_CB(skb).dst_group = 0;
- /*發(fā)送數(shù)據(jù)*/
- ret = netlink_unicast(ps, skb, pid, MSG_DONTWAIT);
- nlmsg_failure:
- return ret;
- ;
- }
復(fù)制代碼
這樣,把原來的代碼稍作修改
改成這樣就能多次發(fā)送了。
- if(strncmp(data, "all", 3) == 0)
- {
- get_dev_info(0, NULL);
- }
- else
- {
- get_dev_info(1, data);
- }
- pid = nlhdr->nlmsg_pid;
- for(i = 0; i < time; i++)
- {
- send_to_user(netlink_exam_sock, pid, szBuff, strlen(szBuff));
- }
- }
復(fù)制代碼
其二:skb釋放問題。(問題解決按照輕重緩急來說^_^)
剛解決了多次發(fā)送的問題,我就有些得意忘形,結(jié)果dmesg時(shí)看到一個(gè)很2的信息,是在rmmod時(shí)報(bào)告的
KERNEL: assertion (!atomic_read(&sk->sk_rmem_alloc)) failed at net/netlink/af_netlink.c (145)
于是再想是不是一樓了什么東西,哦,從隊(duì)列中拿出來的skb沒有釋放,這下好解決了。兩種途徑。
A:既然netlink_unicast發(fā)送完后會(huì)把skb釋放掉,那我們?yōu)樯恫坏谝淮伟l(fā)送時(shí)用從隊(duì)列中拿出來的skb做載體,這樣既發(fā)送數(shù)據(jù)包,又
釋放了skb,果然,報(bào)錯(cuò)沒了。。真是得了便宜還賣乖啊![]()
B:最簡(jiǎn)單的,直接釋放掉從隊(duì)列拿出來的skb,從一而終的構(gòu)造包發(fā)送,不再腳踩兩只船。
復(fù)制代碼
好了,第二個(gè)問題解決了。
其三:也是最末的。應(yīng)用層的阻塞讀問題。
以前我都是一次sendto,然后內(nèi)核一個(gè)回復(fù),應(yīng)用層再一個(gè)recvfrm就了事了。
結(jié)果后來改成
- while(1)
- {
- recvfrom();
- //把數(shù)據(jù)入庫
- }
復(fù)制代碼
的方式,發(fā)現(xiàn)后面的語句沒執(zhí)行,發(fā)現(xiàn)是recvfrom阻塞住了。。這個(gè)好辦,要讓while循環(huán)跳出,內(nèi)核通知應(yīng)用層:“我沒數(shù)據(jù)了,別再再苦苦追尋了,不要浪費(fèi)你時(shí)間”。。這似乎聽起來有些悲哀啊,呵呵![]()
這種人來的語言用程序來寫就是IP報(bào)文的分片標(biāo)志吧,那就自己做個(gè)標(biāo)志吧。
可以這樣做,自己定一個(gè)消息頭,放在netlink消息的開始位置,大小固定,或者直接放一個(gè)整數(shù)都行,反正就是用來標(biāo)識(shí)是否還有數(shù)據(jù)的。
當(dāng)內(nèi)核中還有數(shù)據(jù)要發(fā)送時(shí),每次發(fā)送消息中,這個(gè)標(biāo)志位為1,告訴他:“你還有希望,繼續(xù)追”![]()
如果沒有數(shù)據(jù)了,發(fā)送一個(gè)空包或者帶數(shù)據(jù)的報(bào)文,其中標(biāo)志位為0,告訴他:“我已經(jīng)領(lǐng)證了,終止吧”,用戶態(tài)讀到這個(gè)
標(biāo)志位,跳出循環(huán),后續(xù)工作繼續(xù)。。
就以上這三個(gè)問題,是我實(shí)際中遇到的,希望對(duì)大家有用。
|
|