原帖地址:http://tcspecial.iteye.com/blog/2174781
Linux下如何实现用户态与内核态的交互呢?一种是上文讲的/proc文件,还有一种是netlink套接字机制,netlink实现了用户空间与内核空间双向通信方法。
netlink用户态API与常见的socket编程一致,只是内核态要实现自定义protocol。
一.内核态模块
该模块用于接收用户态信息,并发送一字符串响应。
- #ifndef __KERNEL__
- #define __KERNEL__
- #endif /* __KERNEL__ */
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include // netlink
- #include
- #include
-
- #define NETLINK_TEST 31 // 自定义用户协议
-
- static struct sock *nlfd = NULL;
- 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);
-
- if(skb->len >= NLMSG_SPACE(0))
- {
- nlh = nlmsg_hdr(skb);
- pid = nlh->nlmsg_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");
- }
-
-
- int NLCreate(void)
- {
-
- nlfd = netlink_kernel_create(&init_net,NETLINK_TEST,1,handle_msg,NULL,THIS_MODULE);
- if(!nlfd)
- {
- return -1;
- }
-
- return 0;
- }
-
-
- 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");
#ifndef __KERNEL__
#define __KERNEL__
#endif /* __KERNEL__ */
#include
#include
#include
#include
#include
#include
#include
#include // netlink
#include
#include
#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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
-
- #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;
-
-
- 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;
-
-
- 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;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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;
-
-
- 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;
-
-
- ret = bind(nls,(struct sockaddr*)&nlsource,sizeof(nlsource));
- assert(ret!=-1);
-
-
- char *str = "Hello kernel,i'm from user";
- sendtokernel(str,strlen(str),0);
-
-
- recvfromkernel();
-
- close(nls);
- return 0;
- }
#include
#include
#include
#include
#include
#include
#include
#include
#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