linux用戶空間與內核空間通信——Netlink通信機制【轉】


轉自:https://blog.csdn.net/zhao_h/article/details/80943226

一:什么是Netlink通信機制

Netlink是linux提供的用於內核和用戶態進程之間的通信方式。

但是注意雖然Netlink主要用於用戶空間和內核空間的通信,但是也能用於用戶空間的兩個進程通信。只是進程間通信有其他很多

方式,一般不用Netlink。除非需要用到Netlink的廣播特性時。

那么Netlink有什么優勢呢?

一般來說用戶空間和內核空間的通信方式有三種:/proc、ioctl、Netlink。而前兩種都是單向的,但是Netlink可以實現雙工通信。

Netlink協議基於BSD socket和AF_NETLINK地址簇(address family),使用32位的端口號尋址(以前稱作PID),每個Netlink協議

(或稱作總線,man手冊中則稱之為netlink family),通常與一個或一組內核服務/組件相關聯,如NETLINK_ROUTE用於獲取和

設置路由與鏈路信息、NETLINK_KOBJECT_UEVENT用於內核向用戶空間的udev進程發送通知等。

netlink具有以下特點:

① 支持全雙工、異步通信(當然同步也支持)

② 用戶空間可使用標准的BSD socket接口(但netlink並沒有屏蔽掉協議包的構造與解析過程,推薦使用libnl等第三方庫)

③ 在內核空間使用專用的內核API接口

④ 支持多播(因此支持“總線”式通信,可實現消息訂閱)

⑤ 在內核端可用於進程上下文與中斷上下文

二:用戶態數據結構

首先看一下幾個重要的數據結構的關系:

1.struct msghdr

msghdr這個結構在socket變成中就會用到,並不算Netlink專有的,這里不在過多說明。只說明一下如何更好理解這個結構的功能。我們

知道socket消息的發送和接收函數一般有這幾對:recv/send、readv/writev、recvfrom/sendto。當然還有recvmsg/sendmsg,

前面三對函數各有各的特點功能,而recvmsg/sendmsg就是要囊括前面三對的所有功能,當然還有自己特殊的用途。msghdr的前兩個

成員就是為了滿足recvfrom/sendto的功能,中間兩個成員msg_iov和msg_iovlen則是為了滿足readv/writev的功能,而最后的

msg_flags則是為了滿足recv/send中flag的功能,剩下的msg_control和msg_controllen則是滿足recvmsg/sendmsg特有的功能。

2.struct sockaddr_ln

struct sockaddr_ln為Netlink的地址,和我們通常socket編程中的sockaddr_in作用一樣,他們的結構對比如下:

struct sockaddr_nl的詳細定義和描述如下:

  1.  
    struct sockaddr_nl
  2.  
    {
  3.  
    sa_family_t nl_family; /*該字段總是為AF_NETLINK */
  4.  
    unsigned short nl_pad; /* 目前未用到,填充為0*/
  5.  
    __u32 nl_pid; /* process pid */
  6.  
    __u32 nl_groups; /* multicast groups mask */
  7.  
    };

(1) nl_pid:在Netlink規范里,PID全稱是Port-ID(32bits),其主要作用是用於唯一的標識一個基於netlink的socket通道。通常

情況下nl_pid都設置為當前進程的進程號。前面我們也說過,Netlink不僅可以實現用戶-內核空間的通信還可使現實用戶空間兩

個進程之間,或內核空間兩個進程之間的通信。該屬性為0時一般指內核。

(2) nl_groups:如果用戶空間的進程希望加入某個多播組,則必須執行bind()系統調用。該字段指明了調用者希望加入的多播組

號的掩碼(注意不是組號,后面我們會詳細講解這個字段)。如果該字段為0則表示調用者不希望加入任何多播組。對於每個隸屬於

Netlink協議域的協議,最多可支持32個多播組(因為nl_groups的長度為32比特),每個多播組用一個比特來表示。

