绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
[通信方式] 使用netlink通讯时需要注意的一些问题
2020-05-19 17:29:02


使用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,告诉他:“我已经领证了,终止吧”,用户态读到这个
标志位,跳出循环,后续工作继续。。

就以上这三个问题,是我实际中遇到的,希望对大家有用。

文章来源CU社区:[通信方式] 使用netlink通讯时需要注意的一些问题

分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~