Linux设备模型(七) - Netlink

一,什么是netlink通信机制

Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink 是一种特殊的 socket,它是 Linux 所特有的。

        Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。

二,netlink通信机制的特点

  • 使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。

  • Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。

  • Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。

  • 使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖

  • Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。

三,用户态常用结构体和接口

1,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 */
};

nl_family 制定了协议族,netlink 有自己独立的值:AF_NETLINK,nl_pid 一般取为进程 pid。nl_groups 用以多播,当不需要多播时,该字段为 0。

nl_pid:该属性为发送或接收消息的进程ID,前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。该属性为0时一般适用于如下两种情况:

第一,我们要发送的目的地是内核,即从用户空间发往内核空间时,我们构造的Netlink地址结构体中nl_pid通常情况下都置为0。这里有一点需要跟大家交代一下,在Netlink规范里,PID全称是Port-ID(32bits),其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为当前进程的进程号。

第二,从内核发出的多播报文到用户空间时,如果用户空间的进程处在该多播组中,那么其地址结构体中nl_pid也设置为0。

2,netlink的消息格式

Netlink消息由两部分组成:消息头和有效数据载荷,且整个Netlink消息是4字节对齐,一般按主机字节序进行传递。消息头为固定的16字节,消息体长度可变:

//netlink收发是以消息为单位的,每次收发可以包含一个或多个消息(msg)------------------------------------------------------------------------|                     单次sendto或者recvfrom 数据部分                      |              ------------------------------------------------------------------------|                   msg0       |                    msg1      |  msgn    |------------------------------------------------------------------------| nlmsghdr | data(携带的数据)   | nlmsghdr | data(携带的数据)   |  ....     |------------------------------------------------------------------------

3,netlink的消息头

消息头定义在<include/linux/netlink.h>文件里,由结构体nlmsghdr表示:

struct nlmsghdr

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 */
};消息头中各成员属性的解释及说明:
nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头本身。nlmsg_type:消息的类型,即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,如下:
NLMSG_NOOP-空消息,什么也不做;
NLMSG_ERROR-指明该消息中包含一个错误;
NLMSG_DONE-如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
NLMSG_OVERRUN-暂时没用到。nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。摘录如下:
NLM_F_REQUEST
如果消息中有该标记位,说明这是一个请求消息。所有从用户空间到内核空间的消息都要设置该位,否则内核将向用户返回一个EINVAL无效参数的错误
NLM_F_MULTI
消息从用户->内核是同步的立刻完成,而从内核->用户则需要排队。如果内核之前收到过来自用户的消息中有NLM_F_DUMP位为1的消息,那么内核就会向用户空间发送一个由多个Netlink消息组成的链表。除了最后个消息外,其余每条消息中都设置了该位有效。
NLM_F_ACK
该消息是内核对来自用户空间的NLM_F_REQUEST消息的响应
NLM_F_ECHO
如果从用户空间发给内核的消息中该标记为1,则说明用户的应用进程要求内核将用户发给它的每条消息通过单播的形式再发送给用户进程。和我们通常说的“回显”功能类似。nlmsg_seq:消息序列号。因为Netlink是面向数据报的,所以存在丢失数据的风险,但是Netlink提供了如何确保消息不丢失的机制,让程序开发人员根据其实际需求而实现。消息序列号一般和NLM_F_ACK类型的消息联合使用,如果用户的应用程序需要保证其发送的每条消息都成功被内核收到的话,那么它发送消息时需要用户程序自己设置序号,内核收到该消息后对提取其中的序列号,然后在发送给用户程序回应消息里设置同样的序列号。有点类似于TCP的响应和确认机制。注意:当内核主动向用户空间发送广播消息时,消息中的该字段总是为0。nlmsg_pid:当用户空间的进程和内核空间的某个子系统之间通过Netlink建立了数据交换的通道后,Netlink会为每个这样的通道分配一个唯一的数字标识。其主要作用就是将来自用户空间的请求消息和响应消息进行关联。说得直白一点,假如用户空间存在多个用户进程,内核空间同样存在多个进程,Netlink必须提供一种机制用于确保每一对“用户-内核”空间通信的进程之间的数据交互不会发生紊乱。

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

