1. 程式人生 > >netlink實現驅動和應用層通訊

netlink實現驅動和應用層通訊

1、netlink及相關介面說明
        說到驅動和應用層進行資料通訊,很多人就會想到使用傳統的ioctl,誠然,很多時候應用層呼叫驅動進行引數配置和引數獲取使用的也正是ioctl的方式,但這種方式有一種侷限性,那就是隻適合於資料量較小且操作不頻繁的情況。否則,頻繁操作io將大大影響裝置的效能,甚至造成裝置宕機。
        最近在做一個關於wifi診斷需求時,需要記錄wifi連線過程中的控制幀和管理幀,以及STA斷開及斷開原因。由於wifi驅動不斷在進行資料幀和控制幀的互動,如果直接在驅動中操作io寫檔案,wifi效能將收到很大影響。後來相到了使用netlink進行資料傳輸,將資料傳輸到應用層進行log的記錄。
        下面簡單介紹下linux下netlink相關的介面,由於我使用的核心版本是linux-lsk-v4.1.25,介面主要介紹該版本介面,程式碼例項中會進行版本相容,需要連線其他版本的請自行跟讀相關版本原始碼。

1.1、netlink_kernel_create介面
        netlink_kernel_create介面的定義在lnclude/linux/netlink.h中,可以看出他是真身實際上是__netlink_kernel_create,具體我們不需要過多關心,我們先看看他的引數和返回值情況。

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{
	return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}

        第一個引數(struct net)是一個網路的namespace,在不同的名字空間裡面可以有自己的轉發資訊庫,有自己的一套net_device等等,預設情況下都是使用 init_net這個全域性變數。
        第二個引數(int unit)實際上是當前使用的例項號,由於Netlink本身是linux的一個部分,linux核心中的很多和應用層的互動也使用過netlink(比如防火牆iptables)。因此部分協議號實際已經被佔用,我們只能從之後的協議號中挑選能使用的協議號,否則將造成協議衝突。協議號的定義在include/uapi/linux/netlink.h中,如下。

#define NETLINK_ROUTE		0	/* Routing/device hook				*/
#define NETLINK_UNUSED		1	/* Unused number				*/
#define NETLINK_USERSOCK	2	/* Reserved for user mode socket protocols 	*/
#define NETLINK_FIREWALL	3	/* Unused number, formerly ip_queue		*/
#define NETLINK_SOCK_DIAG	4	/* socket monitoring				*/
#define NETLINK_NFLOG		5	/* netfilter/iptables ULOG */
#define NETLINK_XFRM		6	/* ipsec */
#define NETLINK_SELINUX		7	/* SELinux event notifications */
#define NETLINK_ISCSI		8	/* Open-iSCSI */
#define NETLINK_AUDIT		9	/* auditing */
#define NETLINK_FIB_LOOKUP	10	
#define NETLINK_CONNECTOR	11
#define NETLINK_NETFILTER	12	/* netfilter subsystem */
#define NETLINK_IP6_FW		13
#define NETLINK_DNRTMSG		14	/* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT	15	/* Kernel messages to userspace */
#define NETLINK_GENERIC		16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
#define NETLINK_ECRYPTFS	19
#define NETLINK_RDMA		20
#define NETLINK_CRYPTO		21	/* Crypto layer */

#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG

#define MAX_LINKS 32	

        從上述程式碼中可以看出,協議號最大支援到32,目前0~21已經被佔用,所以實際我們能選擇的只有22-31之間的某一個數。
        第三個引數(struct netlink_kernel_cfg *cfg)是關於netlink的相關引數的封裝,之前的版本中該結構體實際上是分成好幾個引數實現的,具體見之後的例項。該結構體如下,我們主要關注其中註釋部分。

struct netlink_kernel_cfg {
	unsigned int	groups;  //多播組,一般多播的時候設定成1,單播設定成0
	unsigned int	flags;
	void		(*input)(struct sk_buff *skb);  //回撥函式,核心收到應用層發過來的msg時會呼叫此函式。
	struct mutex	*cb_mutex;
	int		(*bind)(struct net *net, int group);
	void		(*unbind)(struct net *net, int group);
	bool		(*compare)(struct net *net, struct sock *sk);
};

        netlink_kernel_create介面的返回值是一個socket描述符,類似於socket程式設計中返回的socket,主要用於msg 傳送時的參入引數,便於與應用層套接字介面建立連線。

