Linux內核和用戶空間通訊之netlink

 

1. netlink

Netlink套接字是用以實現用戶進程與內核進程通訊的一種特殊的進程間通訊(IPC) ,也是網絡應用程序與內核通訊的最經常使用的接口。php

Netlink 是一種特殊的 socket,它是 Linux 所特有的,相似於 BSD 中的AF_ROUTE 但又遠比它的功能強大,目前在Linux 內核中使用netlink 進行應用與內核通訊的應用不少; 包括:路由 daemon(NETLINK_ROUTE),用戶態 socket 協議(NETLINK_USERSOCK),html

防火牆(NETLINK_FIREWALL),netfilter 子系統(NETLINK_NETFILTER),內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)等。linux

Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的很是好的方式,用戶態應用使用標準的 socket API 就可使用 netlink 提供的強大功能,內核態須要使用專門的內核 API 來使用 netlink。shell

 

Netlink 相對於系統調用ioctl 以及 /proc文件系統而言具備如下優勢:api

1. netlink使用簡單,只須要在include/linux/netlink.h中增長一個新類型的 netlink 協議定義便可,(如 #define NETLINK_TEST 20 而後,內核和用戶態應用就能夠當即經過 socket API 使用該 netlink 協議類型進行數據交換);緩存

2. netlink是一種異步通訊機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不須要等待接收者收到消息;cookie

3. 使用 netlink 的內核部分能夠採用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴;網絡

4. netlink 支持多播,內核模塊或應用能夠把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性;數據結構

5. 內核可使用 netlink 首先發起會話;dom

 

2.1 netlink相關數據結構

struct net網絡設備命名空間指針。

struct sock是套接口在網絡層的表示。

struct sk_buff結構是Linux網絡代碼中重要的數據結構,它管理和控制接收或發送數據包的信息。

struct sockaddr_nl是netlink通訊地址。

struct netlink_kernel_cfg netlink的配置結構體。

struct nlmsghdr是netlink提供的協議頭,netlink協議是面向消息的,須要定義本身的協議。自定義協議按照協議頭格式填充協議頭內容,並定義本身的payload,一般自定義的協議體包含自定義協議頭與額外的屬性。

struct net {
    refcount_t        passive;    /* To decided when the network
                         * namespace should be freed.
                         */
    atomic_t        count;        /* To decided when the network
                         *  namespace should be shut down.
                         */
    spinlock_t        rules_mod_lock;

    atomic64_t        cookie_gen;
...
} __randomize_layout;

struct sock {
    /*
     * Now struct inet_timewait_sock also uses sock_common, so please just
     * don't add nothing before this first member (__sk_common) --acme
     */
    struct sock_common    __sk_common;
    socket_lock_t        sk_lock;
    atomic_t        sk_drops;
    int            sk_rcvlowat;
    struct sk_buff_head    sk_error_queue;
    struct sk_buff_head    sk_receive_queue;
...
};

struct sk_buff {
...
    struct sock        *sk;
...
    /* private: */
    __u32            headers_end[0];
    /* public: */

    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t        tail;
    sk_buff_data_t        end;
    unsigned char        *head,
                *data;
    unsigned int        truesize;
    refcount_t        users;
};

struct nlmsghdr {
    __u32        nlmsg_len;    /* Length of message including header */----------整個netlink消息的長度,包含消息頭。
    __u16        nlmsg_type;    /* Message content */----------------------------消息狀態,內核在include/uapi/linux/netlink.h中定義如下4種通用的消息類型。
    __u16        nlmsg_flags;    /* Additional flags */--------------------------消息標記,它們泳衣表示消息的類型。
    __u32        nlmsg_seq;    /* Sequence number */-----------------------------消息序列號,用以將消息排隊有些相似TCP協議中的序號(不徹底同樣),可是netlink的這個字段是可選的,不強制使用。
    __u32        nlmsg_pid;    /* Sending process port ID */---------------------發送端口的ID號,對於內核來講該值就是0,對於用戶進程來講就是其socket所綁定的ID號。
};

struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb);-----------------------------------input回調函數 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);
};

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 */
};

下面是nlmsg_type和nlmsg_flags宏對應解釋:

nlmsg_type:

#define
NLMSG_NOOP 0x1 /* Nothing. */----------------不執行任何動做,必須將該消息丟棄。 #define NLMSG_ERROR 0x2 /* Error */------------------消息發生錯誤。 #define NLMSG_DONE 0x3 /* End of a dump */---------------標識分組消息的末尾。 #define NLMSG_OVERRUN 0x4 /* Data lost */------------緩衝區溢出,表示某些消息已經丟失。 #define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */
nlmsg_flags:
/* Flags values */ #define NLM_F_REQUEST 0x01 /* It is request message. */ #define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 0x08 /* Echo this request */ #define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */ #define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */

系統預約義了一系列netlink類型:

#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_SMC        22    /* SMC monitoring */

#define NETLINK_INET_DIAG    NETLINK_SOCK_DIAG

#define MAX_LINKS 32        

netlink以下些經常使用宏:

#define NLMSG_ALIGNTO    4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )------------------用於獲得不小於len且字節對齊的最小數值
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))----------------------netlink頭部長度
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)-------------------------------------------計算消息數據len的真實消息長度,消息體+消息頭
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))------------------------------------返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))-------------------------用於取得消息的數據部分的首地址,設置和讀取消息數據部分時須要使用該宏
#define NLMSG_NEXT(nlh,len)     ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))------用於獲得下一個消息的首地址,同時len變爲剩餘消息的長度 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len <= (len))--------------------------------------------------判斷消息是否>len #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))---------------------用於返回payload的長度

 

2.2 netlink相關API

內核經過netlink_kernel_create()/netlink_kernel_release() 建立/銷燬struct sock:

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

net:net指向所在的網絡命名空間,通常默認傳入的是&init_net,不須要定義;定義在net_namespace.c中。
unit:netlink協議類型。
cfg:cfg存放的是netlink內核配置參數struct netlink_kernel_cfg數據結構。
void netlink_kernel_release(struct sock *sk)
sk:釋放netlink_kernel_create()建立的sock。

下面是netlink消息經常使用API: 

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
            u32 portid, int nonblock)-----------------------------------------------用來發送單播信息。
ssk:netlink socket
skb:skb buff指針
portid:通訊端口號
nonblock:表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回;若是爲0,該函數在沒有接收緩存可利用時定是睡眠。
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid, u32 group, gfp_t allocation)------------------------------------------用來發送多播信息。
ssk:netlink socket,netlink_kernel_create()返回值
skb:內核skb buff
portid:通訊端口號
group:是全部目標多播組對應掩碼的OR操做的合值
allocation:指定內核內存分配方式,一般GFP_ATOMIC用於中斷上下文,而GFP_KERNEL用於其餘場合。這個參數的存在是由於該API可能須要分配一個或多個緩衝區來對多播消息進行clone。

  static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)----------------從sk_buff->data獲取struct nlmsghdr數據結構。
  {
      return (struct nlmsghdr *)skb->data;
  }

static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)---------------建立len大小的struct sk_buff。
{
    return alloc_skb(nlmsg_total_size(payload), flags);
}

static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
                     int type, int payload, int flags)----------------------------將一個新的netlink消息加入到skb中。若是skb沒法存放消息則返回NULL。
static inline void nlmsg_free(struct sk_buff *skb)--------------------------------釋放nlmsg_new()建立的skb。 { kfree_skb(skb); } static inline void *nlmsg_data(const struct nlmsghdr *nlh)------------------------根據nlmsghdr指針獲取對應的payload。 { return (unsigned char *) nlh + NLMSG_HDRLEN; } static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh, int *remaining)---獲取消息流中下一個netlink消息。 static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh)

用戶空間相關socket操做API有: 

int socket(int domain, int type, int protocol);------------------該函數用來建立一個套接字,並返回一個描述符,該描述符能夠用來訪問該套接字。protocol參數設置爲0表示使用默認協議。 int bind( int socket, const struct sockaddr *address, size_t address_len);--把經過socket()建立的套接字命名,從而讓它能夠被其餘進程使用。 int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);----把緩衝區buffer中的信息送給制定的IP端口程序,buffer存放將要發送的數據,len是buffer長度,to是要發送數據到的程序IP端口,tolen是to參數長度。 int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len); --把發送給程序的信息存儲在緩衝區buffer中,並記錄數據來源的程序IP端口。buffer存放接收的數據,len是buffer長度,src_from是數據來源程序IP端口,src_len是src_from長度。

 

2.3 netlink實例 

對netlink的測試分爲兩部分,一部分是內核提供接收用戶空間消息,並響應發送功能;另外一部分是用戶空間發送netlink到內核,並等待回覆。