3.struct nlmsghdr

Netlink的報文由消息頭和消息體構成,struct nlmsghdr即為消息頭。消息頭定義在文件里,由結構體nlmsghdr表示:

  1.  
    struct nlmsghdr
  2.  
    {
  3.  
    __u32 nlmsg_len; /* Length of message including header */
  4.  
    __u16 nlmsg_type; /* Message content */
  5.  
    __u16 nlmsg_flags; /* Additional flags */
  6.  
    __u32 nlmsg_seq; /* Sequence number */
  7.  
    __u32 nlmsg_pid; /* Sending process PID */
  8.  
    };

 

消息頭中各成員屬性的解釋及說明:

(1) nlmsg_len:整個消息的長度,按字節計算。包括了Netlink消息頭本身。

(2) nlmsg_type:消息的類型,即是數據還是控制消息。目前(內核版本2.6.21)Netlink僅支持四種類型的控制消息,如下:

a) NLMSG_NOOP-空消息,什么也不做;

b) NLMSG_ERROR-指明該消息中包含一個錯誤;

c) NLMSG_DONE-如果內核通過Netlink隊列返回了多個消息,那么隊列的最后一條消息的類型為NLMSG_DONE,其余所有消息的nlmsg_flags屬性都被設置NLM_F_MULTI位有效。

d) NLMSG_OVERRUN-暫時沒用到。

(3) nlmsg_flags:附加在消息上的額外說明信息,如上面提到的NLM_F_MULTI。

三:用戶空間Netlink socket API

1.創建socket

int socket(int domain, int type, int protocol)

 

domain指代地址族,即AF_NETLINK;

套接字類型為SOCK_RAW或SOCK_DGRAM,因為netlink是一個面向數據報的服務;

protocol選擇該套接字使用哪種netlink特征。

以下是幾種預定義的協議類型:

NETLINK_ROUTE,

NETLINK_FIREWALL,

NETLINK_APRD,

NETLINK_ROUTE6_FW。

可以非常容易的添加自己的netlink協議。

為每一個協議類型最多可以定義32個多播組。

每一個多播組用一個bitmask來表示,1<<i(0<=i<= 31),這在一組進程和內核進程協同完成一項任務時非常有用。發送多播

netlink消息可以減少系統調用的數量,同時減少用來維護多播組成員信息的負擔。

2.地址綁定bind()

bind(fd, (struct sockaddr*)&, nladdr, sizeof(nladdr));

3.發送netlink消息

 

為了發送一條netlink消息到內核或者其他的用戶空間進程,另外一個struct sockaddr_nl nladdr需要作為目的地址,這和使用

sendmsg()發送一個UDP包是一樣的。

如果該消息是發送至內核的,那么nl_pid和nl_groups都置為0.

如果消息是發送給另一個進程的單播消息,nl_pid是另外一個進程的pid值而nl_groups為零。

如果消息是發送給一個或多個多播組的多播消息,所有的目的多播組必須bitmask必須or起來從而形成nl_groups域。

sendmsg(fd, &, msg, 0);

4.接收netlink消息

一個接收程序必須分配一個足夠大的內存用於保存netlink消息頭和消息負載。然后其填充struct msghdr msg,再使用標准的

recvmsg()函數來接收netlink消息。

當消息被正確的接收之后,nlh應該指向剛剛接收到的netlink消息的頭。nladdr應該包含接收消息的目的地址,其中包括了消息發送

者的pid和多播組。同時,宏NLMSG_DATA(nlh),定義在netlink.h中,返回一個指向netlink消息負載的指針。調用close(fd)關閉fd

描述符所標識的socket。

recvmsg(fd, &, msg, 0);

四:內核空間Netlink socket API

1.創建 netlink socket

  1.  
    struct sock *netlink_kernel_create(struct net *net,
  2.  
         int unit,unsigned int groups,
  3.  
         void (*input)(struct sk_buff *skb),
  4.  
        struct mutex *cb_mutex,struct module *module);

