天天看點

使用者空間與核心的互動---NETLINK

   關于netlink,相信玩過上層開發的人并不陌生,尤其是經常穿梭于使用者空間和核心之間的開發,前面我們說過在使用者空間與核心空間的互動有ioctl 、proc 等,而netlink又有它獨特的地位和作用.

  我開始接觸netlink是開始于去實作一個恢複出廠值的按鍵.當然先要實作一個中斷,然後再去通知上層做相應的恢複配置檔案的操作,當時是參考了前輩的代碼,稍作修改,才搞定,對于當時的netlink的廣播消息還是相當的費解^^. 在後來就是看了《深入了解linux網絡技術内幕》中有提到,但是篇幅很少,當時從網上來看,從實際的應用來看netlink不容易忽視. 

   Netlink套接字是用以實作使用者程序與核心程序通信的一種特殊的程序間通信(IPC) ,也是網絡應用程式與核心通信的最常用的接口。  

   Netlink套接字可以使用标準的套接字APIs來建立。socket(), bind(), sendmsg(), recvmsg() 和 close()很容易地應用到 netlink socket。

   netlink包含于頭檔案linux/netlink.h中.

Netlink 相對于系統調用,ioctl 以及 /proc 檔案系統而言具有以下優點:

1,為了使用 netlink,使用者僅需要在 include/linux/netlink.h 中增加一個新類型的 netlink 協定定義即可, 如 #define NETLINK_MYTEST 17 然後,核心和使用者态應用就可以立即通過 socket API 使用該 netlink 協定類型進行資料交換。但系統調用需要增加新的系統調用,ioctl 則需要增加裝置或檔案, 那需要不少代碼,proc 檔案系統則需要在 /proc 下添加新的檔案或目錄,那将使本來就混亂的 /proc 更加混亂。

2. netlink是一種異步通信機制,在核心與使用者态應用之間傳遞的消息儲存在socket緩存隊列中,發送消息隻是把消息儲存在接收者的socket的接 收隊列,而不需要等待接收者收到消息,但系統調用與 ioctl 則是同步通信機制,如果傳遞的資料太長,将影響排程粒度。

3.使用 netlink 的核心部分可以采用子產品的方式實作,使用 netlink 的應用部分和核心部分沒有編譯時依賴,但系統調用就有依賴,而且新的系統調用的實作必須靜态地連接配接到核心中,它無法在子產品中實作,使用新系統調用的應用在編譯時需要依賴核心。

4.netlink 支援多點傳播,核心子產品或應用可以把消息多點傳播給一個netlink組,屬于該neilink 組的任何核心子產品或應用都能接收到該消息,核心事件向使用者态的通知機制就使用了這一特性,任何對核心事件感興趣的應用都能收到該子系統發送的核心事件,在 後面的文章中将介紹這一機制的使用。

5.核心可以使用 netlink 首先發起會話,但系統調用和 ioctl 隻能由使用者應用發起調用。

6.netlink 使用标準的 socket API,是以很容易使用,但系統調用和 ioctl則需要專門的教育訓練才能使用

 當然netlink有單點傳播和廣播之分,下面我們就先用實際例子為引導來分析:

userspace :

一個簡單的應用程式:

點選(此處)折疊或打開

// userspace :

#include sys/stat.h>

#include unistd.h>

#include stdio.h>

#include stdlib.h>

#include sys/socket.h>

#include sys/types.h>

#include string.h>

#include asm/types.h>

#include linux/netlink.h>

#include linux/socket.h>

struct u_packet_info

{

    struct nlmsghdr hdr;

    char buf[10];

};

#if 1

int

