Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的。当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同,下图是 netlink 套接字实现此类通信时创建的过程:

用户空间:

1. 创建套接字

skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);

skfd为static int类型,AF_NETLINK是netlink对应的协议簇,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,它可以是一个自定义的类型,也可以使用内核预定义的类型

2. 绑定套接字

bind(skfd, (struct sockaddr*)&local, sizeof(local));

♦ local为netlink的socket地址,其结构描述为:

struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};

/*成员 nl_family为协议簇 AF_NETLINK,成员 nl_pad 当前没有使用,因此要总是设置为 0,成员 nl_pid 为接收或发送消息的进程的 ID*/

♦ 定义socket地址

struct sockaddr_nl local;
memset(&local, , sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /*设置pid为自己的pid值*/
local.nl_groups = ;

3. 用户空间调用send函数(如sendto、sendmsg等)向内核发送数据,使用同样的socket地址来描述内核,不过需要注意,由于对端是内核,nl_pid必须设置为0。

♦ 内核的socket地址

struct sockaddr_nl kpeer;
memset(&kpeer, , sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = ;
kpeer.nl_groups = ;

♦ 用户进程想内核发送的数据包格式为:“netlink消息头 + 数据”,消息头描述为:

struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};

♦ 自定义消息首部,它仅包含了netlink的消息首部

struct msg_to_kernel
{
struct nlmsghdr hdr;
};

♦ 填充首部信息

struct msg_to_kernel message;
memset(&message, , sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(); /*没有数据,所以长度为0.*/
message.hdr.nlmsg_flags = ;
message.hdr.nlmsg_type = IMP2_U_PID;
message.hdr.nlmsg_pid = local.nl_pid;

♦ 向内核发送消息

sendto(skfd, &message, message.hdr.nlmsg_len, ,(struct sockaddr*)&kpeer, sizeof(kpeer));

4. 当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收的数据包含了netlink消息首部和自定义数据结构。

♦ 自定义的数据结构

struct u_packet_info
{
struct nlmsghdr hdr; /*netlink消息头*/
struct packet_info icmp_info;
};
struct u_packet_info info;

♦ 接受和处理从内核接受到的信息

while()
{
kpeerlen = sizeof(struct sockaddr_nl);
/*接收内核空间返回的数据*/
rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),, (struct sockaddr*)&kpeer, &kpeerlen);
/*处理接收到的数据*/
……
}

5. 函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出

♦ 关闭套接字的工作放在了自定义的信号函数sig_int中处理

static void sig_int(int signo)
{
struct sockaddr_nl kpeer; /*内核的socket地址*/
struct msg_to_kernel message; /*自定义netlink消息首部*/
memset(&kpeer, , sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = ;
kpeer.nl_groups = ;
memset(&message, , sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH();
message.hdr.nlmsg_flags = ;
message.hdr.nlmsg_type = IMP2_CLOSE;
message.hdr.nlmsg_pid = getpid();
/*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/
sendto(skfd, &message, message.hdr.nlmsg_len, ,(struct sockaddr *)(&kpeer),sizeof(kpeer));
close(skfd);
exit();
}

内核空间:

1. 创建netlink套接字(netlink_kernel_create函数)

♦ 通过netlink_kernel_create创建一个netlink套接字,同时,注册一个回调函数,用于接收处理用户空间的消息。

struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));

2. 接收处理用户空间发送的数据(kernel_receive 函数)

♦ 用户空间向内核发送了两种自定义消息类型:IMP2_U_PID和IMP2_CLOSE,分别是请求和关闭。kernel_receive 函数分别处理这两种消息:

DECLARE_MUTEX(receive_sem); /*初始化信号量*/
static void kernel_receive(struct sock *sk, int len)
{
do
{
struct sk_buff *skb;
if(down_trylock(&receive_sem)) /*获取信号量*/
return;
/*从sk接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
{
{
struct nlmsghdr *nlh = NULL;
if(skb->len >= sizeof(struct nlmsghdr))
{
/*获取数据中的nlmsghdr 结构的报头*/
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))&& (skb->len >= nlh->nlmsg_len))
{
/*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/
if(nlh->nlmsg_type == IMP2_U_PID)/*请求*/
{
write_lock_bh(&user_proc.pid);
user_proc.pid = nlh->nlmsg_pid;
write_unlock_bh(&user_proc.pid);
}
else if(nlh->nlmsg_type == IMP2_CLOSE)/*应用程序关闭*/
{
write_lock_bh(&user_proc.pid);
if(nlh->nlmsg_pid == user_proc.pid)
user_proc.pid = ;
write_unlock_bh(&user_proc.pid);
}
}
}
}
kfree_skb(skb);
}
up(&receive_sem); /*返回信号量*/
}while(nlfd && nlfd->receive_queue.qlen);
}

3. 发送数据至用户空间(send_to_user函数)

♦ send_to_user 用于将数据发送给用户空间进程,发送调用的是API函数netlink_unicast完成的:

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

♦ 向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段。函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。

