本文主要使用netlink套接字实现中断环境与用户态进程通信。

系统环境:基于linux 2.6.32.27 和 linux 3.16.36

Linux内核态和用户态进程通信方法的提出和实现

用户上下文环境

运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和Unix域套接字来实现内核态和用户态的通信。但这些的数据传输效率较低,linux内核提供copy_from_user() 和 copy_to_user() 函数来实现内核态与用户态数据的拷贝,但这两个函数会引发阻塞,所以不能用在硬、软中断中。一般将这两个特殊拷贝函数用在类似系统调用一类的函数中,如图,

其中相关的系统调用是需要用户自行编写并载入内核。

硬、软中断环境

硬中断和软中断环境与用户态进程无丝毫关系,而且运行过程不能阻塞。

软中断、硬中断有一套同步机制 — 自旋锁(spinlock),可以通过自旋锁来实现中断环境和中断环境,中断环境与内核线程的同步,而内核线程是运行在有进程上下文环境中的,这样便可以在内核线程中使用套接字或消息队列来取得用户空间的数据,然后再将数据通过临界区传递给中断过程,如图

因为中断过程不可能无休止地等待用户态进程发送数据,所以要通过一个内核线程来接收用户空间的数据,再通过临界区传给中断过程。中断过程向用户空间的数据发送必须是无阻塞的。这样的通信模型并不令人满意,因为内核线程是和其他用户态进程竞争cpu接收数据的,效率很低,这样中断过程便不能实时地接收来自用户空间的数据。

netlink套接字的通信依据是一个对应于进程的标识,一般定义为该进程的ID。当通信的一端处于中断过程时,该标识为0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则不同。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函数。工作原理如图

很明显,这里使用了软中断而不是内核线程来接收数据,这样就可以保证数据接收的实时性。

当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同。如图

/**

*imp2.h

*/

#ifndef __IMP2_H__

#define __IMP2_H__

#define IMP2_OPS_BASIC 128

#define IMP2_SET IMP2_OPS_BASIC

#define IMP2_GET IMP2_OPS_BASIC

#define IMP2_MAX (IMP2_OPS_BASIC + 1)

#define IMP2_U_PID 0

#define IMP2_K_MSG 1

#define IMP2_CLOSE 2

#define NL_IMP2 31

struct packet_info

{

__u32 src;

__u32 dest;

};

#endif

/**

*imp2_u.c

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "imp2.h"

struct msg_to_kernel

{

struct nlmsghdr hdr;

};

struct u_packet_info

{

struct nlmsghdr hdr;

struct packet_info icmp_info;

};

static int skfd;

static void sig_int(int signo)

{

struct sockaddr_nl kpeer;

struct msg_to_kernel message;

memset(&kpeer,0,sizeof(kpeer));

kpeer.nl_family = AF_NETLINK;

kpeer.nl_pid = 0;

kpeer.nl_groups = 0;

memset(&message,0,sizeof(message));

message.hdr.nlmsg_len = NLMSG_LENGTH(0);

message.hdr.nlmsg_flags = 0;

message.hdr.nlmsg_type = IMP2_CLOSE;

message.hdr.nlmsg_pid = getpid();

sendto(skfd,&message,message.hdr.nlmsg_len,0,(struct sockaddr *)(&kpeer),sizeof(kpeer));

close(skfd);

exit(0);

}

int main(void)

{

/* 本地的 */

struct sockaddr_nl local;

/* 连线kernel的 */

struct sockaddr_nl kpeer;

int kpeerlen;

struct msg_to_kernel message;

struct u_packet_info info;

int sendlen = 0;

int rcvlen = 0;

struct in_addr addr;

skfd = socket(AF_NETLINK,SOCK_RAW,NL_IMP2);

if(skfd < 0) {

printf("cannot create a netlink socket\n");

exit(0);

}

memset(&local,0,sizeof(local));

local.nl_family = AF_NETLINK;

local.nl_pid = getpid();

local.nl_groups = 0;

if(bind(skfd,(struct sockaddr *)&local,sizeof(local)) != 0) {

printf("bind() error\n");

return -1;

}

signal(SIGINT,sig_int);

memset(&kpeer,0,sizeof(kpeer));

kpeer.nl_family = AF_NETLINK;

kpeer.nl_pid = 0;

kpeer.nl_groups = 0;

memset(&message,0,sizeof(message));

message.hdr.nlmsg_len = NLMSG_LENGTH(0);

message.hdr.nlmsg_flags = 0;

message.hdr.nlmsg_type = IMP2_U_PID;

message.hdr.nlmsg_pid = local.nl_pid;

sendto(skfd,&message,message.hdr.nlmsg_len,0,(struct sockaddr *)&kpeer,sizeof(kpeer));

while(1) {

kpeerlen = sizeof(struct sockaddr_nl);

rcvlen = recvfrom(skfd,&info,sizeof(struct u_packet_info),0,(struct sockaddr *)&kpeer,&kpeerlen);

addr.s_addr = info.icmp_info.src;

printf("src:%s,",inet_ntoa(addr));

addr.s_addr = info.icmp_info.dest;

printf("dest:%s\n",inet_ntoa(addr));

}