參數說明:

(1) net:是一個網絡名字空間namespace,在不同的名字空間里面可以有自己的轉發信息庫,有自己的一套net_device等等。

默認情況下都是使用 init_net這個全局變量。

(2) unit:表示netlink協議類型,如NETLINK_TEST、NETLINK_SELINUX。

(3) groups:多播地址。

(4) input:為內核模塊定義的netlink消息處理函數,當有消 息到達這個netlink socket時,該input函數指針就會被引用,且只

有此函數返回時,調用者的sendmsg才能返回。

(5)  cb_mutex:為訪問數據時的互斥信號量。

(6) module: 一般為THIS_MODULE。

2.發送單播消息 netlink_unicast

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)

參數說明:

(1) ssk:為函數 netlink_kernel_create()返回的socket。

(2) skb:存放消息,它的data字段指向要發送的netlink消息結構,而 skb的控制塊保存了消息的地址信息,宏

NETLINK_CB(skb)就用於方便設置該控制塊。

(3) pid:為接收此消息進程的pid,即目標地址,如果目標為組或內核,它設置為 0。

(4) nonblock:表示該函數是否為非阻塞,如果為1,該函數將在沒有接收緩存可利用時立即返回;而如果為0,該函數在沒有接

收緩存可利用定時睡眠。

3.發送廣播消息 netlink_broadcast

  1.  
    int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)
  2.  
     

前面的三個參數與 netlink_unicast相同,參數group為接收消息的多播組,該參數的每一個位代表一個多播組,因此如果發送給

多個多播組,就把該參數設置為多個多播組組ID的位或。參數allocation為內核內存分配類型,一般地為GFP_ATOMIC或

GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文。

4.釋放 netlink socket

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

五:用戶態范例一

  1.  
    #include <sys/stat.h>
  2.  
    #include <unistd.h>
  3.  
    #include <stdio.h>
  4.  
    #include <stdlib.h>
  5.  
    #include <sys/socket.h>
  6.  
    #include <sys/types.h>
  7.  
    #include <string.h>
  8.  
    #include <asm/types.h>
  9.  
    #include <linux/netlink.h>
  10.  
    #include <linux/socket.h>
  11.  
    #include <errno.h>
  12.  
    #define MAX_PAYLOAD 1024 // maximum payload size
  13.  
    #define NETLINK_TEST 25 //自定義的協議
  14.  
    int main(int argc, char* argv[])
  15.  
    {
  16.  
    int state;
  17.  
    struct sockaddr_nl src_addr, dest_addr;
  18.  
    struct nlmsghdr *nlh = NULL; //Netlink數據包頭
  19.  
    struct iovec iov;
  20.  
    struct msghdr msg;
  21.  
    int sock_fd, retval;
  22.  
    int state_smg = 0;
  23.  
    // Create a socket
  24.  
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
  25.  
    if(sock_fd == -1){
  26.  
    printf("error getting socket: %s", strerror(errno));
  27.  
    return -1;
  28.  
    }
  29.  
    // To prepare binding
  30.  
    memset(&src_addr, 0, sizeof(src_addr));
  31.  
    src_addr.nl_family = AF_NETLINK;
  32.  
    src_addr.nl_pid = 100; //A:設置源端端口號
  33.  
    src_addr.nl_groups = 0;
  34.  
    //Bind
  35.  
    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
  36.  
    if(retval < 0){
  37.  
    printf("bind failed: %s", strerror(errno));
  38.  
    close(sock_fd);
  39.  
    return -1;
  40.  
    }
  41.  
    // To orepare create mssage
  42.  
    nlh = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_PAYLOAD));
  43.  
    if(!nlh){
  44.  
    printf("malloc nlmsghdr error!\n");
  45.  
    close(sock_fd);
  46.  
    return -1;
  47.  
    }
  48.  
    memset(&dest_addr,0,sizeof(dest_addr));
  49.  
    dest_addr.nl_family = AF_NETLINK;
  50.  
    dest_addr.nl_pid = 0; //B:設置目的端口號
  51.  
    dest_addr.nl_groups = 0;
  52.  
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  53.  
    nlh->nlmsg_pid = 100; //C:設置源端口
  54.  
    nlh->nlmsg_flags = 0;
  55.  
    strcpy(NLMSG_DATA(nlh),"Hello you!"); //設置消息體
  56.  
    iov.iov_base = ( void *)nlh;
  57.  
    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
  58.  
    //Create mssage
  59.  
    memset(&msg, 0, sizeof(msg));
  60.  
    msg.msg_name = ( void *)&dest_addr;
  61.  
    msg.msg_namelen = sizeof(dest_addr);
  62.  
    msg.msg_iov = &iov;
  63.  
    msg.msg_iovlen = 1;
  64.  
    //send message
  65.  
    printf("state_smg\n");
  66.  
    state_smg = sendmsg(sock_fd,&msg, 0);
  67.  
    if(state_smg == -1)
  68.  
    {
  69.  
    printf("get error sendmsg = %s\n",strerror(errno));
  70.  
    }
  71.  
    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
  72.  
    //receive message
  73.  
    printf("waiting received!\n");
  74.  
    while(1){
  75.  
    printf("In while recvmsg\n");
  76.  
    state = recvmsg(sock_fd, &msg, 0);
  77.  
    if(state<0)
  78.  
    {
  79.  
    printf("state<1");
  80.  
    }
  81.  
    printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
  82.  
    }
  83.  
    close(sock_fd);
  84.  
    return 0;
  85.  
    }

