linux link 函数,Linux Netlink基本使用

好了,有了这个例子我们就大概知道用户态怎么使用Netlink了,至于我们没有用到的nl_groups等其他信息后面讲到再说,下面看下内核是怎么处理Netlink的。

4. 内核 Netlink api

4.1创建 netlink socket

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);

参数说明:

(1) net:是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用 init_net这个全局变量。

(2) unit:表示netlink协议类型,如NETLINK_TEST、NETLINK_SELINUX。

(3) groups:多播地址。

(4) input:为内核模块定义的netlink消息处理函数,当有消 息到达这个netlink socket时,该input函数指针就会被引用,且只有此函数返回时,调用者的sendmsg才能返回。

(5)  cb_mutex:为访问数据时的互斥信号量。

(6) module: 一般为THIS_MODULE。

4.2 发送单播消息 netlink_unicast

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)

参数说明:

(1) ssk:为函数 netlink_kernel_create()返回的socket。

(2) skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。

(3) pid:为接收此消息进程的pid,即目标地址,如果目标为组或内核,它设置为 0。

(4) nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。

4.3 发送广播消息 netlink_broadcast

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

前面的三个参数与 netlink_unicast相同,参数group为接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或。参数allocation为内核内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。

4.4 释放 netlink socket

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

5. 内核态程序范例一

#include

#include

#include

#include

#include

#include

#include

#define NETLINK_TEST 25

#define MAX_MSGSIZE 1024

int stringlength(char *s);

int err;

struct sock *nl_sk = NULL;

int flag = 0;

//向用户态进程回发消息

void sendnlmsg(char *message, int pid)

{

struct sk_buff *skb_1;

struct nlmsghdr *nlh;

int len = NLMSG_SPACE(MAX_MSGSIZE);

int slen = 0;

if(!message || !nl_sk)

{

return ;

}

printk(KERN_ERR "pid:%d\n",pid);

skb_1 = alloc_skb(len,GFP_KERNEL);

if(!skb_1)

{

printk(KERN_ERR "my_net_link:alloc_skb error\n");

}

slen = stringlength(message);

nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0);

NETLINK_CB(skb_1).pid = 0;

NETLINK_CB(skb_1).dst_group = 0;

message[slen]= '\0';

memcpy(NLMSG_DATA(nlh),message,slen+1);

printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));

netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);

}

int stringlength(char *s)

{

int slen = 0;

for(; *s; s++)

{

slen++;

}

return slen;

}

//接收用户态发来的消息

void nl_data_ready(struct sk_buff *__skb)

{

struct sk_buff *skb;

struct nlmsghdr *nlh;

char str[100];

struct completion cmpl;

printk("begin data_ready\n");

int i=10;

int pid;

skb = skb_get (__skb);

if(skb->len >= NLMSG_SPACE(0))

{

nlh = nlmsg_hdr(skb);

memcpy(str, NLMSG_DATA(nlh), sizeof(str));

printk("Message received:%s\n",str) ;

pid = nlh->nlmsg_pid;

while(i--)

{//我们使用completion做延时,每3秒钟向用户态回发一个消息

init_completion(&cmpl);

wait_for_completion_timeout(&cmpl,3 * HZ);

sendnlmsg("I am from kernel!",pid);

}

flag = 1;

kfree_skb(skb);

}

}

// Initialize netlink

int netlink_init(void)

{

nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 1,

nl_data_ready, NULL, THIS_MODULE);

if(!nl_sk){

printk(KERN_ERR "my_net_link: create netlink socket error.\n");

return 1;

}

printk("my_net_link_4: create netlink socket ok.\n");

return 0;

}

static void netlink_exit(void)

{

if(nl_sk != NULL){

sock_release(nl_sk->sk_socket);

}

printk("my_net_link: self module exited\n");

}

module_init(netlink_init);

module_exit(netlink_exit);

MODULE_AUTHOR("yilong");

MODULE_LICENSE("GPL");

附上内核代码的Makefile文件:

ifneq ($(KERNELRELEASE),)

obj-m :=netl.o

else

KERNELDIR ?=/lib/modules/$(shell uname -r)/build

PWD :=$(shell pwd)

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

6. 程序结构分析

我们将内核模块insmod后,运行用户态程序,结果如下:

563637b6bf312cc6a2d31988b37e0788.png

