目录
简介
用户空间与内核的交互方式,使用copy_from_user(), copy_to_user().
除了这两种交互方式,内核还提供了其他高级的方式,对于写驱动来说很重要。有proc、sysfs、debugfs、netlink、ioctl。
本文学习netlink
一、netlink soket
优点
- 双向同步传输数据;
- 高速数据传输,特别是在本地主机上;
- 异步通信;消息可以排队,因此发送方不必等待接收方;
- 在其他用户到内核通信方法(如procfs、sysfs、debugfs和ioctl)中,用户空间程序必须开始传输到内核空间的;而使用netlink套接字,内核可以起始传输到用户空间;
- procfs、sysfs、debugfs中可能引起域名污染;对于netlink套接字,情况并非如此,因为没有文件。
二、用户空间
1、API
socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO)
2、编程流程
- 创建netlink;socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO);域名 PF_NETLINK,类型SOCK_RAW,协议 NETLINK_MY_UNIT_PROTO
- 初始化netlink源的sockerddr; bind socket
- 初始化netlink目的地址结构。目的地址设置成0表示是内核;
- 初始化netlink的头数据结构,指定源PID,数据负载;
- 初始化iovec结构,指向引用netlink头,初始化msghdr 数据结构,指向目的地址和iov
- 数据的发送sendmsg,数据的接收recvmsg
3、源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define NETLINK_MY_UNIT_PROTO 31
#define NLSPACE 1024
//数据
static const char *thedata = "hello world";
int main(int argc, char **argv)
{
int sd;
struct sockaddr_nl src_nl, dest_nl;
struct nlmsghdr *nlhdr; // 'carries' the payload
struct iovec iov;
struct msghdr msg;
ssize_t nsent, nrecv;
//、创建节点,域名PF_NETLINK,类型SOCK_RAW,协议NETLINK_MY_UNIT_PROTO
sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_MY_UNIT_PROTO);
if (sd < 0) {
exit(EXIT_FAILURE);
}
//2、设置netlink源地址结构体已经绑定
memset(&src_nl, 0, sizeof(src_nl));
src_nl.nl_family = AF_NETLINK;
src_nl.nl_pid = getpid();
src_nl.nl_groups = 0x0; // no multicast
if (bind(sd, (struct sockaddr *)&src_nl, sizeof(src_nl)) < 0) {
exit(EXIT_FAILURE);
}
/* 3. 设置目的地址结构体*/
memset(&dest_nl, 0, sizeof(dest_nl));
dest_nl.nl_family = AF_NETLINK;
dest_nl.nl_groups = 0x0; // no multicast
dest_nl.nl_pid = 0; // destined for the kernel
/* 4. 分配netlink头内存*/
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(NLSPACE));
if (!nlhdr) {
exit(EXIT_FAILURE);
}
memset(nlhdr, 0, NLMSG_SPACE(NLSPACE));
nlhdr->nlmsg_len = NLMSG_SPACE(NLSPACE);
nlhdr->nlmsg_pid = getpid();
/* 设置payload thedata是待发送的数据*/
strncpy(NLMSG_DATA(nlhdr), thedata, strnlen(thedata, NLSPACE)+1);
/* 5.设置iovec*/
memset(&iov, 0, sizeof(struct iovec));
iov.iov_base = (void *)nlhdr;//上面申请的空间
iov.iov_len = nlhdr->nlmsg_len;
/* 设置消息头结构体 */
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&dest_nl; // 目的地址
msg.msg_namelen = sizeof(dest_nl); // 目的地址大学
msg.msg_iov = &iov;//
msg.msg_iovlen = 1; // # elements in msg_iov
/* 6. sendmsg */
nsent = sendmsg(sd, &msg, 0);
if (nsent < 0) {
free(nlhdr);
exit(EXIT_FAILURE);
} else if (nsent == 0) {
free(nlhdr);
exit(EXIT_FAILURE);
}
printf("%s:sendmsg(): *** success, sent %ld bytes all-inclusive\n"
" (see kernel log for dtl)\n", argv[0], nsent);
fflush(stdout);
/* 7. 阻塞接收数据*/
printf("%s: now blocking on kernel netlink msg via recvmsg() ...\n", argv[0]);
nrecv = recvmsg(sd, &msg, 0);
if (nrecv < 0) {
free(nlhdr);
exit(EXIT_FAILURE);
}
printf("%s:recvmsg(): *** success, received %ld bytes:"
"\nmsg from kernel netlink: \"%s\"\n",
argv[0], nrecv, (char *)NLMSG_DATA(nlhdr));
free(nlhdr);
close(sd);
exit(EXIT_SUCCESS);
}
三、内核编程
1、API
struct sock * netlink_kernel_create(struct net *, int , struct netlink_kernel_cfg *);
2、内核空间编程流程
- netlink_kernel_create 创建netlink
- nlmsg_new()分配netlink内存,nlmsg_put()添加netlink信息到分配的内存;借助nlmsg_data()
拷贝数据; - nlmsg_unicast()发送内核数据到用户空间;
- netlink_kernel_release() 释放netlink
3、内核源码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
MODULE_AUTHOR("wy");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");
//文件名
#define OURMODNAME "netlink_simple_intf"
#define NETLINK_MY_UNIT_PROTO 31
#define NLSPACE 1024
static struct sock *nlsock;
//收到数据,触发回调函数。
static void netlink_recv_and_reply(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
struct sk_buff *skb_tx;
//返回的数据
char *reply = "Reply from kernel netlink";
int pid, msgsz, stat;
//发生在进程上下文,而不是中断上下文
PRINT_CTX();
nlh = (struct nlmsghdr *)skb->data;
pid = nlh->nlmsg_pid; //发送进程的PID
pr_info("received from PID %d:\n" "\"%s\"\n", pid, (char *)NLMSG_DATA(nlh));
//返回数据长度
msgsz = strnlen(reply, NLSPACE);
//新netlink申请
skb_tx = nlmsg_new(msgsz, 0);
if (!skb_tx) {
pr_warn("skb alloc failed!\n");
return;
}
// 设置 payload
nlh = nlmsg_put(skb_tx, 0, 0, NLMSG_DONE, msgsz, 0);
NETLINK_CB(skb_tx).dst_group = 0;
//将发送的数据reply,使用nlmsg_data拷贝到netlink
strncpy(nlmsg_data(nlh), reply, msgsz);
// 发送
stat = nlmsg_unicast(nlsock, skb_tx, pid);
if (stat < 0)
pr_warn("nlmsg_unicast() failed (err=%d)\n", stat);
pr_info("reply sent\n");
}
//当用户空间向kernel发数据,将触发此回调函数
static struct netlink_kernel_cfg nl_kernel_cfg = {
.input = netlink_recv_and_reply,
};
static int __init netlink_simple_intf_init(void)
{
//创建netlink
nlsock = netlink_kernel_create(&init_net, NETLINK_MY_UNIT_PROTO,&nl_kernel_cfg);
if (!nlsock) {
pr_warn("netlink_kernel_create failed\n");
return PTR_ERR(nlsock);
}
pr_info("inserted\n");
return 0; /* success */
}
//模块退出清理
static void __exit netlink_simple_intf_exit(void)
{
netlink_kernel_release(nlsock);
pr_info("removed\n");
}
module_init(netlink_simple_intf_init);
module_exit(netlink_simple_intf_exit);
四、串口输出
kernel
[ 310.858620] netlink_simple_intf: loading out-of-tree module taints kernel.
[ 310.858650] netlink_simple_intf: module verification failed: signature and/or required key missing - tainting kernel
[ 310.860505] netlink_simple_intf:netlink_simple_intf_init(): creating kernel netlink socket
[ 310.860510] netlink_simple_intf:netlink_simple_intf_init(): inserted
[ 320.874385] netlink_simple_intf:netlink_recv_and_reply(): 005) netlink_userapp :3730 | ...0 /* netlink_recv_and_reply() */
[ 320.874391] netlink_simple_intf:netlink_recv_and_reply(): received from PID 3730:
"sample user data to send to kernel via netlink"
[ 320.874394] netlink_simple_intf:netlink_recv_and_reply(): reply sent
app
./netlink_userapp:PID 3730: netlink socket created
./netlink_userapp: bind done
./netlink_userapp: destination struct, netlink hdr, payload setup
./netlink_userapp: initialized iov structure (nl header folded in)
./netlink_userapp: initialized msghdr structure (iov folded in)
./netlink_userapp:sendmsg(): *** success, sent 1040 bytes all-inclusive
(see kernel log for dtl)
./netlink_userapp: now blocking on kernel netlink msg via recvmsg() ...
./netlink_userapp:recvmsg(): *** success, received 44 bytes:
msg from kernel netlink: "Reply from kernel netlink"