T14 用户空间与内核空间数据交互

1 copy_to_usercopy_from_user

  • 详见1.4

2 netlink

2.1 netlink简介

  • netlink socket是一种linux特有的socket,用与实现用户进程与内核进程之间通讯的一种特殊的进程之间通讯方式(IPC),也是网络应用程序与内核通讯的最经常使用的接口
  • netlink是一种在内核和用户应用空间之间进行双向数据传输的好方式,用户态只须要使用标准的socket API接口就可以使用Netlink所提供的功能,固然,内核态还须要使用专门的内核API来使用Netlink
  • netlink报文格式:netlink的报文一般由消息头与消息体构成,其中消息头使用struct nlmsghdr结构体保存,以下:
/* netlink消息报文消息头 */
/*
    @nlmsg_len:整个消息长度,包含netlink消息头自己,单位:字节
    @nlmsg_type:消息类型,便是数据消息仍是控制消息,其中有如下4种控制消息
    	NLMSG_NOOP:空消息,啥也不干
    	NLMSG_ERROR:指明该消息中含有一个错误
    	NLMSG_DONE:若是内核经过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其他全部				   消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效
    	NLMSG_OVERRUN:暂时用不到
    @nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI,通常不多用
    @nlmsg_seq:序列号,不多用
    @nlmsg_pid:消息发送方的PID进程号,若是发送方是内核,那么为0
*/
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 PID */
};
  • 一些处理消息的宏
/* 获得不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGNTO   4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

/* 计算数据部分长度为len时实际的消息长度,它通常用于分配消息缓存 */
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

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

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

/* 获得下一个消息的首地址,同时len也减小为剩余消息的总长度,该宏通常在一个消息被分红几个部分发送或接收时使用 */
#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))

/* 返回payload的长度 */
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
  • 用户层:用户层使用方法跟UDP网络使用方法相似
/* step1:创建socket套接字 */
#include <sys/types.h>       
#include <sys/socket.h>
/*
功能:建立套接字
参数:
	@domain:地址族(Address Family),即IP地址类型,经常使用有:
      		AF_INET(IPV4类型),AF_INET6(IPV6类型)
      		AF_UNIX, AF_LOCAL(本地进程通讯)
      		AF_NETLINK(内核与用户空间的通讯,用于设备驱动)
      		AF_PACKET(原始套接字)
	@type:套接字类型,经常使用有:
     		SOCK_STREAM(流式套接字,对应TCP)
     		SOCK_DGRAM(数据报套接字,对应UDP)
     		SOCK_RAW(原始套接字)
	@protocol:TCP与UDP编程时候填0,原始套接字时候需填充,为netlink时候需填入netlink协议类型
返回值:成功返回一个文件描述符,失败返回-1
*/
int socket(int domain, int type, int protocol);

/* 一共支持32种消息类型,下述是linux系统所定义好的几种消息类型,剩下的可本身定义 */
#define NETLINK_ROUTE           0       /* Routing/device hook(路由)                   */
#define NETLINK_W1              1       /* 1-wire subsystem                             */
#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
/***************************************************************************************************/
/* step2:绑定端口 */
#include <sys/types.h>
#include <sys/socket.h>
/*
功能:将一本地地址与一套接口捆绑
参数:
	@sockfd:经过socket函数得到的文件描述符
	@addr:结构体地址,基于Internet通讯时候填sockaddr_in类型结构体,须要强制转换为sockaddr类型
	@addrlen:结构体长度
返回值:成功返回0,失败返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 注意:在绑定的时候bind()函数的参数2是sockaddr类型参数,实际在编程中使用的却不是该格式的数据类型,因此在填充的时候须要强制转换为struct sockaddr类型。在网络编程中使用的是struct sockaddr_in相似的数据,而在与内核通讯时候须要填充为struct sockaddr_nl类型,该类型详细介绍以下:*/
struct sockaddr_nl
{
    sa_family_t nl_family; 		/*该字段老是为AF_NETLINK */
    unsigned short nl_pad; 		/* 目前未用到,填充为0*/
    __u32 nl_pid;               /* 绑定者进程的进程号,一般设置为当前进程的进程号 */
    __u32 nl_groups;            /* 绑定者但愿加入的多播组 */
};
/***************************************************************************************************/
/* setp3:发送消息 */
int sendmsg(struct socket *sock, struct msghdr *m, size_t total_len);
/* msghdr参数格式以下:
	@msg_name:指向sockaddr的指针,在netlink中指向sockaddr_nl类型
	@msg_namelen:msg_name的长度
	@msg_iov:数据
*/
struct msghdr {
    void    *    msg_name;    /* Socket name            */
    int        msg_namelen;    /* Length of name        */
    struct iovec *    msg_iov;    /* Data blocks            */
    __kernel_size_t    msg_iovlen;    /* Number of blocks        */
    void     *    msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */
    __kernel_size_t    msg_controllen;    /* Length of cmsg list */
    unsigned    msg_flags;
};
/* iovc结构体组成以下:
	@iov_base:数据的基地址
	@iov_len:数据的长度
*/
struct iovec
{
    void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
    __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
  • 内核层
/************************************内核API*********************************************************/
/*
功能:建立netlink socket(老版本内核)
参数:
	@net:一个网络名字空间namespace,在不一样的名字空间里面能够有本身的转发信息库,有本身的一套net_device等等,默		  认状况下都是使用 init_net 这个全局变量
	@unit:表示netlink协议类型,如NETLINK_TEST、NETLINK_SELINUX
	@groups:多播地址
	@input:为内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引		   用,且只有此函数返回时,调用者的sendmsg才能返回
	@cb_mutex:为访问数据时的互斥信号量
	@module:通常为THIS_MODULE
*/
struct sock *netlink_kernel_create(struct net *net,
                                   int unit,unsigned int groups,
                                   void (*input)(struct sk_buff *skb),
                                   struct mutex *cb_mutex,struct module *module);
/*
功能:建立netlink socket(新版本内核)
参数:
	@net:一个网络名字空间namespace,在不一样的名字空间里面能够有本身的转发信息库,有本身的一套net_device等等,默		  认状况下都是使用 init_net这个全局变量
	@unit:表示netlink协议类型,如NETLINK_TEST、NETLINK_SELINUX
	@cfg:配置参数结构体
*/
struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
/* netlink_kernel_cfg结构体 */
struct netlink_kernel_cfg {
    unsigned int groups;
    unsigned int flags;
    void (*input)(struct sk_buff *skb);
    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);
}

