netlink socket

来源:HAOMCU

linux ipc源于Unix平台上的ipc
Unix两大分支AT&T Unix和BSD Unix在ipc实现上不同:

AT&T实现运行在单个计算机上的System V ipc.
BSD实现基于socket的ipc.
Linux遵循IEEE的Posix IPC标准.

linux在如上基础实现以下IPC机制:
Pipe/Named Pipe/Signal/Message queue/Shared Memory/Semaphore/Socket,user进程之间。
Netlink(基于socket),kernel与user数据的及时交换,

2.相关研究

目前Linux提供9种机制完成kernel&user数据交换 :
内核启动参数/模块参数与sysfs/sysctl/系统调用/netlink/procfs/seq_file/debugfs/relayfs

  • 模块参数与sysfs/procfs/debugfs/relayfs,基于fs的机制,kernel–>user输出信息
  • sysctl/sc,user发起的通信机制
  • 上均为单工通信机制

Netlink是基于socket的通信机制,由于socket本身的双共性、突发性、不阻塞特点,因此能满足kernel&user小量数据的及时交互,在Linux 2.6内核中广泛使用
eg>SELinux,Linux系统的防火墙分为内核态的netfilter和用户态的iptables,netfilter与iptables的数据交换就是通过Netlink机制完成。

3.1 Netlink机制

linux os中当cpu处于内核状态时,可分为有用户上下文的状态执行硬件、软件中断

1>处于有用户上下文时,由于内核态和用户态的内存映射机制不同,不可直接将本地变量传给用户态的内存区
2>处于硬件、软件中断时,无法直接向用户内存区传递数据,代码执行不可中断 //传递数据会阻塞

传统ipc无法直接在kernel&user间使用:

通信方法无法使用原因
管道(除命名管道)局限于父子进程间的通信 1>
消息队列在硬、软中断中无法无阻塞地接收数据 2>
信号量无法介于内核态和用户态使用 1>
内存共享需要信号量辅助,而信号量又无法使用 1>
套接字在硬、软中断中无法无阻塞地接收数据 2>

解决user<->kernel通信机制可分为两类:

  • 处于有用户上下文时,可用copy_from_user()/copy_to_user()完成,但可能阻塞,因此不能在硬件、软件中断过程中使用
  • 处于硬、软件中断时
    a>可通过kernel提供的spinlock自旋锁实现内核线程与中断过程的同步,由于内核线程运行在有上下文的进程中,因此可在内核线程中用套接字/消息队列来取得用户空间的数据,然后再将数据通过临界区传给中断过程
    b>netlink机制。netlink套接字的通信依据是一个对应于进程的标识,一般为该进程pid。netlink最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不需用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函数。通过软中断而不是自行启动内核线程保证了数据传输的及时性

建立netlink会话过程:
3.2
内核首先通过netlink_kernel_create()创建套接字:

struct sock *netlink_kernel_create(
    struct net *net, //网络设备命名空间指针 
    int unit, unsigned int groups,  
    void (*input)(struct sk_buff *skb), //netlink socket在接受到消息时调用的回调函数指针 
    struct mutex *cb_mutex, //默认为THIS_MODULE
);

用户空间进程发送数据用sendmsg()
需添加struct msghdr消息和nlmsghdr消息头。netlink消息体=nlmsghdr+payload部分
//sendmsg()后,内核进入nlmsghdr指向的缓冲区
内核空间发送数据用独立创建的sk_buff缓冲区,linux宏方便设置缓冲区地址:

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

对缓冲区设置完消息地址之后,可用netlink_unicast()发布单播消息:

int netlink_unicast(
    struct sock *sk, //函数netlink_kernel_create()返回的socket,参数skb存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块
    u32 pid, //接收消息进程的pid
    int nonblock //该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠
);

内核模块/子系统也可用netlink_broadcast发送广播消息:

void netlink_broadcast(
    struct sock *sk, 
    struct sk_buff *skb, 
    u32 pid, 
    u32 group, //为接收消息的多播组,该参数的每一个代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或
    int allocation //为内核内存分配类型,一般为GFP_ATOMIC/GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),GFP_KERNEL用于非原子上下文
);

recvmsg()收数据时需申请足够空间存储netlink消息头和消息的payload部分。

4.netlink通信过程

用KGDB+GDB组合调试netlink通信过程
调试平台:Vmware 5.5 + Fedora Core 10(两台,一台作为host机,一台作为target机)
调试程序:内核模块&用户空间两部分,内核模块被加载后,运行用户空间程序,由用户空间发起Netlink会话,和内核模块进行数据交换。
内核模块无法通过外加的调试器进行调试,KGDB提供了一种内核源码级调试机制。Linux-2.6.26之后在内核中内置了KGDB选项,编译内核时需选择相关选项,调试时host端需使用带有符号表的vmlinz内核,target端使用gdb调试用户空间的程序。

用户空间关键代码:

int send_pck_to_kern(u8 op, const u8 *data, u16 data_len) {    
    struct user_data_ *pck;    
    int ret;       

    pck = (struct user_data_*)calloc(1, sizeof(*pck) + data_len);    
    if(!pck) {    
       printf("calloc in %s failed!!!\n", __FUNCTION__);   
       return -1;    
    }     

    pck->magic_num = MAGIC_NUM_RNQ;    
    pck->op = op;    
    pck->data_len = data_len;    
    memcpy(pck->data, data, data_len);       

    ret = send_to_kern((const u8*)pck, sizeof(*pck) + data_len);  
    if(ret)    
       printf("send_to_kern in %s failed!!!\n", __FUNCTION__);         

    free(pck);       

    return ret ? -1 : 0;    
}  

static void recv_from_nl() {    
    char buf[1000];    
    int len;    
    struct iovec iov = {buf, sizeof(buf)};    
    struct sockaddr_nl sa;    
    struct msghdr msg;    
    struct nlmsghdr *nh;    

    memset(&msg, 0, sizeof(msg));   
    msg.msg_name = (void *)&sa;    
    msg.msg_namelen = sizeof(sa);    
    msg.msg_iov = &iov;    
    msg.msg_iovlen = 1;    

    //len = recvmsg(nl_sock, &msg, 0);    
    len = recvmsg(nl_sock, &msg, 0);       

    for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);    
           nh = NLMSG_NEXT (nh, len)) {    
       // The end of multipart message.    
       if (nh->nlmsg_type == NLMSG_DONE) {    
           puts("nh->nlmsg_type == NLMSG_DONE");    
           return;  
       }   

       if (nh->nlmsg_type == NLMSG_ERROR) {    
           // Do some error handling.   
           puts("nh->nlmsg_type == NLMSG_ERROR");    
           return;    
       }              
#if 1  
       puts("Data received from kernel:");    
       hex_dump((u8*)NLMSG_DATA(nh), NLMSG_PAYLOAD(nh, 0));    
#endif    
    }    
}  

内核模块需防止资源抢占,保证netlink资源互斥占有,内核模块关键代码:

static void nl_rcv(struct sk_buff *skb)  {   
    mutex_lock(&nl_mtx);         
    netlink_rcv_skb(skb, &nl_rcv_msg);         
    mutex_unlock(&nl_mtx);    
}            

static int nl_send_msg(const u8 *data, int data_len)  {    
    struct nlmsghdr *rep;    
    u8 *res;    
    struct sk_buff *skb;      

    if(g_pid < 0 || g_nl_sk == NULL) {    
       printk("Invalid parameter, g_pid = %d, g_nl_sk = %p\n", g_pid, g_nl_sk);    
       return -1;   
    }     

    skb = nlmsg_new(data_len, GFP_KERNEL);    
    if(!skb) {   
       printk("nlmsg_new failed!!!\n");  
       return -1;  
    }     

    if(g_debug_level > 0) {    
       printk("Data to be send to user space:\n");    
       hex_dump((void*)data, data_len);    
    }       

    rep = __nlmsg_put(skb, g_pid, 0, NLMSG_NOOP, data_len, 0);    
    res = nlmsg_data(rep);    
    memcpy(res, data, data_len);    
    netlink_unicast(g_nl_sk, skb, g_pid, MSG_DONTWAIT);    

    return 0;   
}       