/* 宏 NLMSG_ALIGN(len) 用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGNTO   4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )/* Netlink 头部长度 */
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))/* 计算消息数据 len 的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)/* 宏 NLMSG_SPACE(len) 返回不小于 NLMSG_LENGTH(len) 且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))/* 宏 NLMSG_DATA(nlh) 用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))/* 宏 NLMSG_NEXT(nlh,len) 用于得到下一个消息的首地址, 同时 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))/* NLMSG_PAYLOAD(nlh,len) 用于返回 payload 的长度*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

5,创建套接字

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

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

netlink 协议类型

#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    /* Unused number, formerly ip_queue        */
#define NETLINK_SOCK_DIAG    4    /* 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 NETLINK_RDMA        20
#define NETLINK_CRYPTO        21    /* Crypto layer */
#define NETLINK_SMC        22    /* SMC monitoring */#define NETLINK_INET_DIAG    NETLINK_SOCK_DIAG

6,绑定套接字

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

7,收发送数据

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)

四,内核常用结构体和接口

1,struct sock结构体

套接字结构体

2,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);
};

3, 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 skbunsigned  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   
}

struct sk_buff中head, end, data, tail字段的含义

4,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内核配置参数

5,单播netlink_unicast()

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

6,多播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 buffportid: 通信的端口号,对应用态的端口号group: 是所有目标多播组对应掩码的"OR"操作的合值。allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone

7, nlmsg_new()

分配一个新的netlink消息

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

8,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 对应

9,skb API

/**
* alloc_skb - allocate a network buffer
* @size: size to allocate
* @priority: allocation mask
*
* This function is a convenient wrapper around __alloc_skb().
*/
static inline struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)
{return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}static inline void *skb_put_data(struct sk_buff *skb, const void *data,unsigned int len)
{void *tmp = skb_put(skb, len);memcpy(tmp, data, len);return tmp;
}

五,netlink用户态和内核态交互过程

1,socket 通信主要有 2 个操作对象:server 端和 client 端

2,netlink_client - netlink_server 测试程序

user space接收kernel的广播消息,接收到消息后然后再发送消息到kernel。

test netlink client:

