在Linux常常需要对网口和USB等外设接口进行插拔检测,从而执行部分初始化操作。下面简要介绍Linux的Netlink机制,并在C程序中使用Linux的Netlink机制完成网口和USB口插拔检测。
Netlink 是 Linux 内核与用户空间进程通信的一种机制,主要用于内核模块和用户程序之间的数据传输。它比传统的通信方式(如 ioctl、procfs、sysfs)更灵活高效。
一、主要特点
双向通信:支持内核与用户空间的双向数据传输。
多协议支持:通过不同的协议类型(如 NETLINK_ROUTE、NETLINK_SOCK_DIAG)满足多种通信需求。
多播支持:允许一个消息同时发送给多个接收者。
异步通信:支持异步消息传递,适合事件驱动场景。
二、使用场景
网络配置:如 iproute2 工具通过 Netlink 配置网络接口和路由。
防火墙和策略路由:如 iptables 和 nftables 使用 Netlink 配置规则。
设备管理:如 udev 通过 Netlink 监控设备事件。
三、基本使用步骤
1.创建套接字:用户空间使用 socket() 创建 Netlink 套接字。
int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
2.绑定地址:绑定 Netlink 地址。
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid(); // 通常使用进程ID
bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
3.发送和接收消息:使用 sendmsg() 和 recvmsg() 进行消息传递。
4.处理消息:解析和处理接收到的消息。
Demo1:使用Netlink实现网口热插拔。(ZYNQ-7020平台,linux内核为4.19)
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/rtnetlink.h>
#include <linux/netlink.h>
typedef void (*network_status_callback)(char *eth_name,int status) ;
void network_link_monitor(network_status_callback arg)
{
int sock;
int ret;
struct sockaddr_nl sa;
int len=4096;
char buf[4096]={0};
struct nlmsghdr *nh;
struct ifinfomsg *ifinfo;
struct rtattr *attr;
network_status_callback nm=arg;
int i;
memset(&sa,0,sizeof(sa));
sa.nl_family=AF_NETLINK;
sa.nl_groups=RTNLGRP_LINK;
//sa.nl_pid=getpid();
//创建套接字和绑定Netlink地址,这里使用NETLINK_ROUTE
sock=socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE);
if(sock<0)
printf("socket err %d\n",__LINE__);
setsockopt(sock,SOL_SOCKET,SO_RCVBUF,&len,sizeof(len));
if(bind(sock,(struct sockaddr*)&sa,sizeof(sa))<0)
printf("bind err %d\n",__LINE__);
//阻塞接收内核的Netlink消息,可根据应用需求使用非阻塞IO读
while((ret=read(sock,buf,sizeof(buf)))>0)
{
for(nh=(struct nlmsghdr*)buf;NLMSG_OK(nh,ret);nh=NLMSG_NEXT(nh,ret))
{
//解析和处理接收到的内核消息
if(nh->nlmsg_type==NLMSG_DONE)
break;
else if(nh->nlmsg_type==NLMSG_ERROR)
{
printf("read err %d\n",__LINE__);
return ;
}
else if (nh->nlmsg_type!=RTM_NEWLINK)
{
printf("nlmsg_type err %d\n",__LINE__);
continue;
}
ifinfo=NLMSG_DATA(nh);
//printf("%u : %s \n",ifinfo->ifi_index,(ifinfo->ifi_flags&IFF_LOWER_UP)?"UP":"DOWN");
attr=(struct rtattr*)(((char*)nh)+NLMSG_SPACE(sizeof(*ifinfo)));
len=nh->nlmsg_len-NLMSG_SPACE(sizeof(*ifinfo));
for(;RTA_OK(attr,len);attr=RTA_NEXT(attr,len))
{
if(attr->rta_type==IFLA_IFNAME)
{
//执行热插拔检测回调函数
//printf("%s : %s\n",(char*)RTA_DATA(attr),(ifinfo->ifi_flags&IFF_LOWER_UP)?"up":"down");
if(ifinfo->ifi_flags&IFF_LOWER_UP)
nm((char*)RTA_DATA(attr),LINK_UP);
else
nm((char*)RTA_DATA(attr),LINK_DOWN);
break;
}
}
}
}
}
/*
网络插拔检测回调
*/
void monitor_callback(char *eth_name,int status)//status--1 up
{
printf("%s %s\n",eth_name,status?"up":"down");
}
int main()
{
printf("net test\n");
network_link_monitor(monitor_callback);
while(1)sleep(1);
return 0;
}
编译Demo生成net_test拷贝到板子上
测试结果:重复插拔网口可看到内核打印信息和应用程序打印网口up和down信息
Demo2:使用Netlink实现USB口热插拔。(ZYNQ-7020平台,linux内核为4.19)
static void usb_host_thread(void *arg)
{
char rcv_buf[32]={0};
int link_status=0;
int ret=0;
//判断USB口初始拔插状态
ret=usb_exec(USB_HOST_CMD);
if(ret==0)
link_status=1;
else
link_status=0;
struct sockaddr_nl client;
struct timeval tv;
int CppLive,rcvlen;
fd_set fds;
int buffersize=1024;
//创建套接字和绑定Netlink地址,这里使用NETLINK_KOBJECT_UEVENT
CppLive=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
if(CppLive<0)
{
printf("socket err %d\n",__LINE__);
return;
}
memset(&client,0,sizeof(client));
client.nl_family=AF_NETLINK;
//client.nl_pid=getpid();
client.nl_groups=1;
setsockopt(CppLive,SOL_SOCKET,SO_RCVBUF,&buffersize,sizeof(buffersize));
bind(CppLive,(struct sockaddr*)&client,sizeof(client));
while(1)
{
char buf[2048]={0};
/*
FD_ZERO(&fds);
FD_SET(CppLive,&fds);
tv.tv_sec=0;
tv.tv_usec=100 * 1000;
ret=select(CppLive+1,&fds,NULL,NULL,&tv);
if(ret<0)
{
printf("select err %d\n",__LINE__);
continue;
}
if(!(ret>0 && FD_ISSET(CppLive, &fds)))
{
printf("select err %d\n",__LINE__);
continue;
}
*/
//阻塞接收内核的Netlink消息,可根据应用需求使用非阻塞IO读
rcvlen=recv(CppLive,buf,sizeof(buf),0);
if(rcvlen > 0)
{
//解析和处理接收到的内核消息
//printf("%s\n",buf);
if(strstr(buf,"add")!=NULL)
{
if(link_status==0)
{
((void (*)(int))arg)(1);
}
link_status=1;
}
else if(strstr(buf,"remove")!=NULL)
{
if(link_status==1)
{
((void (*)(int))arg)(0);
}
link_status=0;
}
}
}
}
void callback(int status)
{
if(status==1)
printf("usb host up\n");
if(status==0)
printf("usb host down\n");
}
int main()
{
printf("usb test\n");
usb_host_monitor(callback);
while(1)sleep(1);
return 0;
}
编译Demo生成usb_test拷贝到板子上
测试结果:重复插拔USB host口可看到应用程序打印USB口的up和down信息