上面程序首先向內核發送一條消息;“Hello you”,然后進入循環一直等待讀取內核的回復,並將收到的回復打印出來。如果

看上面程序感覺很吃力,那么應該首先復習一下UDP中使用sendmsg的用法,特別時struct msghdr的結構要清楚,這里再贅

述。下面主要分析與UDP發送數據包的不同點:

1. socket地址結構不同,UDP為sockaddr_in,Netlink為struct sockaddr_nl;

2. 與UDP發送數據相比,Netlink多了一個消息頭結構struct nlmsghdr需要我們構造。

注意代碼注釋中的A、B、C三處分別設置了pid。首先解釋一下什么是pid,網上很多文章把這個字段說成是進程的pid,其實這

完全是望文生義。這里的pid和進程pid沒有什么關系,僅僅相當於UDP的port。對於UDP來說port和ip標示一個地址,那對我們

的NETLINK_TEST協議(注意Netlink本身不是一個協議)來說,pid就唯一標示了一個地址。所以你如果用進程pid做為標示當然

也是可以的。當然同樣的pid對於NETLINK_TEST協議和內核定義的其他使用Netlink的協議是不沖突的(就像TCP的80端口和

UDP的80端口)。

下面分析這三處設置pid分別有什么作用,首先A和B位置的比較好理解,這是在地址(sockaddr_nl)上進行的設置,就是相當

於設置源地址和目的地址(其實是端口),只是注意B處設置pid為0,0就代表是內核,可以理解為內核專用的pid,那么用戶進

程就不能用0做為自己的pid嗎?這個只能說如果你非要用也是可以的,只是會產生一些問題,后面在分析。接下來看為什么C處

的消息頭仍然需要設置pid呢?這里首先要知道一個前提:內核不會像UDP一樣根據我們設置的原、目的地址為我們構造消息

頭,所以我們不在包頭寫入我們自己的地址(pid),那內核怎么知道是誰發來的報文呢?當然如果內核只是處理消息不需要回

復進程的話舍不設置這個消息頭pid都可以。

所以每個pid的設置功能不同:A處的設置是要設置發送者的源地址,有人會說既然源地址又不會自動填充到報文中,我們為什么

還要設置這個,因為你還可能要接收回復啊。就像寄信,你連“門牌號”都沒有,即使你在寫信時候寫上你的地址是100號,對