#include <sys/types.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>
#include <errno.h>
#include <stdbool.h>
#include <sys/epoll.h>#define MAX_EPOLL_EVENTS 64
#define NETLINK_T_MSG_LEN 2048
#define NETLINK_USER 31
#define MAX_PAYLOAD 1024 /* maximum payload size*/int sk_fd = -1;
int mPollHandler = -1;
struct nlmsghdr *nlh = NULL;int netlink_t_open_socket(int buf_sz, bool passcred) {struct sockaddr_nl addr;int on = passcred;int buf_sz_readback = 0;socklen_t optlen = sizeof(buf_sz_readback);int s;memset(&addr, 0x0, sizeof(addr));addr.nl_family = AF_NETLINK;addr.nl_pid = 0;addr.nl_groups = 0xffffffff;//s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_USER);if (s < 0) return -1;if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)) < 0 ||getsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz_readback, &optlen) < 0) {close(s);return -1;}/* Only if SO_RCVBUF was not effective, try SO_RCVBUFFORCE. Generally, we* want to avoid SO_RCVBUFFORCE, because it generates SELinux denials in* case we don't have CAP_NET_ADMIN. This is the case, for example, for* healthd. */if (buf_sz_readback < 2 * buf_sz) {if (setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz)) < 0) {close(s);return -1;}}setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {close(s);return -1;}return s;
}ssize_t netlink_t_kernel_recv(int socket, void* buffer, size_t length, bool require_group, uid_t* uid) {struct iovec iov = {buffer, length};struct sockaddr_nl addr;char control[CMSG_SPACE(sizeof(struct ucred))];struct msghdr hdr = {&addr, sizeof(addr), &iov, 1, control, sizeof(control), 0,};struct ucred* cred;*uid = -1;ssize_t n = TEMP_FAILURE_RETRY(recvmsg(socket, &hdr, 0));if (n <= 0) {return n;}struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {/* ignoring netlink message with no sender credentials */goto out;}cred = (struct ucred*)CMSG_DATA(cmsg);*uid = cred->uid;if (addr.nl_pid != 0) {/* ignore non-kernel */goto out;}if (require_group && addr.nl_groups == 0) {/* ignore unicast messages when requested */goto out;}return n;out:/* clear residual potentially malicious data *///bzero(buffer, length);memset(buffer, 0 , length);errno = EIO;return -1;
}static void signal_handler(int signum)
{if (sk_fd > 0) {close(sk_fd);}if (nlh != NULL) {free(nlh);nlh = NULL;}epoll_ctl(mPollHandler, EPOLL_CTL_DEL, sk_fd, NULL);exit(EXIT_SUCCESS);
}int main(int args, char *argv[])
{struct epoll_event ev;int i;char msg[NETLINK_T_MSG_LEN + 2];uid_t uid = -1;int n;//char *cp;int ret;struct msghdr msg_info;  //msghdr includes: struct iovec *   msg_iov;struct sockaddr_nl dest_addr;struct iovec iov;int nevents;struct epoll_event events[MAX_EPOLL_EVENTS];signal(SIGINT, &signal_handler);signal(SIGTERM, &signal_handler);mPollHandler = epoll_create(MAX_EPOLL_EVENTS);if (mPollHandler == -1) {printf("Failed to initialize Moto event looper\n");return false;}sk_fd = netlink_t_open_socket(64*1024, true);if (sk_fd < 0) {printf("fail to open netlink socket fd\n");return false;}//dest addrmemset(&dest_addr, 0, sizeof(dest_addr));dest_addr.nl_family = AF_NETLINK;dest_addr.nl_pid = 0;       /* For Linux Kernel */dest_addr.nl_groups = 0;    /* unicast *///init send msgnlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);nlh->nlmsg_pid = getpid();  //self pidnlh->nlmsg_flags = 0;strcpy(NLMSG_DATA(nlh), "Hello this is a msg from userspace");   //put "Hello" into nlhiov.iov_base = (void *)nlh;         //iov -> nlhiov.iov_len = nlh->nlmsg_len;msg_info.msg_name = (void *)&dest_addr;  //msg_name is Socket name: destmsg_info.msg_namelen = sizeof(dest_addr);msg_info.msg_iov = &iov;                 //msg -> iovmsg_info.msg_iovlen = 1;ev.events = EPOLLIN | EPOLLPRI;ev.data.fd = sk_fd;if (epoll_ctl(mPollHandler, EPOLL_CTL_ADD, sk_fd, &ev) == -1) {close(sk_fd);printf("Failed to add epoll data\n");return false;}while (1) {nevents = epoll_wait(mPollHandler, events, MAX_EPOLL_EVENTS, -1);if (nevents == -1) {if (errno != EINTR)printf("epoll wait failed %d\n", errno);continue;}//loop readfor (i = 0; i < nevents; ++i) {int fd = events[i].data.fd;n = netlink_t_kernel_recv(fd, msg, NETLINK_T_MSG_LEN, true, &uid);if (n < 0) {printf("epoll callback read error %d\n", errno);}msg[n] = '\0';msg[n + 1] = '\0';printf("epoll callback read %d bytes, %s\n", n, msg);//send msgret = sendmsg(fd, &msg_info, 0);printf("send ret: %d\n", ret);//cp = msg;//while (*cp) {//    printf("get netlink msg %s\n", cp);//    while(*cp++)//        ;//}}}return 0;
}

test netlink server:

#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/platform_device.h>
#include <linux/of.h>#define NETLINK_USER 31     //the user defined channel, the key factor
struct sock *nl_sk = NULL;
struct sk_buff *skb = NULL;static ssize_t test_netlink_send_msg_store(struct device *dev,struct device_attribute *attr,const char *buf,size_t count)
{const char *action_string = "netlink msg from kernel ";const char *msg_info = "author william";size_t len;//char *scratch;struct netlink_skb_parms *parms;if (!buf || count <= 0)return -EINVAL;//allocate sk buff and filllen = strlen(action_string) + strlen(msg_info) + 2;skb = alloc_skb(len, GFP_KERNEL);if (!skb)return count;//scratch = skb_put(skb, len);//sprintf(scratch, "%s", action_string);skb_put_data(skb, action_string, strlen(action_string));skb_put_data(skb, msg_info, strlen(msg_info));parms = &NETLINK_CB(skb);parms->creds.uid = GLOBAL_ROOT_UID;parms->creds.gid = GLOBAL_ROOT_GID;parms->dst_group = 1;parms->portid = 0;netlink_broadcast(nl_sk, skb_get(skb), 0 , 1, GFP_KERNEL);consume_skb(skb);return count;
}/*
* ATTRIBUTES:
*
*/
static DEVICE_ATTR(send_msg, S_IWUSR | S_IRUGO,NULL,test_netlink_send_msg_store);static struct attribute *test_netlink_attrs[] = {&dev_attr_send_msg.attr,NULL,
};
ATTRIBUTE_GROUPS(test_netlink);static const struct of_device_id test_netlink_of_match[] = {{ .compatible = "test-netlink", },{ },
};
MODULE_DEVICE_TABLE(of, test_netlink_of_match);static void test_netlink_recv_msg(struct sk_buff *skb)
{struct nlmsghdr *nlh;//for receiving...nlh = (struct nlmsghdr*)skb->data;printk("Netlink received msg payload: %s\n",(char*)nlmsg_data(nlh));
}static int test_netlink_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct netlink_kernel_cfg cfg = {.input = test_netlink_recv_msg,};dev_info(dev, "%s\n", __func__);//allocate netlink socketnl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);if(!nl_sk){dev_err(dev, "%s, Error creating socket.\n", __func__);return -1;}//if (!netlink_has_listeners(nl_sk, 1))//  return -1;return 0;
}static struct platform_driver netlink_device_driver = {.probe      = test_netlink_probe,.driver     = {.name   = "test-netlink",.of_match_table = test_netlink_of_match,.dev_groups = test_netlink_groups,}
};static int __init test_netlink_init(void)
{return platform_driver_register(&netlink_device_driver);
}static void __exit test_netlink_exit(void)
{netlink_kernel_release(nl_sk);platform_driver_unregister(&netlink_device_driver);
}module_init(test_netlink_init);
module_exit(test_netlink_exit);
MODULE_LICENSE("GPL");

