学峰的学习笔记 2015-01-11
Linux下如何实现用户态与内核态的交互呢?一种是上文讲的/proc文件,还有一种是netlink套接字机制,netlink实现了用户空间与内核空间双向通信方法。
netlink用户态API与常见的socket编程一致,只是内核态要实现自定义protocol。
一.内核态模块
该模块用于接收用户态信息,并发送一字符串响应。
#ifndef __KERNEL__
#define __KERNEL__
#endif /* __KERNEL__ */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/uaccess.h>
#include <net/sock.h>
#include <net/netlink.h> // netlink
#include <linux/string.h>
#include <linux/ip.h>
#define NETLINK_TEST 31 // 自定义用户协议
static struct sock *nlfd = NULL; // 内核socket文件描述符
static char *payload = "Hello user,i'm from kernel!";
// 向用户层发送消息
static int sendNLMsg(int pid,void *msg,int len)
{
struct sk_buff *skb;
int size,count;
struct nlmsghdr *nlmsgh = NULL;
char *pos = NULL;
int retval = 0;
size = NLMSG_SPACE(len); // 加消息头部长度
skb = alloc_skb(size,GFP_KERNEL); // 申请空间
if(!skb)
{
retval = -1;
return retval;
}
nlmsgh = nlmsg_put(skb,0,0,0,len,0);
// 填充数据
pos = NLMSG_DATA(nlmsgh);
memset(pos,0,len);
memcpy(pos,msg,len);
NETLINK_CB(skb).dst_group = 0; // 单播
count = netlink_unicast(nlfd,skb,pid,MSG_DONTWAIT);
printk(KERN_ALERT "pid:%d send:%d\n",pid,count);
return retval;
}
// 处理用户层传递的消息
static void handle_msg(struct sk_buff *_sk)
{
struct sk_buff *skb;
struct nlmsghdr *nlh = NULL;
char str[100] = {};
int pid;
printk("==>handle_msg\n");
skb = skb_get(_sk); // 引用当前_sk
if(skb->len >= NLMSG_SPACE(0))
{
nlh = nlmsg_hdr(skb); // 获得信息头部
pid = nlh->nlmsg_pid; // 获取用户进程pid
memcpy(str,NLMSG_DATA(nlh),100);
printk(KERN_ERR "recv:%s\n",str);
sendNLMsg(pid,payload,strlen(payload)); // 向用户层发送数据
kfree_skb(skb);
}
printk("<==handle_msg\n");
}
// 建立netlink套接字
int NLCreate(void)
{
// 消息回调函数为handle_msg,注:参数1区别以往内核版本API,设为init_net
nlfd = netlink_kernel_create(&init_net,NETLINK_TEST,1,handle_msg,NULL,THIS_MODULE);
if(!nlfd)
{
return -1;
}
return 0;
}
// 清除netlink套接字
int NLDestroy(void)
{
if(nlfd)
{
sock_release(nlfd->sk_socket);
}
return 0;
}
static int __init netlink_init(void)
{
NLCreate();
return 0;
}
static void __exit netlink_exit(void)
{
NLDestroy();
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kettas");
MODULE_DESCRIPTION("Netlink Test Demo");
MODULE_VERSION("1.0.1");
MODULE_ALIAS("Netlink 01");二.用户态
该应用向内核模块发送信息,并接收来自内核响应字符串。
#include <unistd.h>
#include <stdio.h>
#include <linux/types.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <assert.h>
#include <stdlib.h>
#define NETLINK_TEST 31
#define MAX_NL_MSG_LEN 1024
typedef struct _packet_u
{
struct nlmsghdr hdr;
char payload[1024];
}packet_u;
static int nls; // socket文件描述符
// 向内核发送消息
int sendtokernel(char *buf,int len,int type)
{
struct nlmsghdr nlmsg;
struct sockaddr_nl nldest = {};
int size;
nldest.nl_family = AF_NETLINK;
nldest.nl_pid = 0;
nldest.nl_groups = 0;
// 填充netlink消息头
nlmsg.nlmsg_len = NLMSG_LENGTH(len);
nlmsg.nlmsg_pid = getpid();
nlmsg.nlmsg_flags = 0;
nlmsg.nlmsg_type = type;
// 填充负载
memcpy(NLMSG_DATA(&nlmsg),buf,len);
// 发送
size = sendto(nls,&nlmsg,nlmsg.nlmsg_len,0,(struct sockaddr*)&nldest,sizeof(nldest));
return size;
}
// 接收内核消息
int recvfromkernel(void)
{
int size = 0;
// 方法一:调用recvfrom方法接收内核数据,注此时message结构体包含有消息体
/*
packet_u message;
struct sockaddr_nl nldest = {};
int len = sizeof(nldest);
memset(&message,0,sizeof(message));
nldest.nl_family = AF_NETLINK;
nldest.nl_pid = 0;
nldest.nl_groups = 0;
// 接收消息
size = recvfrom(nls,
&message,
sizeof(message),
0,
(struct sockaddr*)&nldest,
&len);
printf("size:%d recv:%s\n",size,message.payload); // NLMSG_DATA()与message.payload结果一致
*/
// 方法二:调用recvmsg方法
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;
size = recvmsg(nls, &msg, 0);
printf("size:%d recv:%s\n",size,(char*)NLMSG_DATA(nlhdr));
return size;
}
int main(int argc,char **argv)
{
struct sockaddr_nl nlsource;
int ret;
// socket
nls = socket(PF_NETLINK,SOCK_RAW,NETLINK_TEST);
assert(nls!=-1);
memset(&nlsource,0,sizeof(struct sockaddr_nl));
nlsource.nl_family = AF_NETLINK;
nlsource.nl_pid = getpid();
nlsource.nl_groups = 0;
// bind
ret = bind(nls,(struct sockaddr*)&nlsource,sizeof(nlsource));
assert(ret!=-1);
// send
char *str = "Hello kernel,i'm from user";
sendtokernel(str,strlen(str),0);
// recv
recvfromkernel();
close(nls);
return 0;
}三.测试运行
[scada@linux netlink]$ sudo insmod netlink_test.ko [scada@linux netlink]$ ./netlink_u size:44 recv:Hello user,i'm from kernel! [scada@linux netlink]$ dmesg ==>handle_msg recv:Hello kernel,i'm from user pid:40122 send:44 <==handle_msg