方回信目的地址也是100號,但是郵局發現根本沒有這個地址怎么可能把信送到你手里呢?所以A的主要作用是注冊源地址,保證

可以收到回復,如果不需要回復當然可以簡單將pid設置為0;B處自然就是收信人的地址,pid為0代表內核的地址,假如有一個

進程在101號上注冊了地址,並調用了recvmsg,如果你將B處的pid設置為101,那數據包就發給了另一個進程,這就實現了使

用Netlink進行進程間通信;C相當於你在信封上寫的源地址,通常情況下這個應該和你的真實地址(A)處注冊的源地址相同,

當然你要是不想收到回信,又想惡搞一下或者有特殊需求,你可以寫成其他進程注冊的pid(比如101)。這和我們現實中寄信是

一樣的,你給你朋友寫封情書,把寫信人寫成你的另一個好基友,然后后果你懂得……

好了,有了這個例子我們就大概知道用戶態怎么使用Netlink了。

六:內核態程序范例一

  1.  
    #include <linux/init.h>
  2.  
    #include <linux/module.h>
  3.  
    #include <linux/timer.h>
  4.  
    #include <linux/time.h>
  5.  
    #include <linux/types.h>
  6.  
    #include <net/sock.h>
  7.  
    #include <net/netlink.h>
  8.  
    #define NETLINK_TEST 25
  9.  
    #define MAX_MSGSIZE 1024
  10.  
    int stringlength(char *s);
  11.  
    int err;
  12.  
    struct sock *nl_sk = NULL;
  13.  
    int flag = 0;
  14.  
    //向用戶態進程回發消息
  15.  
    void sendnlmsg(char *message, int pid)
  16.  
    {
  17.  
    struct sk_buff *skb_1;
  18.  
    struct nlmsghdr *nlh;
  19.  
    int len = NLMSG_SPACE(MAX_MSGSIZE);
  20.  
    int slen = 0;
  21.  
    if(!message || !nl_sk)
  22.  
    {
  23.  
    return ;
  24.  
    }
  25.  
    printk(KERN_ERR "pid:%d\n",pid);
  26.  
    skb_1 = alloc_skb(len,GFP_KERNEL);
  27.  
    if(!skb_1)
  28.  
    {
  29.  
    printk(KERN_ERR "my_net_link:alloc_skb error\n");
  30.  
    }
  31.  
    slen = stringlength(message);
  32.  
    nlh = nlmsg_put(skb_1, 0,0,0,MAX_MSGSIZE,0);
  33.  
    NETLINK_CB(skb_1).pid = 0;
  34.  
    NETLINK_CB(skb_1).dst_group = 0;
  35.  
    message[slen]= '\0';
  36.  
    memcpy(NLMSG_DATA(nlh),message,slen+1);
  37.  
    printk( "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
  38.  
    netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);
  39.  
    }
  40.  
    int stringlength(char *s)
  41.  
    {
  42.  
    int slen = 0;
  43.  
    for(; *s; s++)
  44.  
    {
  45.  
    slen++;
  46.  
    }
  47.  
    return slen;
  48.  
    }
  49.  
    //接收用戶態發來的消息
  50.  
    void nl_data_ready(struct sk_buff *__skb)
  51.  
    {
  52.  
    struct sk_buff *skb;
  53.  
    struct nlmsghdr *nlh;
  54.  
    char str[100];
  55.  
    struct completion cmpl;
  56.  
    printk( "begin data_ready\n");
  57.  
    int i=10;
  58.  
    int pid;
  59.  
    skb = skb_get (__skb);
  60.  
    if(skb->len >= NLMSG_SPACE(0))
  61.  
    {
  62.  
    nlh = nlmsg_hdr(skb);
  63.  
    memcpy(str, NLMSG_DATA(nlh), sizeof(str));
  64.  
    printk( "Message received:%s\n",str) ;
  65.  
    pid = nlh->nlmsg_pid;
  66.  
    while(i--)
  67.  
    { //我們使用completion做延時,每3秒鍾向用戶態回發一個消息
  68.  
    init_completion(&cmpl);
  69.  
    wait_for_completion_timeout(&cmpl, 3 * HZ);
  70.  
    sendnlmsg( "I am from kernel!",pid);
  71.  
    }
  72.  
    flag = 1;
  73.  
    kfree_skb(skb);
  74.  
    }
  75.  
    }
  76.  
    // Initialize netlink
  77.  
    int netlink_init(void)
  78.  
    {
  79.  
    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 1,
  80.  
    nl_data_ready, NULL, THIS_MODULE);
  81.  
    if(!nl_sk){
  82.  
    printk(KERN_ERR "my_net_link: create netlink socket error.\n");
  83.  
    return 1;
  84.  
    }
  85.  
    printk( "my_net_link_4: create netlink socket ok.\n");
  86.  
    return 0;
  87.  
    }
  88.  
    static void netlink_exit(void)
  89.  
    {
  90.  
    if(nl_sk != NULL){
  91.  
    sock_release(nl_sk->sk_socket);
  92.  
    }
  93.  
    printk( "my_net_link: self module exited\n");
  94.  
    }
  95.  
    module_init(netlink_init);
  96.  
    module_exit(netlink_exit);
  97.  
    MODULE_AUTHOR( "zhao_h");
  98.  
    MODULE_LICENSE( "GPL");

