之前总结了 netlink 进行用户空间和内核空间数据交互--用户空间使用 的文章
netlink 进行用户空间和内核空间数据交互--用户空间使用_xuaiwai的博客
本篇文章主要就在内核中使用netlink 做一个总结:
netlink 的内核空间 API
内核空间的 netlink API 与应用程序之间的 API 之间有很多的不同, netlink 内核 API 在文件 net/core/af_netlink.c 中实现。内核 netlink API 可以用于访问内核模块的 netlink 套接字,并和用户空间的应用程序进行通信。如果想添加用户自己定义的协议类型,需要自己增加协议类型,如定义如下的协议类型:
#define NETLINK_TEST 27 //这个是自定义的类型,在用户态创建NL socket时使用的协议一致
1.netlink的内核套接字建立
在用户层, socket ()函数用于建立 netlink 套接字,其中的协议类型应该也为 NETLINK_TEST 。内核空间建立此套接字的函数为:
/* net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义); 定义在net_namespace.c(extern struct net init_net);
unit:netlink协议类型
cfg: cfg存放的是netlink内核配置参数
*/
static inline struct sock *netlink_kernel_create(
struct net *net, int unit, struct netlink_kernel_cfg *cfg);
参数 unit ,是 netlink 协议类型,例如为 NETLINK_TEST
参数 cfg,存放的是netlink内核配置参数,包括主要的回调处理函数指针等,下面是netlink_kernel_cfg这个结构的定义。
/* optional Netlink kernel configurationparameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb); /* input 回调函数,收到的消息在此函数中处理 */
struct mutex *cb_mutex;
void (*bind)(int group);
bool (*compare)(struct net *net, struct sock *sk);
};
建立 netlink 套接字函数在成功的时候,返回一个 structure sock 指针类型的值,之后可以用这个值对 netlink 套接字进行处理;当返回值为 NULL 的时候,套接字建立失败了,需要进行容错的处理,通常进行资源的释放等。
static struct netlink_kernel_cfg cfg = {
.input = netlink_recv_msg,
};
struct sock *netlink_sk = netlink_kernel_create(
&init_net, NETLINK_TEST, &cfg);
2. netlink 的应用层数据接收
当内核使用 netlink kernel_create (函数建立一个 NETLINK_TEST 类型的协议之后,当用户空间向内核空间通过之前的 netlink 套接字发送消息的时候, cfg 中函数注册的回调函数 input 会被调用,下面是一个 netlink_recv_msg 函数的实现代码:
static void netlink_recv_msg(struct sk_buff *__skb)
{
struct nlmsghdr *nlhdr = NULL;
struct sk_buff *skb = NULL;
u8 *payload = NULL;
u32 user_pid = 0;
if (NULL == __skb)
{
return;
}
skb = skb_get(__skb);
if (NULL == skb)
{
return;
}
//数据长度大于头长度
if (skb->len >= NLMSG_SPACE(0))
{
//这个 user_pid 可以用来在内核回复消息时使用
user_pid = nlhdr->nlmsg_pid;
nlhdr = nlmsg_hdr(skb);
switch (nlhdr->nlmsg_type)
{
case 1:
{
//取出负载数据
payload = (u8 *)NLMSG_DATA(nlhdr);
//处理 type 为 1的数据
break;
}
defaule:
break;
}
kfree_skb(skb);
}
当应用层的进程通过sendmsg()/sendto()函数发送数据的时候,如果 netlink_recv_msg 函数的处理速度足够快,就不会对系统造成影响。当 netlink_recv_msg 函数的处理过程占用很长时间,需要将处理的代码从netlink_recv_msg函数中移除,放到别的地方进行处理,防止系统调用在此处阻塞,别的系统不能进行调用。
可以使用内核线程来处理上述的功能,在此内核线程中使用 skb = skb_recv_datagrm (netlink_sk)函数来接收客户端发送的数据,接收到的数据保存在 skb->data 中。当使用 netlink kernel create ()函数建立的套接字 netlink_sk 没有数据时,内核处理线程进入睡眠状态,当有数据到来的时候需要将内核处理线程唤醒,进行接收和处理线程的工作。所以这种情况在 input()函数中,需要将内核线程唤醒。可以使用如下的代码实现:
void input(struct sock * sk)
{
wake_up_interruptible(sk->sleep);
}
3. netlink 的内核数据发送
netlink 在内核中发送数据与应用程序发送数据一样,需要设置 netlink 的源地址和目的 netlink 地址。例如,需要发送的 netlink 消息数据在结构 struct sk_buf *skb 中
发送一个单播消息,使用 netlink_unicase()函数,其原型如下:
int netlink_unicast ( struct sock * ssk , struct sk_buff * skb ,u32 pid , int nonblock );
其中的参数 ssk 由 netlink_kernel_create()函数返回, skb->data 指向需要发送的 netlink 消息, pid 是应用层接收数据的 pid , nonblock 用于设置当接收缓冲区不可用的时候是阻塞等待直到缓冲区可用,还是直接返回失败。
发送多播消息使用 netlink_broadcast()函数,它可以向 pid 指定的应用程序或者 goups 指定的群发送消息。原型定义如下:
void netlink_broadcast(struct sock * ssk , struct sk_buff * skb ,u32 pid ,u32 group , int allocation );
其中的参数 group 是 OR 运算组成的接收数据的多播群 ID 号, alocation 是内核内存申请类型,例如 GFP_ATOMIC 在终端上下文中使用, GFP_KERNEL 在其他状态下使用。要申请内存的原因是这个函数在发送消息数据的时候可能需要申请多个套接字缓冲区,用于复制多播消息。
//给 user_pid 发送长度为data_len 的msg_data 类型为nlmsg_type
int netlink_send_msg(int user_pid, int nlmsg_type, u8 *msg_data, int data_len)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlhdr = NULL;
//分配一个sk_buff, 头 + data_len
skb = nlmsg_new(data_len, GFP_ATOMIC);
if (NULL == skb)
{
printk(KERN_ERR "netlink_send_msg: alloc_skb Error.\n");
return ret;
}
//头赋值
nlhdr = nlmsg_put(skb, 0, 0, nlmsg_type, data_len, 0);
if (NULL == nlhdr)
{
printk(KERN_ERR "netlink_send_msg: nlmsg_put Error.\n");
nlmsg_free(skb);
return ret;
}
//payload 赋值
memcpy(nlmsg_data(nlhdr), msg_data, data_len);
//单播 向 user_pid 发送数据 user_pid在接收的回调函数中可获得
ret = netlink_unicast(netlink_sk, skb, user_pid, MSG_DONTWAIT);
if(0 != ret)
{
nlmsg_free(skb);
}
return ret;
}
4. netlink 的套接字关闭
关闭 netlink 套接字,使用 sock_release()函数来进行。主要进行内存等资源的释放,将一些指针进行重置的操作。函数的原型如下
void sock_release ( struct socket * sock );
netlink_kernel_create ()函数建立成功套接字的返回值为 struct *sock , sock_release()函数释放套接字时传入的参数是 struct *socket 类型,类型 struct *socket 是 struct *sock 的一个成员,所以可以使用如下方式来释放套接字:
sock_release(nl_sk->socket);
关键点
1、在创建sock时需要和用户态使用相同的协议类型,比如这里使用的是 NETLINK_TEST;
2、接收到数据有需要获取在头中取去发送端设置的pid,在回复的时候才能正确发送数据给对端;
3、发送时也需要构造一个skb_buff用以发送。
凡是过往,即为序章