/*
功能:发送单播消息
参数:
	@ssk:函数netlink_kernel_create()返回的socket
	@skb:存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,宏
		 NETLINK_CB(skb)就用于方便设置该控制块
	@pid:即接收此消息进程的pid,即目标地址,若是目标为组或内核,它设置为0
	@nonblock:表示该函数是否为非阻塞,若是为1,该函数将在没有接收缓存可利用时当即返回;而若是为0,该函数在没有			 	  接收缓存可利用定时睡眠。
*/
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);

/*
功能:发送广播消息
参数:
	@ssk:函数netlink_kernel_create()返回的socket
	@skb:存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,宏
		 NETLINK_CB(skb)就用于方便设置该控制块
	@pid:即接收此消息进程的pid,即目标地址,若是目标为组或内核,它设置为0
	@group:接收消息的多播组,该参数的每个位表明一个多播组,所以若是发送给多个多播组,就把该参数设置为多个多播			组组ID的位相或,全部目标多播组对应掩码的OR操做的合值
	@allocation:内核内存分配类型,通常地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不能够睡				    眠),而GFP_KERNEL用于非原子上下文。
*/
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation);	

/*
功能:释放 netlink socket
*/
void netlink_kernel_release(struct sock *sk);

/**************************************工具函数******************************************************/
/* 从sk_buff->data获取struct nlmsghdr(消息头)数据 */
static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
{
    return (struct nlmsghdr *)skb->data;
}

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

/* 将一个新的netlink消息加入到skb中。若是skb没法存放消息则返回NULL */
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
                     int type, int payload, int flags);

/* 释放nlmsg_new()建立的skb */
static inline void nlmsg_free(struct sk_buff *skb)--------------------------------释放nlmsg_new()建立的skb。
{
    kfree_skb(skb);
}

/* 根据nlmsghdr指针获取对应的payload */
static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{
    return (unsigned char *) nlh + NLMSG_HDRLEN;
}

/* 获取消息流中下一个netlink消息 */
static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh, int *remaining);

/*  */
static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh);

2.3 netlink使用流程分析

