Linux驱动-Netlink通信

什么是Netlink通信机制?

        Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink 是一种特殊的 socket,它是 Linux 所特有的。
        Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。

Netlink通信机制有哪些特点?  

  1. 使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
  2. Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。
  3. Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。
  4. 使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖
  5. Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。
//netlink收发是以消息为单位的,每次收发可以包含一个或多个消息(msg)

 ------------------------------------------------------------------------
|                     单次sendto或者recvfrom 数据部分                      |              
 ------------------------------------------------------------------------
|                   msg0       |                    msg1      |  msgn    |
 ------------------------------------------------------------------------
| nlmsghdr | data(携带的数据)   | nlmsghdr | data(携带的数据)   |  ....     |
 ------------------------------------------------------------------------

netlink常用的数据结构以及接口

用户态常用结构体:

          struct sockaddr_nl 结构体:

/*套接字结构体*/
struct sockaddr_nl {
 __kernel_sa_family_t    nl_family;  /* AF_NETLINK (跟AF_INET对应)*/
 unsigned short  nl_pad;     /* zero */
 __u32       nl_pid;     /* port ID  (通信端口号)*/
 __u32       nl_groups;  /* multicast groups mask */
};

        struct nlmsghdr 结构体:

/*struct nlmsghdr 是netlink消息头*/
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 */
};
    nlmsg_len :指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小
    nlmsg_type :用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0
        /* Type values */
 
        #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_NOOP:不执行任何动作,必须将该消息丢弃;
        NLMSG_ERROR:消息发生错误;
        NLMSG_DONE:标识分组消息的末尾;
        NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。
        NLMSG_MIN_TYPEK:预留 */
    nlmsg_flags :用于设置消息标志
        /* Flags values */
 
        #define NLM_F_REQUEST       1   /* It is request message.   */
        #define NLM_F_MULTI     2   /* Multipart message, terminated by NLMSG_DONE */
        #define NLM_F_ACK       4   /* Reply with ack, with zero or error code */
        #define NLM_F_ECHO      8   /* Echo this request        */
        #define NLM_F_DUMP_INTR     16  /* Dump was inconsistent due to sequence change */
        
        /* 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       */
    nlmsg_seq: 消息序列号,用以将消息排队,有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用;
    nlmsg_pid:发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号

 用户态常用接口:

        应用层使用接口是标准的 socket API,与UDP通信类似

        创建套接字:

int socket(int domain, int type, int protocol)
    domain :使用netlink方式通信时配置为 AF_NETLINK
    type :使用netlink方式通信时配置为 SOCK_RAW
    protocol:自定义的通信协议

       绑定套接字:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    addr :传参时要将转入的struct sockaddr_nl结构体指针变量强转为struct sockaddr * 

       发送数据:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen)  

       接收数据:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen)

内核常用结构体:

       struct sock 结构体

        套接字结构体

       struct netlink_kernel_cfg 结构体

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int    groups;
unsigned int    flags;
void        (*input)(struct sk_buff skb);  / *input 回调函数 */一
struct mutex    *cb_mutex;
void        (*bind)(int group);
bool        (*compare)(struct net *net, struct sock *sk);
};

        struct sk_buf 结构体

        套接字缓存,作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递

struct  sk_buff
{
     struct  sk_buff *next;
     struct  sk_buff *prev;
     struct  sock *sock ; //struct sock是socket在网络层的表示,其中存放了网络层的信息
          
     unsigned  int  len; //表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。
     unsigned  int  data_len;  //和len不同,data_len只计算分片中数据的长度
     __u16   mac_len ;  //数路链路层的头长度
     __u16   hdr_len ;  //writable header length of cloned skb
     unsigned  int  truesize ;  //socket buffer(套接字缓存区的大小)
     atomic_t users ;  //对当前的struct sk_buff结构体的引用次数;
     __u32   priority ;  //这个struct sk_buff结构体的优先级
     
     sk_buff_data_t transport_header ;  //传输层头部的偏移量
     sk_buff_data_t network_header ;    //网络层头部的偏移量
     sk_buff_data_t mac_header ;        //数据链路层头部的偏移量
     
     char  *data ;  //socket buffer中数据的起始位置;
     sk_buff_data_t tail ;  //socket buffer中数据的结束位置;
     char  *head ;  //socket buffer缓存区的起始位置;
     sk_buffer_data_t end ;  //socket buffer缓存区的终止位置;
     
     struct  net_device *dev;  //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收网络设备

     int  iif;   //网络设备的接口索引号;   
     struct  timeval tstamp ;  //用于存放接受的数据包的到达时间;
     
     __u8  local_df : 1 ,   //allow local fragmentaion;
           cloned   : 1 ,  // head may be cloned
           ;
     
