首页 > 代码库 > quagga源码分析--内核通信netlink
quagga源码分析--内核通信netlink
Linux操作系统中当CPU处于内核状态时,可以分为有用户上下文的状态和执行硬件、软件中断两种。其中当处于有用户上下文时,由于内核态和用户态的内 存映射机制不同,不可直接将本地变量传给用户态的内存区;处于硬件、软件中断时,无法直接向用户内存区传递数据,代码执行不可中断。针对传统的进程间通信 机制,他们均无法直接在内核态和用户态之间使用,原因如下表:
通信方法 |
无法介于内核态与用户态的原因 |
管道(不包括命名管道) |
局限于父子进程间的通信。 |
消息队列 |
在硬、软中断中无法无阻塞地接收数据。 |
信号量 |
无法介于内核态和用户态使用。 |
内存共享 |
需要信号量辅助,而信号量又无法使用。 |
套接字 |
在硬、软中断中无法无阻塞地接收数据。 |
一、解决内核态和用户态通信机制可分为两类:
- 处于有用户上下文时,可以使用Linux提供的copy_from_user()和copy_to_user()函数完成,但由于这两个函数可能阻塞,因此不能在硬件、软件的中断过程中使用。
- 处于硬、软件中断时。
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初始化的时候注册了这些消息对于的处理函数:
1 void __init rtnetlink_init(void) 2 { 3 if (register_pernet_subsys(&rtnetlink_net_ops)) 4 panic("rtnetlink_init: cannot initialize rtnetlink\n"); 5 6 register_netdevice_notifier(&rtnetlink_dev_notifier); 7 8 rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, 9 rtnl_dump_ifinfo, rtnl_calcit); 10 rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL); 11 rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, NULL); 12 rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, NULL); 13 14 rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, NULL); 15 rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, NULL); 16 17 rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, NULL); 18 rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, NULL); 19 rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL); 20 21 rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL); 22 rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL); 23 rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL); 24 }
ok,打开了内核的通路,下面就看看怎么在用户态的使用吧:
1、quagga的sock定义
1 struct nlsock 2 { 3 int sock; 4 int seq; 5 struct sockaddr_nl snl; 6 const char *name; 7 }; 8 9 struct sockaddr_nl { 10 __kernel_sa_family_t nl_family; /* AF_NETLINK */ 11 unsigned short nl_pad; /* zero */ 12 __u32 nl_pid; /* port ID */ 13 __u32 nl_groups; /* multicast groups mask */ 14 };
2、创建socket
1 /* Make socket for Linux netlink interface. */ 2 static int 3 netlink_socket(struct nlsock *nl, unsigned long groups, vrf_id_t vrf_id) { 4 int ret; 5 struct sockaddr_nl snl; 6 int sock; 7 int namelen; 8 int save_errno; 9 10 if (zserv_privs.change(ZPRIVS_RAISE)) { 11 zlog(NULL, LOG_ERR, "Can‘t raise privileges"); 12 return -1; 13 } 14 15 sock = vrf_socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE, vrf_id); 16 if (sock < 0) { 17 zlog(NULL, LOG_ERR, "Can‘t open %s socket: %s", nl->name, 18 safe_strerror(errno)); 19 return -1; 20 } 21 22 memset(&snl, 0, sizeof snl); 23 snl.nl_family = AF_NETLINK; 24 snl.nl_groups = groups; 25 26 /* Bind the socket to the netlink structure for anything. */ 27 ret = bind(sock, (struct sockaddr *)&snl, sizeof snl); 28 save_errno = errno; 29 if (zserv_privs.change(ZPRIVS_LOWER)) zlog(NULL, LOG_ERR, "Can‘t lower privileges"); 30 31 if (ret < 0) { 32 zlog(NULL, LOG_ERR, "Can‘t bind %s socket to group 0x%x: %s", 33 nl->name, snl.nl_groups, safe_strerror(save_errno)); 34 close(sock); 35 return -1; 36 } 37 38 /* multiple netlink sockets will have different nl_pid */ 39 namelen = sizeof snl; 40 ret = getsockname(sock, (struct sockaddr *)&snl, (socklen_t *)&namelen); 41 if (ret < 0 || namelen != sizeof snl) { 42 zlog(NULL, LOG_ERR, "Can‘t get %s socket name: %s", nl->name, 43 safe_strerror(errno)); 44 close(sock); 45 return -1; 46 } 47 48 nl->snl = snl; 49 nl->sock = sock; 50 return ret; 51 }
quagga源码分析--内核通信netlink