2.3.1 用户层分析

  • 首先使用netlink与用户交互数据须要遵照内核所规定的格式
  • 跟UDP网络编程同样,咱们在发送消息的时候须要先创建socket链接,在创建socket链接的时候咱们须要指定地址族为AF_NETLINK类型,专门用于用户与内核空间之间进行数据交互;以后须要将套接字类型设置为原始套接字SOCK_RAW其专门用于进程间通讯;最后的消息类型直接填写咱们本身定义的或者系统定义好的消息类型便可,消息种类可参考2.2
  • 在创建好套接字事后咱们须要绑定端口,其中参数二的类型为sockaddr类型,可是咱们实际的类型倒是sockaddr_nl类型,在此结构体中咱们须要指明咱们的地址族,仍然是AF_NETLINK类型;以后即是设置端口号,注意,虽然代码里面写的是pid,但此pid并不是指的是进程号,相比于进程号,它更像是一个地址,bind函数中此处nl_pid就是数据源地址,即消息的发送者
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr_nl
{
    sa_family_t nl_family; 		/*该字段老是为AF_NETLINK */
    unsigned short nl_pad; 		/* 目前未用到,填充为0*/
    __u32 nl_pid;               /* 消息地址,一般设置为当前进程的进程号,0则表示是内核 */
    __u32 nl_groups;            /* 绑定者但愿加入的多播组 */
};
  • 绑定完成以后就须要咱们去填写消息接收方的信息,一样使用sockaddr_nl结构体去保存消息接收者的信息,示例中的消息接收方是内核,因此nl_pid应该写为0
  • 收发双方信息肯定以后就须要咱们去发送消息了,netlink整个数据包由消息头与消息体所组成,消息头使用nlmsghdr封装起来,在此处咱们须要使用NLMSG_ALIGN宏去计算咱们所申请的用来保存消息头的空间大小
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 PID */
};
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
  • 有了保存消息头的空间以后就须要咱们去完善消息头内容了,首先咱们须要在nlmsg_len处指明消息的大小,此处仍是使用NLMSG_ALIGN宏来计算;以后在nlmsg_pid处填上发送方的地址便可,这里就有人会决定奇怪了,以前不是在sockaddr_nl处指明了消息发送方的地址了吗,为啥还要指定?由于此处不像网络编程,在网络编程里面系统会把发送方的信息给直接写入报文里面去,而此处须要咱们手动写入发信方的地址,这样在接收方接收到这条消息以后才能够从消息中解析出发信人的地址。就比如咱们去邮局寄信,虽然邮局(操做系统)知道你邮寄了一封信件,可是你仍是得在信上写下你的姓名,为的就是告诉收信人这是你发的消息。以后再使用NLMSG_DATA来找到咱们写入消息的位置进而去向这个位置写入消息
/* 取得消息的数据部分的首地址,设置和读取消息数据部分时须要使用该宏 */
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
  • 至此,消息准备完毕,以后就须要寻求载体将准备好的消息发送出去,这里咱们先使用sendmsg函数去发送消息,此处须要将上面准备的接收方信息和消息头数据填入msghdr结构体中
int sendmsg(struct socket *sock, struct msghdr *m, size_t total_len);
/* msghdr参数格式以下:
	@msg_name:指向sockaddr的指针,在netlink中指向sockaddr_nl类型
	@msg_namelen:msg_name的长度
	@msg_iov:数据
*/
struct msghdr {
    /* 指向socket地址结构 */
    void    *    msg_name;    /* Socket name            */
    /* 地址结构长度 */
    int        msg_namelen;    /* Length of name        */
    /* 数据 */
    struct iovec *    msg_iov;    /* Data blocks            */
    __kernel_size_t    msg_iovlen;    /* Number of blocks        */
    void     *    msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */
    __kernel_size_t    msg_controllen;    /* Length of cmsg list */
    unsigned    msg_flags;
};
/* iovc结构体组成以下:
	@iov_base:数据的基地址
	@iov_len:数据的长度
*/
struct iovec
{
    void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
    __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
  • 最后直接调用sendmsg函数发送数据

2.3.2 内核层分析

  • 内核端不用多说,首先就是进入模块初始化函数里面,在里面建立一个内核版本的netlink socket,其中咱们须要在参数二处指定咱们所要收发的消息类型,此处应该与应用层的类型保持一致;并且咱们须要在参数三中配置相关参数,其中最重要的就是消息回调函数,当内核收到消息后会进入消息回调函数处理消息,只有当这个函数处理完消息以后,应用层的sendmsg函数才会返回
struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
void (*input)(struct sk_buff *skb);
  • 其中应用层的消息(消息头)被保存在了sk_buff->data结构体中,咱们可经过函数nlmsg_hdr将消息头取出
  • 取出消息头以后可使用NLMSG_DATA宏将用户实际发送的负载信息取出来
  • 至此,用户数据已经被内核拿到,此刻如何给用户层一个反馈呢?
  • 其实又须要反过来,首先准备咱们的负载信息(内核-->用户空间),以后使用nlmsg_new函数去申请一片sk_buff类型的数据区,

2.4 netlink使用示例