这个结果复合我们的预期,但是运行过程中打印出“state_smg”卡了好久才输出了后面的结果。这时候查看客户进程是处于D状态的(不了解D状态的同学可以google一下)。这是为什么呢?因为进程使用Netlink向内核发数据是同步,内核向进程发数据是异步。什么意思呢?也就是用户进程调用sendmsg发送消息后,内核会调用相应的接收函数,但是一定到这个接收函数执行完用户态的sendmsg才能够返回。我们在内核态的接收函数中调用了10次回发函数,每次都等待3秒钟,所以内核接收函数30秒后才返回,所以我们用户态程序的sendmsg也要等30秒后才返回。相反,内核回发的数据不用等待用户程序接收,这是因为内核所发的数据会暂时存放在一个队列中。

再来回到之前的一个问题,用户态程序的源地址(pid)可以用0吗?我把上面的用户程序的A和C处pid都改为了0,结果一运行就死机了。为什么呢?我们看一下内核代码的逻辑,收到用户消息后,根据消息中的pid发送回去,而pid为0,内核并不认为这是用户程序,认为是自身,所有又将回发的10个消息发给了自己(内核),这样就陷入了一个死循环,而用户态这时候进程一直处于D。

另外一个问题,如果同时启动两个用户进程会是什么情况?答案是再调用bind时出错:“Address already in use”,这个同UDP一样,同一个地址同一个port如果没有设置SO_REUSEADDR两次bind就会出错,之后我用同样的方式再Netlink的socket上设置了SO_REUSEADDR,但是并没有什么效果。

7. 用户态范例二

之前我们说过UDP可以使用sendmsg/recvmsg也可以使用sendto/recvfrom,那么Netlink同样也可以使用sendto/recvfrom。具体实现如下:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define MAX_PAYLOAD 1024 // maximum payload size

#define NETLINK_TEST 25

int main(int argc, char* argv[])

{

struct sockaddr_nl src_addr, dest_addr;

struct nlmsghdr *nlh = NULL;

int sock_fd, retval;

int state,state_smg = 0;

// Create a socket

sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);

if(sock_fd == -1){

printf("error getting socket: %s", strerror(errno));

return -1;

}

// To prepare binding

memset(&src_addr, 0, sizeof(src_addr));

src_addr.nl_family = AF_NETLINK;

src_addr.nl_pid = 100;

src_addr.nl_groups = 0;

//Bind

retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));

if(retval < 0){

printf("bind failed: %s", strerror(errno));

close(sock_fd);

return -1;

}

// To orepare create mssage head

nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));

if(!nlh){

printf("malloc nlmsghdr error!\n");

close(sock_fd);

return -1;

}

memset(&dest_addr,0,sizeof(dest_addr));

dest_addr.nl_family = AF_NETLINK;

dest_addr.nl_pid = 0;

dest_addr.nl_groups = 0;

nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);

nlh->nlmsg_pid = 100;

nlh->nlmsg_flags = 0;

strcpy(NLMSG_DATA(nlh),"Hello you!");

//send message

printf("state_smg\n");

sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));

if(state_smg == -1)

{

printf("get error sendmsg = %s\n",strerror(errno));

}

memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));

//receive message

printf("waiting received!\n");

while(1){

printf("In while recvmsg\n");

state=recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,NULL,NULL);

if(state<0)

{

printf("state<1");

}

printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));

memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));

}

close(sock_fd);

return 0;

}

熟悉UDP编程的同学看到这个程序一定很熟悉,除了多了一个Netlink消息头的设置。但是我们发现程序中调用了bind函数,这个函数再UDP编程中的客户端不是必须的,因为我们不需要把UDP socket与某个地址关联,同时再发送UDP数据包时内核会为我们分配一个随即的端口。但是对于Netlink必须要有这一步bind,因为Netlink内核可不会为我们分配一个pid。再强调一遍消息头(nlmsghdr)中的pid是告诉内核接收端要回复的地址,但是这个地址存不存在内核并不关心,这个地址只有用户端调用了bind后才存在。

再说一个体外话,我们看到这两个例子都是用户态首先发起的,那Netlink是否支持内核态主动发起的情况呢?当然是可以的,只是内核一般需要事件触发,这里,只要和用户态约定号一个地址(pid),内核直接调用netlink_unicast就可以了。

22/2<12

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值