1.2、netlink_unicast介面
        netlink_unicast介面如下,幾個引數說明見介面下方:

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
		    u32 portid, int nonblock)
ssk:    sock結構體指標,netlink_kernel_create返回的套接字描述符
skb:    skb存放訊息,它的data欄位指向要傳送的 netlink訊息結構,而skb的控制塊儲存了訊息的地址資訊,前面的巨集NETLINK_CB(skb)就用於方便設定該控制塊
portid: 埠id,與應用層對應,一般是應用層對應的程序的程序pid
nonblock:表示該函式是否為非阻塞,如果為1,該函式將在沒有接收快取可利用時立即返回,而如果為0,該函式在沒有接收快取可利 用時睡眠
返回: 傳送資料的長度

1.3、netlink_broadcast介面
        netlink_broadcast介面如下,幾個引數說明見介面下方:

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
		      u32 group, gfp_t allocation)
ssk:    sock結構體指標,netlink_kernel_create返回的套接字描述符
skb:    skb存放訊息,它的data欄位指向要傳送的 netlink訊息結構,而skb的控制塊儲存了訊息的地址資訊,前面的巨集NETLINK_CB(skb)就用於方便設定該控制塊
portid: 埠id,一般是應用層對應的程序的程序pid
group:  netlink多播組
allocation: 核心記憶體分配型別,一般地為GFP_ATOMIC或 GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文
返回: 傳送資料的長度

2、netlink程式設計實現
        2.1、應用程式設計

#include <stdio.h> 
#include <string.h> 
#include <malloc.h> 
#include <sys/socket.h> 
#include <sys/ioctl.h>
#include <ctype.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <stdlib.h>
#include <linux/wireless.h>

#define MAX_PAYLOAD 2048  /* maximum payload size*/
#ifndef NETLINK_CTCWIFI_DIAG_2G
#define NETLINK_CTCWIFI_DIAG_2G 22
#endif

int main(int argc, char *argv[])
{
    int state;
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    struct iovec iov;
    struct msghdr msg;
    int sock_fd,retval,protocol;
    int state_smg = 0;
    T_CTCWIFI_DIAG_EVENT event;  //T_CTCWIFI_DIAG_EVENT 為自己定義的資料結構體型別,這裡不方便給出。

    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_CTCWIFI_DIAG_2G );
    if(sock_fd == -1)
    {
        printf("socket fail,Mesg is %s\n",strerror(errno));
        exit(errno);
    }

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid();  /* self pid */
    src_addr.nl_groups = 0;   /*單播設定成0*/
    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
    if (retval < 0)
    {
        printf("bind error,Mesg is %s\n",strerror(errno));
        close(sock_fd);
        exit(errno);
    }

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if(nlh == NULL) 
    {
       close(sock_fd);
       return -1;
    }

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0; 
    dest_addr.nl_groups = 0;

    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh),"Hello Kernel!");

    memset(&iov,0,sizeof(iov));
    iov.iov_base = (void *)nlh;
    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);

    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;

    state_smg = sendmsg(sock_fd,&msg,0);  //向核心傳送"Hello Kernel!"便於核心獲取應用層程序的pid號。
    if(state_smg == -1)
    {
        printf("sendmsg error,Mesg is %s\n",strerror(errno));
    }

    while(1)
    {
        /* Read message from kernel */
        memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
        state = recvmsg(sock_fd, &msg, 0);
        if(state < 0)
        {
            printf("recvmsg error,Mesg is %s\n",strerror(errno));
        }

        memcpy(&event, NLMSG_DATA(nlh), sizeof(event));
       /*handle kernel msg*/
       /*handle kernel msg*/
       /*handle kernel msg*/
    }
    
    free(nlh);
    close(sock_fd);
    return 0;
}

        2.2、核心程式設計

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <linux/netlink.h>

#define MAX_PAYLOAD 2048
static struct sock *ctcwifi_netlink_fd = NULL;
#ifndef NETLINK_CTCWIFI_DIAG_2G
#define NETLINK_CTCWIFI_DIAG_2G 22
#endif
unsigned int pid;

MODULE_LICENSE("GPL");  
MODULE_AUTHOR("zouchun");  
MODULE_DESCRIPTION("netlink_ctcwifi_diag");