static int nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) {   
    const u8 res_data[] = "Hello, user";   
    size_t data_len;           
    u8 *buf;    
    struct user_data_ *pck;    
    struct user_req *req, *match = NULL;                

    g_pid = NETLINK_CB(skb).pid;         
    buf = (u8*)NLMSG_DATA(nlh);    
    data_len = nlmsg_len(nlh);       

    if(data_len < sizeof(struct user_data_)) {    
       printk("Too short data from user space!!!\n");    
       return -1;    
    }   

    pck = (struct user_data_ *)buf;    
    if(pck->magic_num != MAGIC_NUM_RNQ) {   
       printk("Magic number not matched!!!\n");   
       return -1;    
    }       

    if(g_debug_level > 0) {   
       printk("Data from user space:\n");    
       hex_dump(buf, data_len);   
    }          

    req = user_reqs;    
    while(req->op) {    
       if(req->op == pck->op) {    
           match = req;    
           break;    
       }         
       req++;    
    }       

    if(match) {    
       match->handler(buf, data_len);    
    }         

    nl_send_msg(res_data, sizeof(res_data));  

    return 0;    
}  

5.总结

netlink是linux特殊socket,类似于BSD的AF_ROUTE但又比它强大,目前Linux-2.6.14中用netlink进行user&kernel通信的应用很多:
路由 daemon(NETLINK_ROUTE)
1-wire 子系统(NETLINK_W1)
用户态 socket 协议(NETLINK_USERSOCK)
防火墙(NETLINK_FIREWALL)
socket 监视(NETLINK_INET_DIAG)
netfilter 日志(NETLINK_NFLOG)
ipsec 安全策略(NETLINK_XFRM)
SELinux 事件通知(NETLINK_SELINUX)
iSCSI 子系统(NETLINK_ISCSI)
进程审计(NETLINK_AUDIT)
转发信息表查询(NETLINK_FIB_LOOKUP)
netlink connector(NETLINK_CONNECTOR)
netfilter 子系统(NETLINK_NETFILTER)
IPv6 防火墙(NETLINK_IP6_FW)
DECnet 路由信息(NETLINK_DNRTMSG)
内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)
通用 netlink(NETLINK_GENERIC)

netlink是kernel<->user进行双向数据传输的非常好的方式,user用标准socket API就可使用netlink,kernel需用专门的内核API使用netlink
netlink vs sc/ioctl/procfs 优点

1>使用netlink,用户仅需在include/linux/netlink.h自定义新协议并加入协议族, 如 #define NETLINK_MYTEST 17 然后,kernel<->user就可通过socket API用该netlink协议类型进行数据交换。但sc要增加新的系统调用,ioctl要增加设备/文件,procfs要在/proc下添加新的文件/目录,代码很多,使/proc更加混乱。
2>netlink是异步通信机制,在kernel&user间传的消息保存在socket缓存队列中,发消息只是把消息保存在接收者的socket接收队列,而不需等待接收者收到消息,但sc/ioctl是同步通信机制,如传数据太长,将影响调度粒度。
3>用netlink的内核部分可采用模块的方式实现,用netlink的应用部分和内核部分没有编译时依赖,但sc有依赖且新sc的实现必须静态地(build-in)连接到内核中,无法在模块中实现,用新sc的应用在编译时需要依赖内核。
4>netlink支持多播,内核模块/应用可把消息多播给一个netlink组,属于该neilink组的任何内核模块/应用都能接收到该消息.内核事件向用户态的通知机制就用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件。
5>内核可用netlink首先发起会话,但sc/ioctl/procfs只能由user进程发起
6>netlink使用标准的socket API,容易使用,但sc/ioctl要专门培训才能使用