return 0;

}

/**

* imp2_k.c //兼容linux 2.6.32 和 linux 3.16.36

*/

#ifndef __KERNEL__

#define __KERNEL__

#endif

#ifndef MODULE

#define MODULE

#endif

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "imp2.h"

#define NF_IP_PRE_ROUTING 0

/* 实现从netfilter的NF_IP_PRE_ROUTING点截获的ICMP数据包,

再将数据包的相关信息传递到一个用户态进程 */

static struct sock *nlfd;

//是竞争的资源

struct {

__u32 pid;

rwlock_t lock;

}user_proc;

//相当于np_log

static int send_to_user(struct packet_info *info)

{

int ret = 0;

int size = 0;

unsigned char *old_tail = NULL;

struct sk_buff *skb = NULL;

struct nlmsghdr *nlh = NULL;

struct packet_info *packet = NULL;

printk("%s,begin ....\n",__FUNCTION__);

size = NLMSG_SPACE(sizeof(*info));

//分配一块skb(数据缓存区和skb描述符),大小,GFP自动分配

skb = alloc_skb(size,GFP_ATOMIC);

old_tail = skb->tail;

nlh = nlmsg_put(skb,0,0,IMP2_K_MSG,(size-sizeof(*nlh)),0);

packet = NLMSG_DATA(nlh);

memset(packet , 0 ,sizeof(struct packet_info));

packet->src = info->src;

packet->dest = info->dest;

nlh->nlmsg_len = skb->tail - old_tail;

NETLINK_CB(skb).dst_group = 0;

read_lock_bh(&user_proc.lock);

ret = netlink_unicast(nlfd,skb,user_proc.pid,MSG_DONTWAIT);

read_unlock_bh(&user_proc.lock);

printk("%s,end....\n",__FUNCTION__);

return ret;

nlmsg_failure:

if(skb) {

kfree_skb(skb);

}

return -1;

}

static unsigned int get_icmp(unsigned int hooknum,struct sk_buff *skb,

const struct net_device *in,const struct net_device *out,

int(*okfn)(struct sk_buff *))

{

//struct iphdr *iph = (*pskb)->nh.iph;

struct iphdr *iph = ip_hdr(skb); //2.6.24开始使用,因为struct sk_buff

struct packet_info info;

if(iph->protocol == IPPROTO_ICMP) {

read_lock_bh(&user_proc.lock);

if(user_proc.pid != 0) {

info.src = iph->saddr;

info.dest= iph->daddr;

//printk("%s,src = %u.%u,%u.%u,dst = %u,%u,%u,%u\n",__FUNCTION__,NIPQUAD(info.src), NIPQUAD(info.dest));

read_unlock_bh(&user_proc.lock);

send_to_user(&info);

} else {

//printk("%s, no user process runing....\n",__FUNCTION__);

read_unlock_bh(&user_proc.lock);

}

}

return NF_ACCEPT;

}

static struct nf_hook_ops imp2_ops =

{

.hook = get_icmp,

.pf = PF_INET,

.hooknum = NF_IP_PRE_ROUTING,

.priority = NF_IP_PRI_FILTER - 1,

.owner = THIS_MODULE,

};

static void kernel_receive(struct sk_buff *skb)

{

struct nlmsghdr *nlh = NULL;

int len = 0;

nlh = nlmsg_hdr(skb);

len = skb->len;

while(NLMSG_OK(nlh,len)) {

write_lock_bh(&user_proc.lock);

if(nlh->nlmsg_type == IMP2_U_PID) {

user_proc.pid = nlh->nlmsg_pid;

}

else if(nlh->nlmsg_type == IMP2_CLOSE && nlh->nlmsg_pid == user_proc.pid) {

user_proc.pid = 0;

}

write_unlock_bh(&user_proc.lock);

netlink_ack(skb,nlh,0);

nlh = NLMSG_NEXT(nlh,len);

}

}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)

struct netlink_kernel_cfg cfg =

{

.input = kernel_receive,

};

#endif

static int __init init(void)

{

rwlock_init(&user_proc.lock);

//这里的版本问题需要解决

/*在内核创建一个netlink socket ,

协议 NL_IMP2是自定义的,并指示由 kernel_receive接收数据*/

{

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)

nlfd = netlink_kernel_create(&init_net,NL_IMP2,&cfg);

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)

nlfd = netlink_kernel_create(&init_net ,NL_IMP2 , 0, kernel_receive, NULL, THIS_MODULE);

#else

nlfd = NULL;

#endif

}

if(!nlfd) {

printk("cannot create a netlink socket\n");

return -1;

}

return nf_register_hook(&imp2_ops);

}

static void __exit fini(void)

{

if(nlfd) {

netlink_kernel_release(nlfd);

}

nf_unregister_hook(&imp2_ops);

}

module_init(init);

