Linux网络设备驱动—netlink
1.netlink的内核套接字建立
在用户层, socket ()函数用于建立 netlink 套接字,其中的协议类型应该也为 NETLINK_TEST 。内核空间建立此套接字的函数为:Netlink
netlink_kernel_create 是 Linux 内核中的一个函数,主要用于创建 Netlink 套接字,以便于内核与用户空间的通信。
/* 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; /* input 回调函数,收到的消息在此函数中处理 */void (*input)(struct sk_buff *skb);struct mutex *cb_mutex;void (*bind)(int group);bool (*compare)(struct net *net, struct sock *sk);};
建立 netlink 套接字函数在成功的时候,返回一个 structure sock 指针类型的值,之后可以用这个值对 netlink 套接字进行处理;当返回值为 NULL 的时候,套接字建立失败了,需要进行资源的释放。
单播netlink_unicast()
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);ssk: netlink socket skb: skb buff 指针portid: 通信的端口号,对应用态的端口号nonblock:表示该函数是否为非阻塞,如果为1(MSG_DONTWAIT),
该函数将在没有接收缓存可利用时立即返回,而如果为0(MSG_WAITALL),
该函数在没有接收缓存可利用 定时睡眠
多播netlink_broadcast()
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,__u32 group, gfp_t allocation);ssk: 同上(对应netlink_kernel_create 返回值)、skb: 内核skb buffportid: 通信的端口号,对应用态的端口号group: 是所有目标多播组对应掩码的"OR"操作的合值。allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,
而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个
或多个缓冲区来对多播消息进行clone
nlmsg_hdr()获取netlink消息的头部指针nlmsg_new()分配一个新的netlink消息
struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)payload : 分配的大小flags:进程上下文,可以睡眠:GFP_KERNEL进程上下文,不可以睡眠:GFP_ATOMIC中断处理程序:GFP_ATOMIC软中断:GFP_ATOMICTasklet:GFP_ATOMIC用于DMA的内存,可以睡眠:GFP_DMA | GFP_KERNEL用于DMA的内存,不可以睡眠:GFP_DMA |GFP_ATOMIC
向skb缓冲区中获取消息头空间并且初始化netlink消息头,入参中的第5个参数为netlink消息头的总空间。
-----------------------------------------------------------
struct sk_buf 结构体
用作套接字缓存,作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递。
struct sk_buff
{struct sk_buff *next;struct sk_buff *prev;struct sock *sock ; //struct sock是socket在网络层的表示,其中存放了网络层的信息unsigned int len; //表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。unsigned int data_len; //和len不同,data_len只计算分片中数据的长度__u16 mac_len ; //数路链路层的头长度__u16 hdr_len ; //writable header length of cloned skbunsigned int truesize ; //socket buffer(套接字缓存区的大小)atomic_t users ; //对当前的struct sk_buff结构体的引用次数;__u32 priority ; //这个struct sk_buff结构体的优先级sk_buff_data_t transport_header ; //传输层头部的偏移量sk_buff_data_t network_header ; //网络层头部的偏移量sk_buff_data_t mac_header ; //数据链路层头部的偏移量char *data ; //socket buffer中数据的起始位置;sk_buff_data_t tail ; //socket buffer中数据的结束位置;char *head ; //socket buffer缓存区的起始位置;sk_buffer_data_t end ; //socket buffer缓存区的终止位置;struct net_device *dev; //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收网络设备int iif; //网络设备的接口索引号; struct timeval tstamp ; //用于存放接受的数据包的到达时间;__u8 local_df : 1 , //allow local fragmentaion;cloned : 1 , // head may be cloned;__u8 pkt_type : 3 , //数据包的类型;fclone : 2, // struct sk_buff clone status
}
====================================
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);
}
nlmsg_put()
向skb缓冲区中获取消息头空间并且初始化netlink消息头,入参中的第5个参数为netlink消息头的总空间。
struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,int type, int payload, int flags)
portid:与 netlink消息头 中的 nlmsg_pid 对应
seq:与 netlink消息头 中的 nlmsg_seq 对应
type:与 netlink消息头 中的 nlmsg_type 对应
payload:与 netlink消息头 中的 nlmsg_len 对应
flags:与 netlink消息头 中的 nlmsg_flags 对应
3.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);