user用标准socket api, socket()/bind()/sendmsg()/recvmsg()/close()就能用netlink socket将pid发送至内核空间:
创建netlink socket:

int socket(
    int domain, //Netlink协议族AF_NETLINK/PF_NETLINK(一样),表示要用netlink
    int type, //必须是SOCK_RAW/SOCK_DGRAM
    int protocol //为Netlink提供的协议/用户自定义的协议
);

内核预定义的协议类型有:

#define NETLINK_ROUTE 0  
#define NETLINK_W1 1  
#define NETLINK_USERSOCK 2   
#define NETLINK_FIREWALL 3  
#define NETLINK_INET_DIAG 4  
#define NETLINK_NFLOG 5  
#define NETLINK_XFRM 6   
#define NETLINK_SELINUX 7   
#define NETLINK_ISCSI 8   
#define NETLINK_AUDIT 9   
#define NETLINK_FIB_LOOKUP 10   
#define NETLINK_CONNECTOR 11   
#define NETLINK_NETFILTER 12   
#define NETLINK_IP6_FW 13   
#define NETLINK_DNRTMSG 14   
#define NETLINK_KOBJECT_UEVENT 15   
#define NETLINK_GENERIC 16

16是通用的协议类型,专为用户使用,用户可直接用它而不必再添加新类型。
上述协议已为不同系统应用所使用,每种不同应用都有特有的传输数据格式,因此如果用户不用这些协议,需加入自定义协议类型。对于每个类型最多有32多播组,每个多播组用一个位表示,多播特性使得发送消息给同一个组仅需一次sc,因而对于需要多播消息的应用而言,降低了系统调用次数。

bind()把本地socket地址(源socket地址)与打开的netlink socket绑定,内核空间接收到用户pid后便可进行通讯。netlink socket的地址结构如下:

struct sockaddr_nl {  
   sa_family_t nl_family; //必须为AF_NETLINK/PF_NETLINK 
   unsigned short nl_pad;  //当前没有使用,因此为0
   __u32 nl_pid;  //接收/发送消息的pid,如希望内核处理消息/多播消息,就设为0,否则设为处理消息的pid
   __u32 nl_groups; //指定多播组,bind用于把调用进程加入到该字段指定的多播组,如果设为0,表示调用者不加入任何多播组。
};  

nl_pid应为本进程的pid,相当于netlink socket的本地地址。但对于一个进程的多个线程使用netlink socket,nl_pid可为其它值,如:

pthread_self() << 16 | getpid();

nl_pid未必是pid,只用于区分不同接收者/发送者的一个标识,可根据需要去设置:

bind(
    fd, //netlink socket
    (struct sockaddr*)&nladdr, //struct sockaddr_nl类型的地址。为了发送netlink消息给内核或其他用户态应用,需要填充目标netlink socket地址,此时,字段nl_pid和nl_groups分别表示接收消息者的进程ID与多播组。如果nl_pid为0,表示消息接收者为内核或多播组,如果nl_groups为0,表示该消息为单播消息,否则为多播消息
    sizeof(struct sockaddr_nl)
);

sendmsg发送netlink消息时要引用struct msghdr/struct nlmsghdr/struct iovec,struct msghdr需如下设置:

struct msghdr msg;   
memset(&msg, 0, sizeof(msg));   
msg.msg_name = (void *)&(nladdr);   
msg.msg_namelen = sizeof(nladdr);  

其中nladdr为消息接收者的netlink地址。
struct nlmsghdr为netlink socket自己的消息头,用于多路复用和多路分解 netlink定义的所有协议类型以及其它一些控制,netlink的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,因此也被称为netlink控制块。因此,应用在发送 netlink 消息时必须提供该消息头。

