Linux操作系统中当CPU处于内核状态时,可以分为有用户上下文的状态和执行硬件、软件中断两种。其中当处于有用户上下文时,由于内核态和用户态的内 存映射机制不同,不可直接将本地变量传给用户态的内存区;处于硬件、软件中断时,无法直接向用户内存区传递数据,代码执行不可中断。针对传统的进程间通信 机制,他们均无法直接在内核态和用户态之间使用,原因如下表:

 通信方法

无法介于内核态与用户态的原因

管道(不包括命名管道)

局限于父子进程间的通信。

消息队列

在硬、软中断中无法无阻塞地接收数据。

信号量

无法介于内核态和用户态使用。

内存共享

需要信号量辅助,而信号量又无法使用。

套接字

在硬、软中断中无法无阻塞地接收数据。

一、解决内核态和用户态通信机制可分为两类:

  1. 处于有用户上下文时,可以使用Linux提供的copy_from_user()和copy_to_user()函数完成,但由于这两个函数可能阻塞,因此不能在硬件、软件的中断过程中使用。
  2. 处于硬、软件中断时。

2.1   可以通过Linux内核提供的spinlock自旋锁实现内核线程与中断过程的同步,由于内核线程运行在有上下文的进程中,因此可以在内核线程中使用套接字或消息队列来取得用户空间的数据,然后再将数据通过临界区传递给中断过程.

2.2   通过Netlink机制实现。Netlink 套接字的通信依据是一个对应于进程的标识,一般定为该进程的 ID。Netlink通信最大的特点是对对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用 用户事先指定的接收函数。通过软中断而不是自行启动内核线程保证了数据传输的及时性。

二、Netlink优点

Netlink相对于其他的通信机制具有以下优点:

使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
    Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。
    Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。
    Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。

三、quagga代码中的netlink

rt_link.c:

static const struct message nlmsg_str[] = {
{ RTM_NEWROUTE, "RTM_NEWROUTE" },
{ RTM_DELROUTE, "RTM_DELROUTE" },
{ RTM_GETROUTE, "RTM_GETROUTE" },
{ RTM_NEWLINK, "RTM_NEWLINK" },
{ RTM_DELLINK, "RTM_DELLINK" },
{ RTM_GETLINK, "RTM_GETLINK" },
{ RTM_NEWADDR, "RTM_NEWADDR" },
{ RTM_DELADDR, "RTM_DELADDR" },
{ RTM_GETADDR, "RTM_GETADDR" },
{ 0, NULL }
};

struct message nimsg_str[] 定义了需要使用的netlink消息类型集合的一个描述。以及对于描述字符串,在后续调试打印,比如_netlink_route_debug 等函数使用。

内核在rtnetlink初始化的时候注册了这些消息对于的处理函数:

 void __init rtnetlink_init(void)
{
if (register_pernet_subsys(&rtnetlink_net_ops))
panic("rtnetlink_init: cannot initialize rtnetlink\n"); register_netdevice_notifier(&rtnetlink_dev_notifier); rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink,
rtnl_dump_ifinfo, rtnl_calcit);
rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL);
rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, NULL);
rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, NULL); rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, NULL);
rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, NULL); rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, NULL);
rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, NULL);
rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL); rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL);
rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL);
rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL);
}
 void __init ip_fib_init(void)
{
rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL);
rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL);
rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib, NULL); register_pernet_subsys(&fib_net_ops);
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier); fib_trie_init();
}

ok,打开了内核的通路,下面就看看怎么在用户态的使用吧:

1、quagga的sock定义

 struct nlsock
{
int sock;
int seq;
struct sockaddr_nl snl;
const char *name;
}; struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* multicast groups mask */
};

2、创建socket

 /* Make socket for Linux netlink interface. */