main (int argc, char **argv)

    int fd,len1,len2;

    struct sockaddr_nl src_addr,dst_addr;

    struct nlmsghdr *mymsg, mymsg2 ;

    struct msghdr msg;

      struct u_packet_info *info;

     char *buf;

    /*test my netlink :socket */

    fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYCTRL);

    if(fd 0)

    {

        printf("create netlink_myctrl socket fail !n");

        return -1;

    }

        memset(&src_addr,0,sizeof(src_addr));

        src_addr.nl_family = AF_NETLINK;

        src_addr.nl_pid = getpid();

        src_addr.nl_groups = 0;

    if(bind(fd,(struct sockaddr *)&src_addr,sizeof(src_addr)) != 0)

        perror("bind errorn");

         memset(&dst_addr,0,sizeof(dst_addr));

          dst_addr.nl_family = AF_NETLINK;

        dst_addr.nl_pid = 0;

        dst_addr.nl_groups = 0; //not in multicast

    mymsg =(struct nlmsghdr *)malloc(NLMSG_SPACE(20));

    info =(struct u_packet_info *)malloc(sizeof(struct u_packet_info ));

    mymsg->nlmsg_len = NLMSG_LENGTH(20);

    mymsg->nlmsg_flags = 0;

    mymsg->nlmsg_type = NETLINK_MYCTRL;

    mymsg->nlmsg_pid = src_addr.nl_pid;

    strcpy(NLMSG_DATA(mymsg), "Hello you!");

     memset(&msg,0,sizeof(msg));

    //send to kernel

    sendto(fd,mymsg,mymsg->nlmsg_len,0,(struct sockaddr *) &dst_addr,sizeof(dst_addr));

    free(mymsg);

   while(1)

      len2 = sizeof(struct sockaddr_nl);

      len1 = recvfrom(fd, info ,sizeof(struct u_packet_info),0,(struct sockaddr *)&dst_addr,(socklen_t *)&len2);

     if(len1 > 0)

     {

         printf("------------------%d-----recv ok !!!n",info->hdr.nlmsg_pid);

        printf("------------------%s-----recv ok !!!n",info->buf);

        free(info);

        return 0;

     }

}

#endif

這裡我們關于頭檔案include/linux/netlink.h  ,可以自定義我們netlink協定.

#define   NETLINK_MYCTRL  23

#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    /* Firewalling hook                */

#define NETLINK_INET_DIAG    4    /* INET 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 MAX_LINKS 32        

struct net;

struct sockaddr_nl

    sa_family_t    nl_family;    /* AF_NETLINK    */

    unsigned short    nl_pad;        /* zero        */

    __u32        nl_pid;        /* port ID    */

           __u32        nl_groups;    /* multicast groups mask */

struct nlmsghdr

    __u32        nlmsg_len;    /* Length of message including header */

    __u16        nlmsg_type;    /* Message content */

    __u16        nlmsg_flags;    /* Additional flags */

    __u32        nlmsg_seq;    /* Sequence number */

    __u32        nlmsg_pid;    /* Sending process port ID */

從頭檔案裡我們知道其實核心已經支援了一些常用的協定.

對于使用者空間使用netlink我們直接調用socket API即可.  

 fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYCTRL);  

關于socket的幾個參數這裡不過多解釋. 上面例子我們是為了向核心發送一個消息,核心處理後,發送回來,我們來接收. 下面就看看核心代碼: 

#include linux/kernel.h>

#include linux/module.h>

#include linux/types.h>

#include linux/sched.h>

#include net/sock.h>

int my_pid;

struct sock * myctrlnl;

static void netlink_kernel_rcv(struct sk_buff *skb)

    struct nlmsghdr *nlh = NULL;

    nlh = nlmsg_hdr(skb);

            if(nlh->nlmsg_type == NETLINK_MYCTRL){

                my_pid = nlh->nlmsg_pid; //record pid

                printk("NETLINK_TEST_U_PID: user_proc.pid = %dn",my_pid);

                printk("%s: received netlink message payload:%sn", __FUNCTION__, (char*)NLMSG_DATA(nlh));

        skb = skb_clone(skb, GFP_KERNEL);

        if (skb == NULL)

                return;

             nlh = nlmsg_hdr(skb);

            nlh->nlmsg_pid =5;

            memcpy((char *)NLMSG_DATA(nlh),"yes,we do!",20);

            netlink_unicast(myctrlnl, skb, my_pid, MSG_DONTWAIT);

 }

void __init mynetlink_init(void)

    /*test my netlink */

    myctrlnl = netlink_kernel_create(&init_net, NETLINK_MYCTRL, MYCTRLNLGRP_MAX, netlink_kernel_rcv,

     NULL, THIS_MODULE);

    if (myctrlnl == NULL)

        panic("ctrlnetlink_init: cannot initialize myctrlnetlinkn");