struct nlmsghdr {  
    __u32 nlmsg_len;  //消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小 
    __u16 nlmsg_type;  //用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为 0 
    __u16 nlmsg_flags; //用于设置消息标志,可用的标志如下: 
    __u32 nlmsg_seq;  
    __u32 nlmsg_pid;  
};  
#define NLM_F_REQUEST 1   
#define NLM_F_MULTI     2   
#define NLM_F_ACK        4   
#define NLM_F_ECHO      8   
#define NLM_F_ROOT     0x100   
#define NLM_F_MATCH    0x200   
#define NLM_F_ATOMIC  0x400   
#define NLM_F_DUMP      (NLM_F_ROOT|NLM_F_MATCH)   
#define NLM_F_REPLACE  0x100   
#define NLM_F_EXCL       0x200   
#define NLM_F_CREATE   0x400   
#define NLM_F_APPEND   0x800  

NLM_F_REQUEST表示消息是一个请求,所有应用首先发起的消息都应设置该标志。
NLM_F_MULTI 指示该消息是一个多部分消息的一部分,后续消息可通过宏NLMSG_NEXT来获得。
NLM_F_ACK该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。
NLM_F_ECHO该消息是相关的一个包的回传。
NLM_F_ROOT 被许多 netlink 协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求通常导致响应消息设置NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段 nlmsg_type 中指定协议类型。
NLM_F_MATCH 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。
NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。
NLM_F_DUMP 未实现。
NLM_F_REPLACE 用于取代在数据表中的现有条目。
NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,如果条目已经存在,将失败。
NLM_F_CREATE 指示应当在指定的表中创建一个条目。
NLM_F_APPEND 指示在表末尾添加新的条目。
内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源pid。示例:

#define MAX_MSGSIZE 1024  
char buffer[] = "An example message";   
struct nlmsghdr nlhdr;   
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));   
strcpy(NLMSG_DATA(nlhdr),buffer);   
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));   
nlhdr->nlmsg_pid = getpid();   
nlhdr->nlmsg_flags = 0;  

结构 struct iovec 用于把多个消息通过一次系统调用来发送,下面是该结构使用示例:

struct iovec iov;   
iov.iov_base = (void *)nlhdr;   
iov.iov_len = nlh->nlmsg_len;   
msg.msg_iov = &iov;   
msg.msg_iovlen = 1; 

在完成以上步骤后,消息就可以通过下面语句直接发送:

sendmsg(fd, &msg, 0);

应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,添完后就可以直接调用函数 recvmsg() 来接收。

#define MAX_NL_MSG_LEN 1024   
struct sockaddr_nl nladdr;   
struct msghdr msg;   
struct iovec iov;   
struct nlmsghdr * nlhdr;   
nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);   
iov.iov_base = (void *)nlhdr;   
iov.iov_len = MAX_NL_MSG_LEN;   
msg.msg_name = (void *)&(nladdr);   
msg.msg_namelen = sizeof(nladdr);   
msg.msg_iov = &iov;   
msg.msg_iovlen = 1;   
recvmsg(fd, &msg, 0);  

注意:fd为socket调用打开的netlink socket描述符。
在消息接收后,nlhdr指向消息头,nladdr保存了接收到的消息的目标地址,宏NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。
在linux/netlink.h中定义了一些方便处理消息的宏,包括:
#define NLMSG_ALIGNTO 4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值。

#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存。

#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存。

#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。

#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用。

#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长。

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度。

close()关闭打开的netlink socket。

