- 论坛徽章:
- 169
|
使用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,从一而终的构造包发送,不再脚踩两只船。
复制代码
好了,第二个问题解决了。
其三:也是最末的。应用层的阻塞读问题。
以前我都是一次sendto,然后内核一个回复,应用层再一个recvfrm就了事了。
结果后来改成
- while(1)
- {
- recvfrom();
- //把数据入库
- }
复制代码
的方式,发现后面的语句没执行,发现是recvfrom阻塞住了。。这个好办,要让while循环跳出,内核通知应用层:“我没数据了,别再再苦苦追寻了,不要浪费你时间”。。这似乎听起来有些悲哀啊,呵呵![]()
这种人来的语言用程序来写就是IP报文的分片标志吧,那就自己做个标志吧。
可以这样做,自己定一个消息头,放在netlink消息的开始位置,大小固定,或者直接放一个整数都行,反正就是用来标识是否还有数据的。
当内核中还有数据要发送时,每次发送消息中,这个标志位为1,告诉他:“你还有希望,继续追”![]()
如果没有数据了,发送一个空包或者带数据的报文,其中标志位为0,告诉他:“我已经领证了,终止吧”,用户态读到这个
标志位,跳出循环,后续工作继续。。
就以上这三个问题,是我实际中遇到的,希望对大家有用。
|
|