附上內核代碼的Makefile文件:

  1.  
    ifneq ($(KERNELRELEASE),)
  2.  
    obj-m :=netl.o
  3.  
    else
  4.  
    KERNELDIR ?=/lib/modules/$(shell uname -r)/build
  5.  
    PWD :=$(shell pwd)
  6.  
    default:
  7.  
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  8.  
    endif

我們將內核模塊insmod后,運行用戶態程序,結果如下:

 

這個結果復合我們的預期,但是運行過程中打印出“state_smg”卡了好久才輸出了后面的結果。這時候查看客戶進程是處於D

狀態的(不了解D狀態的同學可以google一下)。這是為什么呢?因為進程使用Netlink向內核發數據是同步,內核向進程發數

據是異步。什么意思呢?也就是用戶進程調用sendmsg發送消息后,內核會調用相應的接收函數,但是一定到這個接收函數執行

完用戶態的sendmsg才能夠返回。我們在內核態的接收函數中調用了10次回發函數,每次都等待3秒鍾,所以內核接收函數30秒

后才返回,所以我們用戶態程序的sendmsg也要等30秒后才返回。相反,內核回發的數據不用等待用戶程序接收,這是因為內核

所發的數據會暫時存放在一個隊列中。

再來回到之前的一個問題,用戶態程序的源地址(pid)可以用0嗎?我把上面的用戶程序的A和C處pid都改為了0,結果一運行

就死機了。為什么呢?我們看一下內核代碼的邏輯,收到用戶消息后,根據消息中的pid發送回去,而pid為0,內核並不認為這

是用戶程序,認為是自身,所有又將回發的10個消息發給了自己(內核),這樣就陷入了一個死循環,而用戶態這時候進程一直

處於D。

另外一個問題,如果同時啟動兩個用戶進程會是什么情況?答案是再調用bind時出錯:“Address already in use”,這個同

UDP一樣,同一個地址同一個port如果沒有設置SO_REUSEADDR兩次bind就會出錯,之后我用同樣的方式再Netlink的socket

上設置了SO_REUSEADDR,但是並沒有什么效果。

七:用戶態范例二

之前我們說過UDP可以使用sendmsg/recvmsg也可以使用sendto/recvfrom,那么Netlink同樣也可以使用sendto/