  • 用户端实现1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/socket.h>

#define MAX_PAYLOAD 1024
#define NETLINK_TEST 25

int main(int argc, char *argv[])
{
    int sock_fd;
    int ret;
    struct nlmsghdr *nlh;
    struct sockaddr_nl send_addr, recv_addr;
    struct iovec iov;
    struct msghdr msg;
    /* step1:建立一个socket套接字 */
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if (sock_fd == -1) {
        printf("fail to create socket\n");
        return -1;
    }
    /* 填充绑定步骤所须要的数据,地址族,消息源地址,多播组等 */
    memset(&send_addr, 0x00, sizeof(send_addr));
    send_addr.nl_family = AF_NETLINK;
    send_addr.nl_pid = 100;
    send_addr.nl_groups = 0;
    /* step2:绑定,与建立的套接字绑定,发信人信息准备完毕 */
    ret = bind(sock_fd, (struct sockaddr*)&send_addr, sizeof(send_addr));
    if (ret < 0) {
        printf("fail to bind\n");
        close(sock_fd);
        return -1;
    }
    /* 填写消息接收者的信息,收信人信息准备完毕 */
    memset(&recv_addr, 0x00, sizeof(recv_addr));
    recv_addr.nl_family = AF_NETLINK;
    recv_addr.nl_pid = 0;
    recv_addr.nl_groups = 0;
    /* 申请一个空间来存储消息头的信息,申请了一张信纸 */
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if (!nlh) {
        printf("failed to malloc space\n");
        close(sock_fd);
        return -1;
    }
    /* 填写消息头信息,在信纸上写信 */
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = 100;						//填写发送方的地址
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh), "hello kernel\n");	  //填写发送的内容
    /* 将消息头数据再次封装 */
    iov.iov_base = (void *)nlh;
    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
    /* 填写接收方信息 */
    memset(&msg, 0x00, sizeof(msg));
    msg.msg_name = (void *)&recv_addr;
    msg.msg_namelen = sizeof(recv_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    /* 发送消息 */
    ret = sendmsg(sock_fd, &msg, 0);
    if (ret == -1) {
        printf("failed to sendmsg\n");
    }
    /* 清空消息头 */
    memset(nlh, 0x00, NLMSG_SPACE(MAX_PAYLOAD));
    while (1) {
        /* 接收消息 */
        ret = recvmsg(sock_fd, &msg, 0);
        if (ret < 0) {
            printf("failed to recvmsg\n");
        }
        /* 取出收到的消息 */
        printf("APP recv msg--->%s\n", (char *)NLMSG_DATA(nlh));
    }
    close(sock_fd);
    free(nlh);
    nlh = NULL;
    return 0;
}
  • 内核端
#include <linux/module.h>
#include <linux/init.h>
#include <net/netlink.h>
#include <net/sock.h>

#define NETLINK_TEST 25
#define USER_PORT 100

int netlink_count = 0;
/* 发送给用户层的消息 */
char netlink_kmsg[30] = {0};
struct sock *nl_sock = NULL;

int send_to_user(char *buff, int len)
{
    int ret;
    /* 保存消息头 */
    struct nlmsghdr *nlh = NULL;
    /* 保存向用户发送的消息 */
    struct sk_buff *nl_skb;
    /* 申请一片sk_buff类型的数据区 */
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if (!nl_skb) {
        printk("sk_buff malloc failed\n");
        return -1;
    }    
    /* 将消息头数据做为负载加入到sk_buff中 */
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if (!nlh) {
        printk("failed to nlmsg_put\n");
        nlmsg_free(nl_skb);
        return -1;
    }
    /* 在消息头中找到负载区,将负载消息加入到负载区 */
    memcpy(nlmsg_data(nlh), buff, len);
    /* 发送单播消息给用户层 */
    ret = netlink_unicast(nl_sock, nl_skb, USER_PORT, MSG_DONTWAIT);
    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    char *recv_msg = NULL;
    /* 保存消息头 */
    struct nlmsghdr *nlh = NULL;
    if (skb->len >= NLMSG_SPACE(0)) {
        netlink_count++;
        /* 封装给用户层发送的消息 */
        snprintf(netlink_kmsg, sizeof(netlink_kmsg), "recv %d messages from user\n", netlink_count);
        /* 取出消息头 */
        nlh = nlmsg_hdr(skb);
        /* 取出消息头中的负载信息(真实信息) */
        recv_msg = NLMSG_DATA(nlh);
        if (recv_msg) {
            printk(KERN_INFO "recv from user:%s\n", recv_msg);
            /* 发送反馈信息给用户层 */
            send_to_user(netlink_kmsg, strlen(netlink_kmsg));
        }
    }
}

/* 填写netlink socket的配置参数,此处指定了对应的消息处理函数 */
struct netlink_kernel_cfg cfg = {
    .input = netlink_rcv_msg,
};
static int __init netlink_init(void)
{
    /* 建立内核netlink socket */
    nl_sock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg); 
    if (nl_sock == NULL) {
        printk(KERN_ERR "fail to create kernel netlink\n");
        return -1;
    }
    printk("create kernel netlink success\n");
    return 0;
}

static void __exit netlink_exit(void)
{
    if (nl_sock != NULL) {
        netlink_kernel_release(nl_sock);
        nl_sock = NULL;
    }
    printk("kernel netlink release success\n");
}

module_init(netlink_init);
module_exit(netlink_exit);
MODULE_AUTHOR("hq");
MODULE_LICENSE("GPL");
相关文章
相关标签/搜索