Chinaunix
标题:
使用netlink通讯时需要注意的一些问题
[打印本页]
作者:
gaokeke123
时间:
2019-07-26 14:02
标题:
使用netlink通讯时需要注意的一些问题
使用netlink通讯时需要注意的一些问题 原作者:duanjigang
之前发过一个用户态通过netlink从内核中获取网络卡列表以及每个网卡状态信息的例子
大概的原理就是内核创建netlink socket,然后用户态调用应用程序发送查询命令,或者获取所有网卡列表,或者获取某一个网卡的状态信息。
当时做的比较简单,也就过去了,最近要用到这个通讯,传输比较大量数据,遇到了一些问题,今天刚刚解决,稍微小结下,发上来。
希望能对大家有点用(估计很多高手早都注意这个问题了^_^)
首先列举下问题:
其一,内核多次发送数据的问题。
在上篇文章中,我们看到,kernel是收到一个命令,就获取数据,然后简单的完成一次发送,代码片段如下:
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);
复制代码
当时没太注意,后来遇到情况是,数据有多条,内核需要多次发送,怎么办??结果我尝试用netlink_unicast多次发送,比如
for (int i = 0; i < n ;i++)
{
//make data for record i
netlink_unicast(netlink_exam_sock, skb, pid, MSG_DONTWAIT);
}
复制代码
结果一运行,就崩溃,后来知道netlink_unicast发送后会把skb释放掉,所以第二次调用是无效的了,这才会崩溃。
大体上感觉在每次发送的时候,可能需要clone一个或者自己构造一个包发送,上文的例子中的是直接利用从队列中拿出来的
skb做为负载发送的,所以没问题,但是还不能偷懒。就在网上找资料。
终于还是找到说法了,居然也是在CU的帖子,就是另外写一个函数,自己构造包,填数据,然后发送,就能多次发送了。
参考“执一”的博文:
通过Netlink与TC进行通信
(让我们再次对九贱兄和执一表示感谢!)修改了下,写(基本上是copy,只不过修改了参数)了个发送的函数,如下:
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;
/*计算消息总长:消息首部加上数据加度*/
size = NLMSG_SPACE(len);
/*分配一个新的套接字缓存*/
skb = alloc_skb(size, GFP_ATOMIC);
old_tail = skb->tail;
/*初始化一个netlink消息首部*/
nlhdr = NLMSG_PUT(skb, 0, 0, NETLINK_CME, size - sizeof(*nlhdr));
/*跳过消息首部,指向数据区*/
packet = NLMSG_DATA(nlhdr);
/*初始化数据区*/
memset(packet, 0, len);
memcpy(packet, szdata, len);
nlhdr->nlmsg_len = skb->tail - old_tail;
/*设置控制字段*/
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;
/*发送数据*/
ret = netlink_unicast(ps, skb, pid, MSG_DONTWAIT);
nlmsg_failure:
return ret;
;
}
复制代码
这样,把原来的代码稍作修改
改成这样就能多次发送了。
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));
}
}
复制代码
其二:skb释放问题。(问题解决按照轻重缓急来说^_^)
刚解决了多次发送的问题,我就有些得意忘形,结果dmesg时看到一个很2的信息,是在rmmod时报告的
KERNEL: assertion (!atomic_read(&sk->sk_rmem_alloc)) failed at net/netlink/af_netlink.c (145)
于是再想是不是一楼了什么东西,哦,从队列中拿出来的skb没有释放,这下好解决了。两种途径。
A:既然netlink_unicast发送完后会把skb释放掉,那我们为啥不第一次发送时用从队列中拿出来的skb做载体,这样既发送数据包,又
释放了skb,果然,报错没了。。真是得了便宜还卖乖啊
B:最简单的,直接释放掉从队列拿出来的skb,从一而终的构造包发送,不再脚踩两只船。
kfree_skb(skb);
复制代码
好了,第二个问题解决了。
其三:也是最末的。应用层的阻塞读问题。
以前我都是一次sendto,然后内核一个回复,应用层再一个recvfrm就了事了。
结果后来改成
while(1)
{
recvfrom();
//把数据入库
}
复制代码
的方式,发现后面的语句没执行,发现是recvfrom阻塞住了。。这个好办,要让while循环跳出,内核通知应用层:“我没数据了,别再再苦苦追寻了,不要浪费你时间”。。这似乎听起来有些悲哀啊,呵呵
这种人来的语言用程序来写就是IP报文的分片标志吧,那就自己做个标志吧。
可以这样做,自己定一个消息头,放在netlink消息的开始位置,大小固定,或者直接放一个整数都行,反正就是用来标识是否还有数据的。
当内核中还有数据要发送时,每次发送消息中,这个标志位为1,告诉他:“你还有希望,继续追”
如果没有数据了,发送一个空包或者带数据的报文,其中标志位为0,告诉他:“我已经领证了,终止吧”,用户态读到这个
标志位,跳出循环,后续工作继续。。
就以上这三个问题,是我实际中遇到的,希望对大家有用。
欢迎光临 Chinaunix (http://bbs.chinaunix.net/)
Powered by Discuz! X3.2