module_exit(fini);

MODULE_LICENSE("GPL");

#Makefile

MODULE_NAME := imp2_k

obj-m := $(MODULE_NAME).o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

all:

$(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:

rm -fr *.ko

rm -fr *.o

rm -fr *.cmd sender $(MODULE_NAME).mod.c

.PHONY:clean aLL

netlink具体结构分析,可参考其他博文

http://blog.csdn.net/luckyapple1028/article/details/50839395

http://blog.csdn.net/luckyapple1028/article/details/50936563

Linux内核态之间进程通信,内核态和用户态通信(二)--实现相关推荐

  1. Linux内核态之间进程通信,Linux 系统内核空间与用户空间通信的实现与分析[转载]...

    [https://www.ibm.com/developerworks/cn/linux/l-netlink/index.html] 多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 ...

  2. Linux 2.6 下通过 ptrace 和 plt 实现用户态 API Hook

    (转载兼整理)Linux 2.6 下通过 ptrace 和 plt 实现用户态 API Hook 这厮此文写的相当实用,不知道为啥不好好整理一下,得,我代劳了吧.作者:l04m33@gmail.com ...

  3. (转载兼整理)Linux 2.6 下通过 ptrace 和 plt 实现用户态 API Hook

    这厮此文写的相当实用,不知道为啥不好好整理一下,得,我代劳了吧.作者:l04m33@gmail.com,原文.去看一眼就知道我干嘛干这个脏活儿了... 感觉这篇文章有上首页的素质,可惜不是我自己写的, ...

  4. 用户态和内核态之间的切换

    用户态和内核态之间的切换 切换方式 从用户态到内核态切换可以通过三种方式,或者说会导致从用户态切换到内核态的操作: 系统调用,这个上面已经讲解过了,在我公众号之前的文章也有讲解过.其实系统调用本身就是 ...

  5. Linux 内核态与用户态通信 netlink

    参考资料: https://blog.csdn.net/zqixiao_09/article/details/77131283 https://www.cnblogs.com/lopnor/p/615 ...

  6. Linux的init进程(内核态到用户态的变化)

    init进程,也就是内核启动3个进程中的进程1: init进程完成了从内核态向用户态的转变: (1)init进程是比较特殊,一个进程两个状态,init刚开始运行时是内核态,他属于内核线程,然后他自己运 ...

  7. java运行在用户态_理解Linux用户态和内核态

    Linux整体架构图 我们先来看一张Linux整体架构图. 系统调用 ​ 系统调用时操作系统的最小功能单位.根据不同的应用场景,不同的Linux发行版本提供的系统调用数量也不尽相同,大致在240-35 ...

  8. Linux 操作系统原理 — 内核态与用户态

    目录 文章目录 目录 Linux 的内核态与用户态 系统调用(System Call) Shell 用户态和内核态的切换 进程的用户空间和内核空间的内存布局 内核空间 用户空间 Linux 的内核态与 ...

  9. 【Linux 内核】Linux 内核体系架构 ( 硬件层面 | 内核空间 | 用户空间 | 内核态与用户态切换 | 系统调用 | 体系结构抽象层 )

    文章目录 一.Linux 内核体系架构 二.内核态与用户态切换 ( 系统调用层 ) 三.体系结构抽象层 一.Linux 内核体系架构 Linux 内核最初的源码不足一万行 , 当前的 Linux 内核 ...

最新文章

  1. TensorFlow中的语义分割套件
  2. NSIS修改开始菜单中图标
  3. 如何降低微服务测试成本?
  4. SAP Spartacus里的converter实例化逻辑
  5. ebtables之BROUTING和PREROUTING的redirect的区别
  6. 实例变量与局部变量的区别 java 1615135277
  7. contentsiz contentoffset contentInset的区别
  8. 那年我学过的Spring笔记
  9. Hibernate事务
  10. 下载——百度文库下载方法
  11. MySQL 约束语法
  12. 五、鼎捷T100生产管理之报工
  13. 怎么获取论文所在期刊的电子版封面及目录
  14. C# EXCEL的帮助类,仅使用NPOI,不用安装Office
  15. 基于Vue的数据埋点统计
  16. Stream Collectors - reducing
  17. 软件测试/测试开发丨学习Docker就应该掌握的dockerfile语法与指令
  18. 启动Jenkins时报错,localhost拒绝了我们的连接请求
  19. 声源定位之GCC-PHAT算法
  20. 【小罗的hdlbits刷题笔记2】补码运算中溢出的问题(Exams/ece241 2014 q1c)

热门文章

  1. 小孔子文章管理系统V2.0发布测试
  2. fastclick库的介绍和使用
  3. Effective Objective-C 2.0 初读小结
  4. git -- 练习的笔记
  5. 是什么时候开始学习gulp了
  6. SQL之用户自定义函数
  7. NodeManager启动流程与服务
  8. weblogic环境搭建
  9. Interactive Reflection Editing (SIGGRAPH ASIA 09)
  10. Sql Server临时表中插入标示列