static int send_to_user(struct packet_info *info)
{
int ret;
int size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;
/*计算消息总长:消息首部加上数据加度*/
size = NLMSG_SPACE(sizeof(*info));
/*分配一个新的套接字缓存*/
skb = alloc_skb(size, GFP_ATOMIC);
old_tail = skb->tail;
/*初始化一个netlink消息首部,NLMSG_PUT(skb, pid, seq, type, len)*/
nlh = NLMSG_PUT(skb, , , IMP2_K_MSG, size-sizeof(*nlh));
/*跳过消息首部,指向数据区*/
packet = NLMSG_DATA(nlh);
/*初始化数据区*/
memset(packet, , sizeof(struct packet_info));
/*填充待发送的数据*/
packet->src = info->src;
packet->dest = info->dest;
/*计算skb两次长度之差,即netlink的长度总和*/
nlh->nlmsg_len = skb->tail - old_tail;
/*设置控制字段*/
NETLINK_CB(skb).dst_groups = ; /*如果它目标为某一进程或内核,dst_group 应当设置为 0。*/
/*发送数据*/
read_lock_bh(&user_proc.lock);
ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
read_unlock_bh(&user_proc.lock);
}

NetLink通信机制学习的更多相关文章

  1. linux netlink通信机制

    一.什么是Netlink通信机制  Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口. Netlink 是一种特殊的 s ...

  2. Netlink通信机制【转】

    本文转载自:http://www.cnblogs.com/wenqiang/p/6306727.html 一.什么是Netlink通信机制  Netlink套接字是用以实现用户进程与内核进程通信的一种 ...

  3. linux netlink通信机制简介

    一.什么是Netlink通信机制  Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口. Netlink 是一种特殊的 s ...

  4. 1、netlink 连接器 通信机制

    使用netlink之前,先参考一下资料:http://www.ibm.com/developerworks/cn/linux/l-connector/ netlink通信机制介绍:资料来源 linux ...

  5. 【单页应用之通信机制】view之间应该如何通信

    前言 在单页应用中,view与view之间的通信机制一直是一个重点,因为单页应用的所有操作以及状态管理全部发生在一个页面上 没有很好的组织的话很容易就乱了,就算表面上看起来没有问题,事实上会有各种隐忧 ...

  6. 【转】跟我学Kafka之NIO通信机制

    from:云栖社区 玛德,今天又被人打脸了,小看人,艹,确实,相对比起来,在某些方面差一点,,,,该好好捋捋了,强化下短板,规划下日程,,,引以为耻,铭记于心. 跟我学Kafka之NIO通信机制   ...

  7. netlink---Linux下基于socket的内核和上层通信机制 (转)

    需要在linux网卡 驱动中加入一个自己的驱动,实现在内核态完成一些报文处理(这个过程可以实现一种零COPY的网络报文截获),对于复杂报文COPY下必要的数据交给用户 态来完成(因为过于复杂的报文消耗 ...

  8. 其实没那么复杂!探究react-native通信机制

    近段时间来Android上最火的框架非react native莫属了,这里我不去评价这个框架的好坏,毕竟只有用过的人才会有深刻的体会.但是我个人有一个习惯,在使用一个开源库之前,一定要看过它的源码,不 ...

  9. React Native通信机制详解

    React Native是facebook刚开源的框架,可以用javascript直接开发原生APP,先不说这个框架后续是否能得到大众认可,单从源码来说,这个框架源码里有非常多的设计思想和实现方式值得 ...

随机推荐

  1. streamsets 数据流设计

    streamsets 支持branch(分支)&& merge(合并)模式的数据流 branch 数据流 如下图: 我们可以根据数据包含的字段进行拆分,不同的数据流处理自己关注的数据 ...

  2. Mybatis新增mysql时中文乱码

    Mybatis新增mysql时中文乱码 1.设置数据库连接的编码(jdbc.properties) jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:my ...

  3. Hive之 hive与hadoop的联系

    Hive与Hadoop调用图 解析: 1.提交sql 交给驱动2.驱动编译:解析相关的字段表信息3.去metastore查询相关的信息 返回字段表信息4.编译返回信息 发给驱动5.驱动发送一个执行计划 ...

  4. MySQL建表规范与常见问题 (go)

    一. 表设计 库名.表名.字段名必须使用小写字母,“_”分割. 库名.表名.字段名必须不超过12个字符. 库名.表名.字段名见名知意,建议使用名词而不是动词. 建议使用InnoDB存储引擎. 存储精确 ...

  5. laravel Hash密码 校对

    laravel加密 是使用hash不可逆的,但是可以对加密后的密码进行校对 $data = $r->all();$id = $data['id'];$user_password = bcrypt ...

  6. bzoj4403 两个串

    Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有“?”字符,这个字符可以匹配任何字符. Input 两行两个字 ...

  7. 关系型数据库与Key-value型数据库Mongodb模式设计对比

    MongoDb 相比于传统的 SQL 关系型数据库,最大的不同在于它们的模式设计( Schema Design )上的差别,正是由于这一层次的差别衍生出其它各方面的不同. 我们可以简单的认为关系型数据 ...

  8. 第 0 课 Golang环境搭建

    1  下载 https://studygolang.com/dl  2   安装完成后,查看环境变量 GOBIN 存放可执行文件的目录的绝对路径. GOPATH 工作区目录的绝对路径.存放远源码的路径 ...

  9. Request模块(八)

    Requests: 让 HTTP 服务人类 虽然Python的标准库中 urllib2 模块已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好,而 Requests 自称 “ ...

  10. oracle sqlplus conn 远程连接