這裡是接收到使用者空間消息後,列印pid ,和傳遞的資料負載.  然後修改後,在傳遞回使用者空間. 這裡有個小插曲就是關于要用skb_clone.

這裡參考的是核心代碼: 

static void nl_fib_input(struct sk_buff *skb)

    struct net *net;

    struct fib_result_nl *frn;

    struct nlmsghdr *nlh;

    struct fib_table *tb;

    u32 pid;

    net = sock_net(skb->sk);

    if (skb->len NLMSG_SPACE(0) || skb->len nlh->nlmsg_len ||

     nlh->nlmsg_len NLMSG_LENGTH(sizeof(*frn)))

        return;

    skb = skb_clone(skb, GFP_KERNEL);

    if (skb == NULL)

    frn = (struct fib_result_nl *) NLMSG_DATA(nlh);

    tb = fib_get_table(net, frn->tb_id_in);

    nl_fib_lookup(frn, tb);

    pid = NETLINK_CB(skb).pid; /* pid of sending process */

    NETLINK_CB(skb).pid = 0; /* from kernel */

    NETLINK_CB(skb).dst_group = 0; /* unicast */

    netlink_unicast(net->ipv4.fibnl, skb, pid, MSG_DONTWAIT);

有時候我們不僅僅要自己修改傳遞過來的skb,或許我們想自己新建立一個:

static int send_to_kernel(void)

    struct sk_buff *skb;

    struct nlmsghdr* nlh;    

    char msg[6] ="hello";

    char *msg1;

    int size,pid;

    unsigned char *b;

       printk("----here send from kernel :%dn",my_pid);

       size_t payload = sizeof(*msg);

    //計算skb配置設定長度

        size = NLMSG_SPACE(20);

    //配置設定skb

    skb = alloc_skb(size, GFP_ATOMIC);

    if (NULL == skb )

    {     

        printk(" skb malloc error.\n");

    nlh = NLMSG_PUT (skb, 0, 0, 0, 20);

    memcpy((char *)NLMSG_DATA(nlh),"nihao",20);

    nlh->nlmsg_len = NLMSG_LENGTH(20);

    NETLINK_CB(skb).dst_group = 0; /* unicast */    

    netlink_unicast(myctrlnl, skb, my_pid, MSG_DONTWAIT);

    return 0;    

nlmsg_failure:

    kfree_skb (skb);

    return -1;

核心裡使用netlink需要調用 netlink_kernel_create函數,不同核心版本參數有變動,這裡是用的2.6.32.60 

很多時候我們不想這麼麻煩的傳遞一個東西,先是從使用者空間傳遞一個消息(主要是為了pid ,相對于單點傳播) ,然後核心才能傳遞給使用者空間特定的程序. 我們有時候會需要不理會使用者空間,直接從核心傳遞到使用者空間,然後在做處理,那麼這裡就需要到netlink的廣播功能.  

那麼相對單點傳播廣播其實需要改動的地方也不大 :

對于核心部分我們隻需要修改:

        NETLINK_CB(skb).pid =0;

        NETLINK_CB(skb).dst_group = MYCTRLNLGRP_TEST;

        netlink_broadcast(myctrlnl,skb,0,MYCTRLNLGRP_TEST,GFP_ATOMIC);

我們同樣需要在netlink頭檔案裡定義我們的廣播協定:

enum myrlnetlink_groups {

    MYCTRLNLGRP_NONE,

#define MYCTRLNLGRP_NONE        MYCTRLNLGRP_NONE

        MYCTRLNLGRP_TEST,

#define MYCTRLNLGRP_TEST MYCTRLNLGRP_TEST

        __MYRLNLGRP_MAX

#define MYRLNLGRP_MAX    (__MYRLNLGRP_MAX - 1)

那麼我們使用者空間的程式需要修改些什麼呢?  

src_addr.nl_groups = MYCTRLNLGRP_TEST; 

這裡我們并沒有關注太多的細節,而是直接拿來個例子.當然更關注的是如何應用,至于其他的深入分析待以後更加了解後在說吧.隻是為了學習netlink一個小小的開始.

繼續閱讀