     __u8  pkt_type : 3 ,  //数据包的类型;
           fclone   : 2,   // struct sk_buff clone status   
}

内核常用接口:

       netlink_kernel_create

        netlink_kernel_create内核函数用于创建 内核socket用用户态通信 

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
   net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义);  定义在net_namespace.c(extern struct net init_net);
   unit:netlink协议类型,对应用户态创建套接字时的protocol参数,两者需保持一致
   cfg: cfg存放的是netlink内核配置参数

       单播netlink_unicast()

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
   ssk: netlink socket 
   skb: skb buff 指针
   portid: 通信的端口号,对应用态的端口号
   nonblock:表示该函数是否为非阻塞,如果为1(MSG_DONTWAIT),该函数将在没有接收缓存可利用时立即返回,而如果为0(MSG_WAITALL),该函数在没有接收缓存可利用 定时睡眠

        多播netlink_broadcast()

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
                 __u32 group, gfp_t allocation);
   ssk: 同上(对应netlink_kernel_create 返回值)、
   skb: 内核skb buff
   portid: 通信的端口号,对应用态的端口号
   group: 是所有目标多播组对应掩码的"OR"操作的合值。
   allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。
                这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone

       nlmsg_hdr()

        获取netlink消息的头部指针

        nlmsg_new()

        分配一个新的netlink消息

struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
    payload : 分配的大小
    flags:
        进程上下文,可以睡眠:GFP_KERNEL
        进程上下文,不可以睡眠:GFP_ATOMIC
        中断处理程序:GFP_ATOMIC
        软中断:GFP_ATOMIC
        Tasklet:GFP_ATOMIC
        用于DMA的内存,可以睡眠:GFP_DMA | GFP_KERNEL
        用于DMA的内存,不可以睡眠:GFP_DMA |GFP_ATOMIC

        nlmsg_put()

        向skb缓冲区中获取消息头空间并且初始化netlink消息头,入参中的第5个参数为netlink消息头的总空间

struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
                          int type, int payload, int flags)
      portid:与 netlink消息头 中的 nlmsg_pid 对应
      seq:与 netlink消息头 中的 nlmsg_seq 对应
      type:与 netlink消息头 中的 nlmsg_type 对应
      payload:与 netlink消息头 中的 nlmsg_len 对应
      flags:与 netlink消息头 中的 nlmsg_flags 对应

用户态与内核对数据处理的宏函数:

1、NLMSG_LENGTH(len)
    根据消息数据部分长度,返回消息长度,即头长度+len,但是这个长度不是对齐的(消息头和整个消息都是4字节对其的;
    len:消息数据部分长度
2、NLMSG_SPACE(len) 
    根据消息数据部分长度,返回消息实际占用空间,也就是在NLMSG_LENGTH(len)基础上进行4字节对齐。
    len:消息数据部分长度
    -----------------------------------------------------------
    | nlmsghdr            |                   data(携带的数据)   |
     -----------------------------------------------------------
    |                     |<-----  len  --->|              |
    | <-------    NLMSG_LENGTH(len)    ---->|<--4字节对齐-->|
    | <-------             NLMSG_SPACE(len)           ---->|
3、NLMSG_DATA(nlh)
    根据消息内存地址,返回数据部分地址.
    nlh:消息头地址,消息头地址和消息地址是一样。         

     -----------------------------------------------------
    | nlmsghdr            |             data(携带的数据)   |
     -----------------------------------------------------
    |                     |
    |                     |
     --->这里就是nlh地址    |
                          |
                           ---> 这里就是返回值,也就是数据部分地址(与TCP/IP协议很像吧, 先是数据头再是数据)
4、NLMSG_NEXT(nlh,len)
    根据当前消息地址,返回下一个消息地址
    nlh: 当前消息地址
    len:剩余消息部分长度,比如当前recvfrom收到100字节数据,包含4个长度为25字节的消息,存储在buf中:
5、NLMSG_OK(nlh,len)
    检查nlh地址是否是一条完整的消息
    nlh: 通过recvfrom收到数据的缓冲区地址
    len: 收到数据的长度
6、NLMSG_PAYLOAD(nlh,len)
    消息数据部分剩余长度
    nlh: 消息的首地址
    len: 数据部分已经使用的长度
    |<------------------100字节-------------------------->|   
     -----------------------------------------------------
    | nlmsghdr            |             data(携带的数据)   |
     -----------------------------------------------------
    |                     |<--------len------|<----+---->|
    |                                              |                                
    |                                              |
     --> nlh                                       +---> 这里就是返回值的含义

你可能感兴趣的:(Linux驱动,Linux内核,linux,c语言,驱动开发)