测试:

netlink client

netlink server

/sys/devices/platform/soc/soc:m_netlink_server # echo 1 > send_msg

参考链接:

内核通信之 Netlink 源码分析和实例分析-腾讯云开发者社区-腾讯云

Linux驱动-Netlink通信_linux netlink-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/727689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的创作周年纪念日

机缘 最初成为创作者的初心&#xff1a;整理自己的知识体系&#xff0c;普及前端知识 实战项目中的经验分享日常工作学习过程中的记录通过文章进行技术交流归纳和整理自己的知识体系 收获 创作的过程中收获&#xff1a; 获得了909粉丝的关注获得了很多正向的反馈&#xff0c…

【C语言】终の指针(前篇)

个人主页点这里~ 指针初阶点这里~ 指针初阶2.0点这里~ 指针进阶点这里~ 终の指针 一、回调函数二、qsort函数1、整形比较2、结构数据比较①结构体②-> 的使用③结构数据比较 一、回调函数 回调函数就是⼀个通过函数指针调用的函数。 把一个函数的指针作为参数传递给另一…

dubbo3适配springboot2.7.3

版本详细 <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId><version>3.0.3</version> </dependency><parent><groupId>org.springframework.boot</groupId><artifactId&…

mysql数据库入门到精通-Windows版本mysql安装(1)

文章目录 一、数据库介绍1.1、数据库概念1.2、为什么要使用数据库1.3、关系型数据库与非关系型数据库1.4、数据库术语1.5、mysql下载及地址 二、安装mysql数据库三、mysql的管理工具3.1、mysql command line client使用 四、SQL结构化查询语言4.1、SQL概述4.2、SQL发展4.3、SQL…

水牛社:专为创业者和网赚小白精心打造的助手

近几年来&#xff0c;经济发展快是快&#xff0c;物价也在蹭蹭往上涨&#xff0c;但工资却不见明显提升&#xff0c;随着生活成本的增加&#xff0c;单单靠工资&#xff0c;已经很难维持生活开支&#xff0c;加之疫情的影响&#xff0c;很多行业发展不景气&#xff0c;一些岗位…

医学大数据|统计基础|医学统计学(笔记):开学说明与目录

开始学习统计基础&#xff0c;参考教材&#xff1a;医学统计学第五版 点点关注一切来学习吧 责任编辑&#xff1a;医学大数据刘刘老师&#xff1a;头部医疗大数据公司医学科学部研究员 邮箱&#xff1a;897282268qq.com 久菜盒子工作室 我们是&#xff1a;985硕博/美国全奖…

python 输入和输出

在 Python 中&#xff0c;输入和输出是最基本的操作之一。你可以使用内置函数 input() 来获取用户输入&#xff0c;使用 print() 函数来输出信息到控制台。 输入&#xff08;Input&#xff09; input() 函数用于从用户那里获取输入。这个函数会将用户的输入作为字符串返回。 示…

安装sqlserver2022最新版只能使用.\SQLEXPRESS登录数据库怎么修改成.

.\SQLEXPRESS “服务器名称 localhost\SQLEXPRESS”中的 “SQLEXPRESS”就是数据库的实例名称/数据库名/服务器名&#xff0c; “localhost”即登录本计算机安装的数据库 安装sqlserver2022最新版只能使用.\SQLEXPRESS登录数据库怎么修改成. 2、查看SQL Server数据库的实例名…