void ctcwifi_diag_netlink_send(T_CTCWIFI_DIAG_EVENT *event, char *event_data, unsigned int event_datalen)
{
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlh;
    char * msg_event_data;

    if ((event == NULL) || (ctcwifi_netlink_fd == NULL))
        return;

    skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);
    if (NULL == skb) 
        return;

    skb_put(skb, NLMSG_SPACE(MAX_PAYLOAD));
    nlh = (struct nlmsghdr *)skb->data;
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = 0;	/* from kernel */
    nlh->nlmsg_flags = 0;
    event->datalen = 0;
    if(event_data) 
    {
        msg_event_data = (char*)(((T_CTCWIFI_DIAG_EVENT *)NLMSG_DATA(nlh)) + 1);
        memcpy(msg_event_data, event_data, event_datalen);
        event->datalen = event_datalen;
    }
    event->datalen += sizeof(T_CTCWIFI_DIAG_EVENT);
    memcpy(NLMSG_DATA(nlh), event, sizeof(*event));
#if LINUX_VERSION_CODE >= KERNEL_VERSION (3,10,0)
    NETLINK_CB(skb).portid = 0;	/* from kernel */
#else
    NETLINK_CB(skb).pid = 0;        /* from kernel */
#endif
    NETLINK_CB(skb).dst_group = 0;

    /*unicast the message*/
    netlink_unicast(ctcwifi_netlink_fd, skb, pid, MSG_DONTWAIT);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)
static void ctcwifi_diag_nl_receive(struct sk_buff *__skb)
#else
static void ctcwifi_diag_nl_receive(struct sk_buff *__skb,int len)
#endif
{
    struct sk_buff *skb;
    struct nlmsghdr *nlh = NULL;
    unsigned char *data = NULL;
    unsigned int uid, seq;

#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)
    skb = skb_get(__skb);
#else
    skb = skb_dequeue(&sk->sk_receive_queue);
#endif
    if (skb->len >= NLMSG_SPACE(0))
    {
        nlh = nlmsg_hdr(skb);
        data = NLMSG_DATA(nlh);
        pid = NETLINK_CREDS(skb)->pid;
        uid = NETLINK_CREDS(skb)->uid.val;
        seq = nlh->nlmsg_seq;
        
        printk("recv skb from user space uid:%d pid:%d seq:%d,\n",uid,pid,seq);
        printk("data is :%s\n",(char *)data);
    }
    kfree_skb(skb);
}

static int __init wlan_ctcwifi_init(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION (3,10,0)
    struct netlink_kernel_cfg cfg;
    memset(&cfg, 0, sizeof(cfg));
    cfg.input = &ctcwifi_diag_nl_receive;
    ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(&init_net, 
                                                              NETLINK_CTCWIFI_DIAG_2G, 
                                                              &cfg);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,24)
    ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(&init_net, 
                                                              NETLINK_CTCWIFI_DIAG_2G,
                                                              1,
                                                              ctcwifi_diag_nl_receive,
                                                              NULL,
                                                              THIS_MODULE);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,22)
    ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(NETLINK_CTCWIFI_DIAG_2G,
                                                              1,
                                                              ctcwifi_diag_nl_receive,
                                                              NULL,
                                                              THIS_MODULE);
#else
ctcwifi_netlink_fd = (struct sock *)netlink_kernel_create(NETLINK_CTCWIFI_DIAG_2G,
                                                          1,
                                                          ctcwifi_diag_nl_receive,
                                                          THIS_MODULE);
#endif
    if(!ctcwifi_netlink_fd)
    {
        printk(KERN_ERR "can not create a netlink socket\n");
        return -1;
    }
    return 0;
}

static void __exit wlan_ctcwifi_exit(void)
{
    if (ctcwifi_netlink_fd) 
    {
        sock_release(ctcwifi_netlink_fd->sk_socket);
        printk(KERN_DEBUG "test_netlink_exit!!\n");
    }
}

module_init(wlan_ctcwifi_init);
module_exit(wlan_ctcwifi_exit);

        注:由於協議號需要呼叫netlink_kernel_create後方可註冊到核心,因此使用時應提供機制保證驅動在應用層之前先啟動,否則會出現報錯:當前協議不支援。