内核态到用户态通信
(一) netlink简介
netlink
套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC
),也是网络应用程序与内核通信的最常用的接口。
(二) netlink使用
下面首先通过一个实际问题来说明netlink的使用方法
1、要解决的问题
- 环境
硬件:路由器
, 操作系统:openwrt
,芯片:mt7628
- 问题
解决无线驱动接口的状态消息发送到用户态程序
2、无线驱动中使用netlink
在加入该代码的文件必须包含内核部分封装netlink的源文件
/* 封装netlink在内核态发送消息到用户态的代码 */
enum {
ACTION_DOWN = 0, /* 初始默认APCLI状态为down */
ACTION_UP
};
/* 在下述apcli_status.h文件中实现 */
extern int send_netlink_data(char *data, u_int data_len);
static VOID ApCliCtrlSendNetlinkMsg(
IN PRTMP_ADAPTER pAd,
IN USHORT ifIndex,
IN ULONG action)
{
NlAPCliStatusMsg nlData;
memset(&nlData, 0, sizeof(nlData));
nlData.msg_type = action;
nlData.band_type = WIRELESS_BAND_2G;
nlData.ssid_len = pAd->ApCfg.ApCliTab[ifIndex].SsidLen;
memcpy(nlData.ssid, pAd->ApCfg.ApCliTab[ifIndex].Ssid, MAX_LEN_OF_SSID);
send_netlink_data((char *)&nlData, sizeof(nlData));
DBGPRINT(RT_DEBUG_TRACE, ("(%s) 2.4G %s, send netlink msg.\n", __FUNCTION__, action == ACTION_DOWN? "down" : "up"));
}
在相应的地方调用上述函数发送消息
ApCliCtrlSendNetlinkMsg(pAd, ifIndex, ACTION_UP);
3、内核封装netlink
无线驱动使用netlink时,需要包含如下两个文件
/*************************************************************************
File name: apcli_status.c
Description:
The module supports reporting status to application layer
*************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/apcli_status.h>
#define DEBUG_APCLI_STATUS 0
#if DEBUG_APCLI_STATUS
#define DEBUG_PRINT(fmt ,args...) printk("[%s |%d]" fmt, __func__, __LINE__, ##args)
#else
#define DEBUG_PRINT(fmt, argx...)
#endif
static int pid = 0;
static struct sock *nl_sk = NULL;
static DEFINE_MUTEX(apcli_status_mutex);
int send_netlink_data(char *data, u_int data_len)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
int ret = 0;
if(!nl_sk) {
DEBUG_PRINT("netlink sock error .\n");
return -1;
}
skb = nlmsg_new(data_len, GFP_ATOMIC);
if(skb) {
nlh = nlmsg_put(skb, 0, 0, 0, data_len, 0);
NETLINK_CB(skb).dst_group = 0;
memcpy(NLMSG_DATA(nlh), data, data_len);
ret = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
} else {
ret = -1;
DEBUG_PRINT("nlmsg_new error .\n");
}
DEBUG_PRINT("send netlink msg, ret:%d\n", ret);
return ret;
}
EXPORT_SYMBOL(send_netlink_data);
static int rcv_user_msg (struct sk_buff *skb, struct nlmsghdr *nlh)
{
if (skb->len < sizeof(struct nlmsghdr)) {
DEBUG_PRINT("skb len error.\n");
return 1;
}
NlAPCliStatusMsg *nlData;
nlData = (NlAPCliStatusMsg *)NLMSG_DATA(nlh);
DEBUG_PRINT("receive msg_type %d .\n", nlData->msg_type);
switch(nlData->msg_type)
{
case INIT_UNICAST_PID:
pid= nlh->nlmsg_pid;
DEBUG_PRINT("get pid %d .\n", pid);
break;
default:
break;
}
return 0;
}
static void netlink_rcv(struct sk_buff *skb)
{
mutex_lock(&apcli_status_mutex);
netlink_rcv_skb(skb, &rcv_user_msg);
mutex_unlock(&apcli_status_mutex);
}
static int __init apcli_status_init(void)
{
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv,
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_APCLI_STATUS, &cfg);
if(!nl_sk){
DEBUG_PRINT("netlink_kernel_create netlink socket error.\n");
return -1;
}
return 0;
}
static void __init apcli_status_exit(void)
{
if(nl_sk != NULL) {
netlink_kernel_release(nl_sk);
nl_sk = NULL;
}
pid = 0;
}
module_init(apcli_status_init);
module_exit(apcli_status_exit);
MODULE_AUTHOR("zhangyahai");
MODULE_LICENSE("GPL");
/* include/linux/apcli_status.h */
#ifndef __APCLI_STATUS_H__
#define __APCLI_STATUS_H__
#define MAX_SSID_LEN 32
#define NETLINK_MAX_NLMSGSIZE 1024
typedef enum {
INIT_UNICAST_PID = 0,
ACTION_UP, /* DISCONNECTED -> CONNECTED */
ACTION_DOWN /* CONNECTED -> DISCONNECTED */
} NetlinkMsgType;
typedef enum {
WIRELESS_BAND_2G = 0,
WIRELESS_BAND_5G
} NetlinkBandType;
/* netlink message struct */
typedef struct st_NlAPCliStatusMsg {
NetlinkMsgType msg_type;
NetlinkBandType band_type;
unsigned char ssid[MAX_SSID_LEN];
unsigned char ssid_len;
} NlAPCliStatusMsg;
#endif
4、用户态程序通过netlink接收事件消息
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#include <syslog.h>
#include <linux/apcli_status.h>
#include "libapcli.h"
#include "libphic.h"
#include "apcli_status_action.h"
APCliInfo apcli_info;
int debug_print = 1;
int apcli_br_2g = 0;
int apcli_br_5g = 0;
int apcli_udhcpc = 0;
static void dpf(const char *format, ...)
{
if(debug_print){
va_list args;
va_start(args, format);
vprintf(format,args);
va_end(args);
}
}
static int _system(char *cmdbuf)
{
int stat;
stat = system(cmdbuf);
if(WIFEXITED(stat))
{
if(WEXITSTATUS(stat) == 0)
{
dpf("cmd ok:%s \n", cmdbuf);
return 1;
}
else
{
dpf("cmd err:%s, wexitstaus [0x%x] \n", cmdbuf, WEXITSTATUS(stat));
return 0;
}
}
dpf("cmd err:%s, wifexited [0x%x]\n", cmdbuf, WIFEXITED(stat));
return 0;
}
static void do_system(const char* format, ...)
{
char cmdstr[BUFSIZ];
va_list args;
memset(cmdstr, 0, sizeof(cmdstr));
va_start(args,format);
vsnprintf(cmdstr, sizeof(cmdstr)-1, format, args );
va_end(args);
_system(cmdstr);
}
static int get_cmd_value (char *cmd_str, char *value, int len )
{
FILE *fp=NULL;
if(cmd_str == NULL ||value == NULL) {
return -1;
}
memset(value, 0, len);
fp = popen(cmd_str, "r");
if(fp) {
fgets(value, len, fp);
if(value[strlen(value)-1] == '\n') {
value[strlen(value)-1] = '\0';
}
pclose(fp);
return 0;
} else {
dpf("cmd:%s error!%s\n",cmd_str,strerror(errno));
return -1;
}
}
static int apcli_netlink_sock_bind (unsigned int pid, unsigned int grp)
{
struct sockaddr_nl src_addr;
int sock_fd;
sock_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_APCLI_STATUS);
if(sock_fd == -1) {
syslog(LOG_ERR, "error getting socket: %s", strerror(errno));
} else {
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = pid; /*unicast*/
src_addr.nl_groups = grp; /*broadcast*/
int status = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
if(status == -1) {
syslog(LOG_ERR, "bind failed: %s", strerror(errno));
sock_fd = -1;
close(sock_fd);
}
}
return sock_fd;
}
static int apcli_nl_msg_send (int sock_fd, char *data,unsigned int data_len, unsigned int dpid, unsigned int dgrp)
{
struct sockaddr_nl dest_addr;
struct iovec iov;
struct msghdr msg;
char buf[NETLINK_MAX_NLMSGSIZE];
struct nlmsghdr *nlh = NULL;
int ret = 0;
nlh = (struct nlmsghdr*) buf;
if(NLMSG_SPACE(data_len) <= NETLINK_MAX_NLMSGSIZE) {
memset(buf, 0, sizeof(buf));
nlh->nlmsg_len = NLMSG_SPACE(data_len);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags |= NLM_F_REQUEST ;
nlh->nlmsg_type = 0x26;
memcpy(NLMSG_DATA(nlh), data, data_len);
memset(&dest_addr,0,sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = dpid;
dest_addr.nl_groups = dgrp;
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(data_len);
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if(sendmsg(sock_fd, &msg, 0) == -1) {
syslog(LOG_ERR, "get error sendmsg = %s",strerror(errno));
}
}
return ret;
}
static void apcli_netlink_unicast_pid(int sock_fd)
{
NlAPCliStatusMsg nlData;
memset(&nlData, 0, sizeof(nlData));
nlData.msg_type = INIT_UNICAST_PID;
if(sock_fd > 0) {
apcli_nl_msg_send(sock_fd, (char *)&nlData, sizeof(nlData), 0, 0);
}
}
int main(int argc, char *argv[])
{
struct sockaddr_nl nl_addr;
struct iovec iov;
struct msghdr msg;
char buf[NETLINK_MAX_NLMSGSIZE];
struct nlmsghdr *nlh = NULL;
NlAPCliStatusMsg *nlData = NULL;
int apcli_nl_sock_fd = -1;
openlog("apcli_status", LOG_CONS, LOG_DAEMON);
memset(&msg, 0, sizeof(msg));
nlh = (struct nlmsghdr*) buf;
iov.iov_base = (void *)nlh;
msg.msg_name = (void *)&nl_addr;
msg.msg_namelen = sizeof(nl_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
apcli_nl_sock_fd = apcli_netlink_sock_bind(getpid(), 0);
if (apcli_nl_sock_fd < 0) {
syslog(LOG_ERR, "netlink_sock_bind error");
return -1;
}
apcli_netlink_unicast_pid(apcli_nl_sock_fd);
while(1) {
iov.iov_len = NETLINK_MAX_NLMSGSIZE;
memset(buf, 0 ,sizeof(buf));
if(recvmsg(apcli_nl_sock_fd, &msg, 0) > 0) {
nlData = (NlAPCliStatusMsg *)NLMSG_DATA(nlh);
switch(nlData->msg_type)
{
case ACTION_UP:
// do something ...
break;
case ACTION_DOWN:
// do something ...
break;
default:
break;
}
}
}
closelog();
return 0;
}