2024年腾讯云学生服务器活动详细说明、学生机购买流程

2024年腾讯云学生服务器优惠活动「云校园」&#xff0c;学生服务器优惠价格&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&#xff0c;CVM云服务器2核4G配置842.4元一年&…

【操作系统概念】 第7章:死锁

文章目录 0.前言7.1 系统模型7.2 死锁特征7.2.1 必要条件7.2.2 资源分配图 7.3 死锁处理方法7.4 死锁预防&#xff08;deadlock prevention&#xff09;7.4.1 互斥7.4.2 占有并等待7.4.3 非抢占7.4.4 循环等待 7.5 死锁避免&#xff08;deadlock-avoidance&#xff09;7.5.1 安…

b站小土堆pytorch学习记录—— P25-P26 网络模型的使用和修改、保存和读取

文章目录 一、修改1.方法2.代码 二、保存和读取1.方法2.代码&#xff08;1&#xff09;保存&#xff08;2&#xff09;加载 3.陷阱 一、修改 1.方法 add_module(name: str, module: Module) -> None name 是要添加的子模块的名称。 module 是要添加的子模块。 调用 add_m…

练习 6 Web [极客大挑战 2019]HardSQL

[极客大挑战 2019]HardSQL 先尝试登录&#xff0c;查看报错信息 admin 111 password 1111 登录失败admin 111 password 1’or’1 登录成功 这里直接试了万能密码成功&#xff0c;复习一下&#xff0c;第一个 ’ 是为了闭合前面的sql语句&#xff0c;最后的1后面没有 ’ 是因为…

如何学习I2C协议

文章目录 学习I2C协议0 懒人直达1 了解协议开发者2 从恩智浦半导体公司下载官方技术文档3 翻译成中文4 资源下载 学习I2C协议 0 懒人直达 点击直达 1 了解协议开发者 I2C&#xff08;Inter-Integrated Circuit&#xff09;协议是由荷兰皇家飞利浦电子公司&#xff08;现恩智…

008-跨域

跨域 什么是跨域&#xff1f;非同源限制跨域解决方案CORSJSONP服务器进行第三方代理webscoketwindow.postMessage 什么是跨域&#xff1f; 同源策略&#xff1a;协议相同、域名相同、端口号相同 的两个页面被认为是同源。 由一个页面的 js 访问不同源的页面内容&#xff0c;被…

HTTP代理ip如何助旅游大数据领域?怎么去建立安全代理隧道连接?

HTTP代理IP在旅游大数据领域的应用主要体现在以下几个方面&#xff1a; 数据抓取&#xff1a;旅游大数据的获取往往需要从各种在线旅游平台、社交媒体、评论网站等抓取数据。使用HTTP代理IP可以在抓取过程中隐藏真实IP&#xff0c;避免被目标网站封锁&#xff0c;从而持续、稳定…

LeetCode 刷题 [C++] 第300题.最长递增子序列

题目描述 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 题目…

CTP-API开发系列之五:SimNow环境介绍

CTP-API开发系列之五&#xff1a;SimNow环境介绍 CTP-API开发系列之五&#xff1a;SimNow环境介绍SimNow模拟测试环境第一套第二套登录关键字段可视化终端常见问题 CTP-API开发系列之五&#xff1a;SimNow环境介绍 如果你要研发一套国内期货程序化交易系统&#xff0c;从模拟测…

设计模式在芯片验证中的应用——备忘录

1. 前言 软件设计模式定义了一组类和它们之间的关系&#xff0c;它们相互作用用以解决软件开发过程中面临的常见问题。由于验证工程师所做工作的重要部分包括使用面向对象语言(如SystemVerilog)进行编码&#xff0c;因此许多遇到的挑战都适合应用特定的设计模式来解决。将它们…

SCCM部署时遇到的问题:无法连接到SQL Server

根据提示信息逐一排除以下问题&#xff1a; 1、确保SQL服务器名称是否正确。 2、确保TCP1433和4022端口有没有被防火墙屏蔽。 3、站点服务器帐号加入SQLServer的sysadmin角色成员里 、确保SQL实例没有使用动态端口&#xff0c;可参考&#xff1a; Configure SQL Server to…

贪心 Leetcode 763 划分字母区间

划分字母区间 Leetcode 763 学习记录自代码随想录 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍然是 s 。 返…