netlink内核api
netlink的内核实现在net/core/af_netlink.c中,内核模块想用netlink,须#include< linux/netlink.h>
内核用netlink需专门API,完全不同于user对netlink的使用。如果用户需要增加新的netlink协议类型,须通过修改linux/netlink.h来实现
目前的netlink实现已经包含了一个通用的协议类型NETLINK_GENERIC以方便用户使用,用户可以直接使用它而不必增加新的协议类型。
增加新netlink协议类型,用户仅需在linux/netlink.h增加定义:
#define NETLINK_MYTEST 17
之后,用户可在内核的任何地方引用该协议。
在内核中,创建netlink socket:

struct sock * netlink_kernel_create(
    int unit, //netlink协议类型,如NETLINK_MYTEST
    void (*input)(struct sock *sk, int len) //内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引用。参数sk是函数netlink_kernel_create返回的struct sock指针,sock是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个struct sock结构来表示。 
);

input函数示例:

void input (struct sock *sk, int len) { 
    struct sk_buff *skb; 
    struct nlmsghdr *nlh = NULL; 
    while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
        nlh = (struct nlmsghdr *)skb->data; 
        data = NLMSG_DATA(nlh);
    } 
}

input()会在发送进程执行sendmsg()时被调用,这样处理消息比较及时,但是,如果消息特别长时,这样处理将增加系统调用sendmsg()的执行时间,对于这种情况,可以定义一个内核线程专门负责消息接收,而函数input的工作只是唤醒该内核线程,这样sendmsg将很快返回。
struct sk_buff skb = skb_dequeue(&sk->receive_queue) //取得socket sk的接收队列上的消息,skb->data指向实际的netlink消息。
skb_recv_datagram(nl_sk)也用于在netlink socket nl_sk上接收消息,与skb_dequeue的不同指出是,如果socket的接收队列上没有消息,它将导致调用进程睡眠在等待队列nl_sk->sk_sleep,因此它必须在进程上下文使用,刚才讲的内核线程就可以采用这种方式来接收消息。
下面的input()就是这种使用的示例:

void input (struct sock *sk, int len) {                  wake_up_interruptible(sk->sk_sleep);
}

当内核中发送netlink消息时,也需要设置目标地址与源地址,而且内核中消息是通过struct sk_buff来管理的, linux/netlink.h中定义了一个宏:

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb)) //来方便消息的地址设置。消息地址设置的例子:
NETLINK_CB(skb).pid = 0;//消息发送者进程ID,即源地址,对于内核,它为0
NETLINK_CB(skb).dst_pid = 0;//消息接收者进程ID,即目标地址,如果目标为组或内核,设置为0 
NETLINK_CB(skb).dst_group = 1;//目标组地址,如果目标为某一进程或内核,设置为0

在内核中,模块调用netlink_unicast()发送单播消息:

int netlink_unicast(
    struct sock *sk,//netlink_kernel_create()返回的socket 
    struct sk_buff *skb,//存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块 
    u32 pid,//接收消息进程的pid 
    int nonblock//该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠
);

内核模块/子系统也可用netlink_broadcast()发送广播消息:

void netlink_broadcast(
    struct sock *sk, 
    struct sk_buff *skb, 
    u32 pid, 
    u32 group,//接收消息的多播组,该参数的每一个代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或 
    int allocation//内核内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文
);

在内核中用sock_release()来释放netlink_kernel_create()创建的netlink socket:
void sock_release(struct socket * sock);
注意netlink_kernel_create()返回类型为struct sock sk,因此应该:
sock_release(sk->sk_socket);

在源代码包中给出了一个使用netlink的示例,包括一个内核模块netlink-exam-kern.c和两个应用程序netlink-exam-user-recv.c, netlink-exam-user-send.c。内核模块必须先插入到内核,然后在两个终端上分别运行用户态接收程序&用户态发送程序
发送程序读取参数指定的文本文件并把它作为netlink消息的内容发送给内核模块,内核模块接受该消息保存到内核缓存中,也通过proc接口到 procfs,因此用户能够通过/proc/netlink_exam_buffer看到全部内容,同时内核把该消息发送给用户态接收程序,打印接收到的内容。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值