static int
netlink_socket(struct nlsock *nl, unsigned long groups, vrf_id_t vrf_id) {
int ret;
struct sockaddr_nl snl;
int sock;
int namelen;
int save_errno; if (zserv_privs.change(ZPRIVS_RAISE)) {
zlog(NULL, LOG_ERR, "Can't raise privileges");
return -;
} sock = vrf_socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE, vrf_id);
if (sock < ) {
zlog(NULL, LOG_ERR, "Can't open %s socket: %s", nl->name,
safe_strerror(errno));
return -;
} memset(&snl, , sizeof snl);
snl.nl_family = AF_NETLINK;
snl.nl_groups = groups; /* Bind the socket to the netlink structure for anything. */
ret = bind(sock, (struct sockaddr *)&snl, sizeof snl);
save_errno = errno;
if (zserv_privs.change(ZPRIVS_LOWER)) zlog(NULL, LOG_ERR, "Can't lower privileges"); if (ret < ) {
zlog(NULL, LOG_ERR, "Can't bind %s socket to group 0x%x: %s",
nl->name, snl.nl_groups, safe_strerror(save_errno));
close(sock);
return -;
} /* multiple netlink sockets will have different nl_pid */
namelen = sizeof snl;
ret = getsockname(sock, (struct sockaddr *)&snl, (socklen_t *)&namelen);
if (ret < || namelen != sizeof snl) {
zlog(NULL, LOG_ERR, "Can't get %s socket name: %s", nl->name,
safe_strerror(errno));
close(sock);
return -;
} nl->snl = snl;
nl->sock = sock;
return ret;
}
 groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
#ifdef HAVE_IPV6
groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR;
#endif /* HAVE_IPV6 */
netlink_socket(&zvrf->netlink, groups, zvrf->vrf_id);
netlink_socket(&zvrf->netlink_cmd, , zvrf->vrf_id);
/* Callback upon enabling a VRF. */
static int
zebra_vrf_enable(vrf_id_t vrf_id, void **info) {
struct zebra_vrf *zvrf = (struct zebra_vrf *)(*info); assert(zvrf); #if defined (HAVE_RTADV)
rtadv_init(zvrf);
#endif
kernel_init(zvrf);
interface_list(zvrf);
route_read(zvrf); return 0;
}

/* Zebra VRF initialization. */
static void
zebra_vrf_init(void) {
vrf_add_hook(VRF_NEW_HOOK, zebra_vrf_new);
vrf_add_hook(VRF_ENABLE_HOOK, zebra_vrf_enable);
vrf_add_hook(VRF_DISABLE_HOOK, zebra_vrf_disable);
vrf_init();
} 1 /* Initialize VRF module, and make kernel routing socket. */
zebra_vrf_init();

由zebra_vrf_init在main函数完成了netlink socket的初始化。

上面创建了2个socket。

1.netlink.sock用于接收内核主动发送的消息,比如动态路由:

 /* Kernel route reflection. */
static int
kernel_read(struct thread *thread) {
struct zebra_vrf *zvrf = (struct zebra_vrf *)THREAD_ARG(thread);
netlink_parse_info(netlink_information_fetch, &zvrf->netlink, zvrf);
zvrf->t_netlink = thread_add_read(zebrad.master, kernel_read, zvrf,
zvrf->netlink.sock); return ;
}
注意:netlink_information_fetch函数接收内核发送过来的信息并做相应的处理。
内核的netlink消息发送函数:
 /**
* nlmsg_notify - send a notification netlink message
* @sk: netlink socket to use
* @skb: notification message
* @portid: destination netlink portid for reports or 0
* @group: destination multicast group or 0
* @report: 1 to report back, 0 to disable
* @flags: allocation flags
*/
int nlmsg_notify(struct sock *sk, struct sk_buff *skb, u32 portid,
unsigned int group, int report, gfp_t flags)
{
int err = ; if (group) {
int exclude_portid = ; if (report) {
atomic_inc(&skb->users);
exclude_portid = portid;
} /* errors reported via destination sk->sk_err, but propagate
* delivery errors if NETLINK_BROADCAST_ERROR flag is set */
err = nlmsg_multicast(sk, skb, exclude_portid, group, flags);
} if (report) {
int err2; err2 = nlmsg_unicast(sk, skb, portid);
if (!err || err == -ESRCH)
err = err2;
} return err;
}


2.netlink_cmd.sock用于向内核发送上面定义的消息请求:

 /* Routing table read function using netlink interface.  Only called
bootstrap time. */
int
netlink_route_read(struct zebra_vrf *zvrf) {
int ret; /* Get IPv4 routing table. */
ret = netlink_request(AF_INET, RTM_GETROUTE, &zvrf->netlink_cmd);
if (ret < ) return ret;
ret = netlink_parse_info(netlink_routing_table, &zvrf->netlink_cmd, zvrf);
if (ret < ) return ret; #ifdef HAVE_IPV6
/* Get IPv6 routing table. */
ret = netlink_request(AF_INET6, RTM_GETROUTE, &zvrf->netlink_cmd);
if (ret < ) return ret;
ret = netlink_parse_info(netlink_routing_table, &zvrf->netlink_cmd, zvrf);
if (ret < ) return ret;
#endif /* HAVE_IPV6 */ return ;
}

quagga还支持其他的内核交互接口,如ioctl,sysctl,procfs等等。

quagga源码分析--内核通信netlink的更多相关文章

  1. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  2. Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...

  3. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  4. v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  5. v87.01 鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main () | 百篇博客分析 OpenHarmony 源码

    本篇关键词:内核重定位.MMU.SVC栈.热启动.内核映射表 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 鸿蒙内核源码分析(汇编基础) | ...

  6. Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内 ...

  7. Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

    前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就 ...

  8. quagga源码分析--大内总管zebra

    zebra,中文翻译是斑马,于是我打开了宋冬野的<斑马,斑马>作为BGM来完成这个篇章,嘿嘿,小资一把! zebra姑且戏称它是quagga项目的大内总管. 因为它负责管理其他所有协议进程 ...

  9. quagga源码分析--路由信息处理zebra-rib

    对于各个协议生成的路由信息的处理属于quagga中非常重要的一个功能,如何在内核进行路由增加,更新,删除是一个复杂的过程. quagga在thread任务调度中加入了一种工作队列,work_queue ...

随机推荐

  1. 安装Visual Studio 2010 - 初学者系列 - 学习者系列文章

    本文讲述如何安装Visual Studio 2010开发工具. 首先,通过下列地址获取Visual Studio 2010的副本 1.开始页面 2.欢迎页 3.这里选择 自定义 ,选择安装路径 4.这 ...

  2. 如何使用OPENQUERY访问另一个SQL Server

    原文:如何使用OPENQUERY访问另一个SQL Server 在项目中,经常会遇到一个数据库访问另一个数据库,[CNVFERPDB]为服务器名,[CE3]为库名 SELECT Dtl.* FROM ...

  3. Android在包名称更改项目

    通常时引起包名称的变化R文件错误,有时原因不明Manifest混乱多个文本文件. 所以,我们现在感到最简单方便的包名称变更流程文件,如以下: 如果程序包命名com.pepper.util,我们将更改包 ...

  4. Unity 3.5

    ASP.NET Web Forms 的 DI 應用範例 跟 ASP.NET MVC 与 Web API 比起来,在 Web Forms 应用程式中使用 Dependency Injection 要来的 ...

  5. Google Dataflow

    十分钟了解分布式计算:Google Dataflow 介绍 Google Cloud Dataflow是一种构建.管理和优化复杂数据处理流水线的方法,集成了许多内部技术,如用于数据高效并行化处理的Fl ...

  6. AngularJS 3

    AngularJS 源码分析3 本文接着上一篇讲 上一篇地址 回顾 上次说到了rootScope里的$watch方法中的解析监控表达式,即而引出了对parse的分析,今天我们接着这里继续挖代码. $w ...

  7. 最简单的修改HashMap value值的方法

    说到遍历,首先应该想到for循环,然而map集合的遍历通常情况下是要这样在的,先要获得一个迭代器. Map<Integer,String> map = new HashMap<> ...

  8. Product Trader(操盘手)

    Product Trader(操盘手) 索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Product Trader 的示例实现. 意图 使客户程序可以通过命名抽象超类和给定规 ...

  9. Extjs表单控件入门

    ExtJs表单控件用formPanel来做为表单元素的容器.默认情况下,是使用Ajax异步提交. 大家知道要使用Extjs必须引入他的库,所以我们要引入以下几个文件: ext-all.css ext- ...

  10. Redis for Windows(C#缓存)配置文件详解

    Redis for Windows(C#缓存)配置文件详解   前言 在上一篇文章中主要介绍了Redis在Windows平台下的下载安装和简单使用http://www.cnblogs.com/aehy ...