2.3.1 內核netlink測試代碼

新建netlink_kernel.c文件:

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

#define NETLINK_TEST     30
#define USER_PORT        100
int netlink_count = 0;
char netlink_kmsg[30];

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    //Create sk_buff using nlmsg_new().
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("netlink alloc failure\n");
        return -1;
    }

    //Set up nlmsghdr.
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if(nlh == NULL)
    {
        printk("nlmsg_put failaure \n");
        nlmsg_free(nl_skb);  //If nlmsg_put() failed, nlmsg_free() will free sk_buff.
        return -1;
    }

    //Copy pbuf to nlmsghdr payload.
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    char *umsg = NULL;
    //char *kmsg = "hello users!!!";
    char *kmsg;

    if(skb->len >= nlmsg_total_size(0))
    {
        netlink_count++;
    snprintf(netlink_kmsg, sizeof(netlink_kmsg), "hello users count=%d", netlink_count);
    kmsg = netlink_kmsg;
        nlh = nlmsg_hdr(skb);  //Get nlmsghdr from sk_buff.
        umsg = NLMSG_DATA(nlh); //Get payload from nlmsghdr.
        if(umsg)
        {
            printk("kernel recv from user: %s\n", umsg);
            send_usrmsg(kmsg, strlen(kmsg));
        }
    }
}

struct netlink_kernel_cfg cfg = { 
        .input  = netlink_rcv_msg, /* set recv callback */
};  

__init int netlink_test_init(void)
{
    /* Create netlink socket */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if(nlsk == NULL)
    {   
        printk("netlink_kernel_create error !\n");
        return -1; 
    }   
    printk("netlink_test_init\n");
    
    return 0;
}

__exit void netlink_test_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }   
    printk("netlink_test_exit!\n");
}

module_init(netlink_test_init);
module_exit(netlink_test_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("netlink test");

 

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

#define NETLINK_TEST     30
#define USER_PORT        100
int netlink_count = 0;
char netlink_kmsg[30];

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    //Create sk_buff using nlmsg_new().
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("netlink alloc failure\n");
        return -1;
    }

    //Set up nlmsghdr.
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if(nlh == NULL)
    {
        printk("nlmsg_put failaure \n");
        nlmsg_free(nl_skb);  //If nlmsg_put() failed, nlmsg_free() will free sk_buff.
        return -1;
    }

    //Copy pbuf to nlmsghdr payload.
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    char *umsg = NULL;
    //char *kmsg = "hello users!!!";
    char *kmsg;

    if(skb->len >= nlmsg_total_size(0))
    {
        netlink_count++;
    snprintf(netlink_kmsg, sizeof(netlink_kmsg), "hello users count=%d", netlink_count);
    kmsg = netlink_kmsg;
        nlh = nlmsg_hdr(skb);  //Get nlmsghdr from sk_buff.
        umsg = NLMSG_DATA(nlh); //Get payload from nlmsghdr.
        if(umsg)
        {
            //printk("kernel recv from user: %s\n", umsg);
            send_usrmsg(kmsg, strlen(kmsg));
        }
    }
}

struct netlink_kernel_cfg cfg = { 
        .input  = netlink_rcv_msg, /* set recv callback */
};  

__init int netlink_test_init(void)
{
    /* Create netlink socket */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if(nlsk == NULL)
    {   
        printk("netlink_kernel_create error !\n");
        return -1; 
    }   
    printk("netlink_test_init\n");
    
    return 0;
}

__exit void netlink_test_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }   
    printk("netlink_test_exit!\n");
}