recvfrom。具體實現如下:

  1.  
    #include <sys/stat.h>
  2.  
    #include <unistd.h>
  3.  
    #include <stdio.h>
  4.  
    #include <stdlib.h>
  5.  
    #include <sys/socket.h>
  6.  
    #include <sys/types.h>
  7.  
    #include <string.h>
  8.  
    #include <asm/types.h>
  9.  
    #include <linux/netlink.h>
  10.  
    #include <linux/socket.h>
  11.  
    #include <errno.h>
  12.  
    #define MAX_PAYLOAD 1024 // maximum payload size
  13.  
    #define NETLINK_TEST 25
  14.  
    int main(int argc, char* argv[])
  15.  
    {
  16.  
    struct sockaddr_nl src_addr, dest_addr;
  17.  
    struct nlmsghdr *nlh = NULL;
  18.  
    int sock_fd, retval;
  19.  
    int state,state_smg = 0;
  20.  
    // Create a socket
  21.  
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
  22.  
    if(sock_fd == -1){
  23.  
    printf("error getting socket: %s", strerror(errno));
  24.  
    return -1;
  25.  
    }
  26.  
    // To prepare binding
  27.  
    memset(&src_addr, 0, sizeof(src_addr));
  28.  
    src_addr.nl_family = AF_NETLINK;
  29.  
    src_addr.nl_pid = 100;
  30.  
    src_addr.nl_groups = 0;
  31.  
    //Bind
  32.  
    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
  33.  
    if(retval < 0){
  34.  
    printf("bind failed: %s", strerror(errno));
  35.  
    close(sock_fd);
  36.  
    return -1;
  37.  
    }
  38.  
    // To orepare create mssage head
  39.  
    nlh = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_PAYLOAD));
  40.  
    if(!nlh){
  41.  
    printf("malloc nlmsghdr error!\n");
  42.  
    close(sock_fd);
  43.  
    return -1;
  44.  
    }
  45.  
    memset(&dest_addr,0,sizeof(dest_addr));
  46.  
    dest_addr.nl_family = AF_NETLINK;
  47.  
    dest_addr.nl_pid = 0;
  48.  
    dest_addr.nl_groups = 0;
  49.  
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  50.  
    nlh->nlmsg_pid = 100;
  51.  
    nlh->nlmsg_flags = 0;
  52.  
    strcpy(NLMSG_DATA(nlh),"Hello you!");
  53.  
    //send message
  54.  
    printf("state_smg\n");
  55.  
    sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD), 0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));
  56.  
    if(state_smg == -1)
  57.  
    {
  58.  
    printf("get error sendmsg = %s\n",strerror(errno));
  59.  
    }
  60.  
    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
  61.  
    //receive message
  62.  
    printf("waiting received!\n");
  63.  
    while(1){
  64.  
    printf("In while recvmsg\n");
  65.  
    state=recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD), 0,NULL,NULL);
  66.  
    if(state<0)
  67.  
    {
  68.  
    printf("state<1");
  69.  
    }
  70.  
    printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
  71.  
    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
  72.  
    }
  73.  
    close(sock_fd);
  74.  
    return 0;
  75.  
    }

熟悉UDP編程的同學看到這個程序一定很熟悉,除了多了一個Netlink消息頭的設置。但是我們發現程序中調用了bind函數,這

個函數再UDP編程中的客戶端不是必須的,因為我們不需要把UDP socket與某個地址關聯,同時再發送UDP數據包時內核會為

我們分配一個隨即的端口。但是對於Netlink必須要有這一步bind,因為Netlink內核可不會為我們分配一個pid。再強調一遍消

息頭(nlmsghdr)中的pid是告訴內核接收端要回復的地址,但是這個地址存不存在內核並不關心,這個地址只有用戶端調用了

bind后才存在。

我們看到這兩個例子都是用戶態首先發起的,那Netlink是否支持內核態主動發起的情況呢?

當然是可以的,只是內核一般需要事件觸發,這里,只要和用戶態約定號一個地址(pid),內核直接調用netlink_unicast就可以了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM