- 论坛徽章:
- 0
|
上一年接触netlink,几乎现在这种方式非常方便用户空间和内核进程空间通信,并且可以监视异步时间,例如网卡的状态改变,ARP缓存的修改,这种改变用原有的IOCTL方式几乎是不可想象的。后来看了网上很多关于netlink的狗屁文章,看的我头大,正好这段时间重新看网络,所以爷们就写点东西,请看::
一 :相关的数据结构:
1:struct netlink_table *nl_table;
struct netlink_table {
struct nl_pid_hash hash;
struct hlist_head mc_list;
unsigned long *listeners;
unsigned int nl_nonroot;
unsigned int groups;
struct mutex *cb_mutex;
struct module *module;
int registered;
};
描述:
socket(PF_NETLINK, SOCK_DGRAM, protocol(例如INET,NETLINK_ROUTE)),其中的每一个protocol都有一个对应的netlink_table。一般当这些协议模块(例如NETLINK_ROUTE)在初始化的时候,使用函数`netlink_kernel_create`去利用自己协议模块的信息去填充nl_table,函数原型如下:
struct sock * netlink_kernel_create(struct net *net, int unit, unsigned int groups,
void (*input)(struct sk_buff *skb),
struct mutex *cb_mutex, struct module *module)
例如当NETLINK_ROUTE在初始化的时候调用的方式就如下:
sk = netlink_kernel_create(net, NETLINK_ROUTE, RTNLGRP_MAX,
rtnetlink_rcv, &rtnl_mutex, THIS_MODULE);
其中NETLINK_ROUTE(宏)用来确定NETLINK_ROUTE子协议去填充哪一个netlink_table,可以把unit看成是nl_tabl数组的索引。
使用:
为了简单说明netlink的工作模式,让我们假想一种情况,因为不论是内核中的协议模块,还是用户自己创建的协议模块,实际上都是使用socket来进行通信,比方说a为了给b发送一条消息,那么a就一定要知道怎么找到b。再比方说我们现在想出了一种方法用于a和b来通信:创建一个固定长度的数组array,每个数组元素里面的数组是一个函数,该函数的都类似于这样:sayhi-to_X,比方分配给a的数组的索引是1,分配给b的索引是2,a初始化元素array[1]= sayhello-to-a,b初始化元素array[2]=sayhello-to-b。当a要和b通信的时候,只要a知道b的索引,那么就可以直接调用array[index(b)]()来给b发送信息了。
让我们来看看上面的这个模型对于真实的内核中的netlink是如何工作的。
当netlink子协议模块在把自己的模块信息用netink_kernel_create填充nl_table的时候,netlink_kernel_create同时还为该模块在nl_table的元素nl_pid_hash中分配一个元素,该元素的索引就为0,你可以把nl_pid_hash简单的想象成一个数组,netlink的子协议模块占用的索引为0,并且一定为0,这个数组中剩下的元素则可以被用户空间的程序在调用socket(...) -> netlink_create的时候使用,也就是说实际上如果用户空间想利用socket发送消息的时候,那么它一定在nl_pid_hash这个数组中占用一个元素。再来看NETLINK_ROUTE子协议模块在把自己注册到nl_table中的那个函数:
sk = netlink_kernel_create(net, NETLINK_ROUTE, RTNLGRP_MAX,
rtnetlink_rcv, &rtnl_mutex, THIS_MODULE);
该函数中的rtnetlink_rcv实际上就类似于上面说的array[index(a)]的函数,因为NETLINK子协议模块的索引都为0,所以当发送给给内核协议模块发送消息的索引也就为0,索引为0也就意味着给内核自协议模块发消息,那比方说要给子协议模块INET发送信息,那么只需要两个参数就可以了:INET该参数用来对nl_table进行索引,找到用来描述INET协议模块的那个netlink_table,然后0用来在nl_pid_hash中找到inet注册的用来和它通信的函数,比方说sayhello-to-inet。这样就可以给inet来发送信息了。
因为不论是子协议模块还是用户空间的soket,实际上都拥有一个socket这样的结构题,这个结构体里都有两个链表,一个recv列队,一个是send列队。比方说当用户空间发送一条信息给子协议模块inet一个信息的时候,实际上inet子协议并不是立刻的处理该请求,而是把这个用户空间发来的信息加入到一个inet自模块所占有的socket中的recv列队中,然后就返回给用户空间说该信息已经发送过去了。在某一个时刻inet发送他的recv类对中有信息需要处理,那么他就会处理该信息,然后发送给用户空间一个响应,这种响应的方式和用户的socket找到inet的方式是一样的,这个响应会把inet发送的响应加入到用户空间socket的recv列队中去,然后通知用户空间在该socket上有数据存在了。这种异步的方式是和ioctl的方式不一样的。ioctl则把请求和响应放在了同一个函数里面,这样就要求内核在处理请求的时候,用户空间需要一直等待。
编程:
socket(...)
?bind(...)本来意味bind是必须的,但是发现根本不用bind,当用户空间的socket发送数据给内核的时候,如果没有为该socket在nl_pid_hash上分配索引,那么内核会自动的分配一个索引,并且这个索引会一直被该socket使用。那么什么时候需要这个东西呢?那就是用户进程通过socket给用户进程的socket发送信息,这样一方必须知道另外一方的固定的索引,这就和真正的socket编程中分配端口的概念是一样的,如果一方是服务器,那么他的端口一定是固定的。我经常看到很多人都在bind地址的时候设定sockadd_nl的元素pid=getpid(),这样作简直就是吃包了撑的,首先这个pid在进程启动的时候都极有可能是不一样的,这就意味着该socket不能做为固定的接受方使用,分配这个pid还有一个不好的地方就是如果你设置pid为一个值的话,那么如果有另外一个进程就故意设置自己的bind pid也为那个数值,虽然这样很变态,但是却足以让你的程序在bind的时候就出错。所以我建议永远不要设置pid为你的getpid,或者是永远都不要bind,除非你的程序要接受来自其他的进程的消息,如果真的是那样的话,请给你的程序设定一个固定的数值,你可以通过定义一个宏来亲送实现。
send() || recv()
相关资料:
struct socket
netlink_create netlink_kernel_create netlink_sendmsg netlink_recvmsg
netlink_unicast
2:监听内核的事件(网卡up或者down,ip地址改变,arp缓冲区改变)
一般使用ioctl的方式要监听这些事件的话要一直从内核中获取数据,而利用netlink则可以非常方便的来获知该事件,因为它的操作是分为2步的,你甚至可以使用epoll来监听那个socket。
当内核中一个网络时间发生的时候,一般都会使用通知链通知这一事情。简单来说:当网卡down的时候,那么路由表就要修改,以真实的反应这一情况。在这种情况下通知链的作用就体现出来了。例如在上面的例子中路由系统注册一个函数到网卡的通知链中去,当网卡发生路由系统感兴趣的事情的时候就会调用路由系统提供的函数,这样网卡的情况就能真实的反应到路由系统中去了。
netlink也有一些子协议模块例如rtnetlink去做这样的事情,例如rtnetlink也会注册到网卡的通知链中,当网卡出现一些情况的时候,例如down,那么rtnetlink就会广播该事件,该广播的时间照样是通过nl_table来处理的,在netlink_table中有一个mc_list,用户程序可以通过setsockopt netlink_add_membership来把自己加入到mc_list中去,这样当网卡有事情的时候,那么就可以通过这种方式来通知应用层了。
相关资料:
netlink_broadcast
rtnl_notify
rtnl_register
下期我会给出足够多的程序来表述详细的使用方式。 |
评分
-
查看全部评分
|