module_init(netlink_test_init);
module_exit(netlink_test_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("netlink test");
View Code

Makefile以下:

MODULE_NAME :=netlink_kernel
obj-m :=$(MODULE_NAME).o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

 

2.3.2 用戶空間netlink測試代碼

新建netlink_user.c文件:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#define NETLINK_TEST    30
#define USER_PORT    100
#define MAX_PLOAD    125
#define MSG_LEN        125

typedef struct _user_msg_info
{
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
    int skfd;
    int ret;
    user_msg_info u_info;
    socklen_t len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr;
    char *umsg = "hello netlink!!";
    int loop_count = 0;

    /*Create netlink socket*/
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
    if(skfd == -1)
    {
        perror("create socket error\n");
        return -1;
    }

    //Source address.
    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = USER_PORT;  //netlink portid, same as kernel.
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) //bind to skfd with saddr.
    {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    //Destination address.
    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0;    // to kernel 
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
    while(loop_count < 11) {
        printf("sendto kernel:%s\n", umsg);
        ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
        if(!ret)
        {
            perror("sendto error\n");
            close(skfd);
            exit(-1);
        }

        //Receive netlink message from kernel.
        memset(&u_info, 0, sizeof(u_info));
        len = sizeof(struct sockaddr_nl);
        ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
        if(!ret)
        {
            perror("recv form kernel error\n");
            close(skfd);
            exit(-1);
        }

        printf("from kernel:%s\n", u_info.msg);
    //usleep(1000);
    loop_count++;
    }
    close(skfd);

    free((void *)nlh);
    return 0;
}

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

#define NETLINK_TEST    30
#define USER_PORT    100
#define MAX_PLOAD    125
#define MSG_LEN        125

typedef struct _user_msg_info
{
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
    int skfd;
    int ret;
    user_msg_info u_info;
    socklen_t len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr;
    char *umsg = "hello netlink!!";
    int loop_count = 0;
    struct timespec time1, time2;
    unsigned long int duration;

    /*Create netlink socket*/
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
    if(skfd == -1)
    {
        perror("create socket error\n");
        return -1;
    }

    //Source address.
    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = USER_PORT;  //netlink portid, same as kernel.
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) //bind to skfd with saddr.
    {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    //Destination address.
    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0;    // to kernel 
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    clock_gettime(CLOCK_REALTIME, &time1);
    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
    while(loop_count < 10000) {
        //printf("sendto kernel:%s\n", umsg);
        ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
        if(!ret)
        {
            perror("sendto error\n");
            close(skfd);
            exit(-1);
        }

        //Receive netlink message from kernel.
        memset(&u_info, 0, sizeof(u_info));
        len = sizeof(struct sockaddr_nl);
        ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
        if(!ret)
        {
            perror("recv form kernel error\n");
            close(skfd);
            exit(-1);
        }

        //printf("from kernel:%s\n", u_info.msg);
    //usleep(1000);
    loop_count++;
    }
    close(skfd);
    clock_gettime(CLOCK_REALTIME, &time2);
    duration =(time2.tv_sec-time1.tv_sec)*1000000000 + (time2.tv_nsec-time1.tv_nsec);
    printf("End time %ld.%ld\n", duration/1000000000, duration%1000000000);
    free((void *)nlh);

    return 0;
}
View Code

而後經過gcc netlink_user.c -o netlink_user編譯。

2.3.3 測試結果

將編譯的內核module經過sudo insmod netlink_kernel.kl加載到內核。

而後執行./netlink_user,獲得以下結果。

sendto kernel:hello netlink!!
from kernel:hello users count=1
sendto kernel:hello netlink!!
from kernel:hello users count=2
sendto kernel:hello netlink!!
from kernel:hello users count=3
sendto kernel:hello netlink!!
from kernel:hello users count=4
sendto kernel:hello netlink!!
from kernel:hello users count=5
sendto kernel:hello netlink!!
from kernel:hello users count=6
sendto kernel:hello netlink!!
from kernel:hello users count=7
sendto kernel:hello netlink!!
from kernel:hello users count=8
sendto kernel:hello netlink!!
from kernel:hello users count=9
sendto kernel:hello netlink!!
from kernel:hello users count=10
sendto kernel:hello netlink!!
from kernel:hello users count=11

經過dmesg獲取內核log以下:

[16924.072556] netlink_test_init
[16926.909889] kernel recv from user: hello netlink!!
[16926.909919] kernel recv from user: hello netlink!!
[16926.909940] kernel recv from user: hello netlink!!
[16926.909960] kernel recv from user: hello netlink!!
[16926.909980] kernel recv from user: hello netlink!!
[16926.910002] kernel recv from user: hello netlink!!
[16926.910021] kernel recv from user: hello netlink!!
[16926.910048] kernel recv from user: hello netlink!!
[16926.910072] kernel recv from user: hello netlink!!
[16926.910089] kernel recv from user: hello netlink!!
[16926.910114] kernel recv from user: hello netlink!!

能夠看出從用戶發送->內核接收->內核響應->用戶接收,整個流程耗時均值在(16926.910114 - 16926.909889) / 10 = 22.5us

 參考文檔:《linux netlink通訊機制》、《Linux進程間通訊(九):數據報套接字 socket()、bind()、sendto()、recvfrom()、close()

相關文章
相關標籤/搜索