基于NETLINK的内核与用户空间共享内存的实现

简介: 一、前言前些日子,开发中用到了netlink来实现内核与用户空间共享内存,写点笔记与大家分享。因为我对这块也不了解,写出来的东西一定存在很多错误,请大家批评指正~内核与用户空间共享内存的关键是,用户空间必须得知共享内存的起始地址,这就要求内核空间应该有一种通信机制来通知用户空间。已经有Godbach版主等人用proc文件系统实现了(可以google '共享内存 内核 用户空间'),很显然任何内核空间与用户空间的通信方法都可资利用。本文主要讲基于NETLINK机制的实现。二、NETLINK简介netlink在linux的内核与用户空间通信中用得很多(但具体例子我举不出,因为我不清楚~~

一、前言
前些日子,开发中用到了netlink来实现内核与用户空间共享内存,写点笔记与大家分享。因为我对这块也不了解,写出来的东西一定存在很多错误,请大家批评指正~
内核与用户空间共享内存的关键是,用户空间必须得知共享内存的起始地址,这就要求内核空间应该有一种通信机制来通知用户空间。已经有Godbach版主等人用proc文件系统实现了(可以google '共享内存 内核 用户空间'),很显然任何内核空间与用户空间的通信方法都可资利用。本文主要讲基于NETLINK机制的实现。

二、NETLINK简介
netlink在linux的内核与用户空间通信中用得很多(但具体例子我举不出,因为我不清楚~~请google之),其最大优势是接口与网络编程中的socket相似,且内核要主动发信息给用户空间很方便。
但通过实践,我发现netlink通信机制的最大弊病在于其在各内核版本中接口变化太大,让人难以适从(可从后文列出的源码中的kernel_receive的声明窥一斑)。
既然涉及到内核与用户空间两个空间,就应该在两个空间各有一套接口。用户空间的接口很简单,与一般的socket接口相似,内核空间则稍先复杂,但简单的应用只需简单地了解即可:首先也是建立描述符,建立描述符时会注册一个回调函数(源码中的kernel_receive即是),然后当用户空间有消息发过来时,我们的函数将被调用,显然在这个函数里我们可做相应的处理;当内核要主动发消息给用户进程时,直接调用一个类send函数即可(netlink_unicast系列函数)。当然这个过程中,有很多结构体变量需要填充。具体用法请google,我差不多忘光了~。

三、基于netlink的共享内存
这里的共享内存是指内核与用户空间,而不是通常的用户进程间的。
大概流程如下。
内核:__get_free__pages分配连续的物理内存页(貌似返回的其实是虚拟地址)-->SetPageReserved每一页(每一页都需这个操作,参见源码)-->如果用户空间通过netlink要求获取共享内存的起始物理地址,将__get_free__pages返回的地址__pa下发给用户空间。
用户空间:open "/dev/shm"(一个读写物理内存的设备,具体请google"linux读写物理内存")-->发netlink消息给内核,得到共享内存的起始物理地址-->mmap上步得到的物理地址。

四、源码说明
正如二中提到的,netlink接口在各版本中接口变化很大,本人懒惰及时间紧,只实验了比较新的内核2.6.25,源码如需移植到老版本上,需要一些改动,敬请原谅。
另外,由于本源码是从一个比较大的程序里抠出来的,所以命名什么的可能有点怪异~
源码包括shm_k.c(内核模块)和用户空间程序(shm_u.c)两部分。shm_k.c模块的工作是:分配8KB内存供共享,并把前几十个字节置为“hello, use share memory with netlink"字样。shm_u.c工作是:读取共享内存的前几十个字节,将内容输出在stdout上。
特别说明:该程序只适用于2.6.25左右的新版本!用__get_free_pages分配连续内存时,宜先用get_order获取页数,然后需将各页都SetPageReserved下,同样地,买QQ号平台释放内存时,需要对每一页调用ClearPageReserved。
我成功用该程序分配了4MB共享内存,运行还比较稳定。因为linux内核的默认设置,一般情况下用get_free_pages只能分配到4MB内存左右,如需增大,可能需改相应的参数并重新编译内核。

五、内核源码

1、common.h(内核与用户空间都用到的头文件)

ifndef COMMON_H

define COMMON_H

/ protocol type /

define SHM_NETLINK 30

/ message type /

define SHM_GET_SHM_INFO 1

/ you can add othe message type here /

define SHM_WITH_NETLINK "hello, use share memory with netlink"

typedef struct _nlk_msg
{
union _data
{
struct _shm_info
{
uint32_t mem_addr;
uint32_t mem_size;
}shm_info;

/ you can add other content here /
}data;
}nlk_msg_t;

endif / COMMON_H /

2、shm_k.c(内核模块)

include

include

include

include

include

include

include

include

include "common.h"

define SHM_TEST_DEBUG

ifdef SHM_TEST_DEBUG

define SHM_DBG(args...) printk(KERN_DEBUG "SHM_TEST: " args)

else

define SHM_DBG(args...)

endif

define SHM_ERR(args...) printk(KERN_ERR "SHM_TEST: " args)

static struct _glb_para
{
struct _shm_para
{
uint32_t mem_addr; / memory starting address /
uint32_t mem_size; / memory size /
uint32_t page_cnt; / memory page count/
uint16_t order;
uint8_t mem_init_flag; / 0, init failed; 1, init successful /
}shm_para;

struct sock nlfd; / netlink descriptor */
uint32_t pid; / user-space process's pid /
rwlock_t lock;
}glb_para;

static void init_glb_para(void);
static int init_netlink(void);
static void kernel_receive(struct sk_buff* __skb);
static int nlk_get_mem_addr(struct nlmsghdr *pnhdr);
static void clean_netlink(void);
static int init_shm(void);
static void clean_shm(void);
static int __init init_shm_test(void);
static void clean_shm_test(void);

static void init_glb_para(void)
{
memset(&glb_para, 0, sizeof(glb_para));
}

static int init_netlink(void)
{
rwlock_init(&glb_para.lock);
SHM_DBG("linux version:%08x\n", LINUX_VERSION_CODE);

if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))

glb_para.nlfd = netlink_kernel_create(SHM_NETLINK, kernel_receive);

elif(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))

glb_para.nlfd = netlink_kernel_create(SHM_NETLINK, 0, kernel_receive, THIS_MODULE));

else

glb_para.nlfd = netlink_kernel_create(&init_net, SHM_NETLINK, 0, kernel_receive, NULL, THIS_MODULE);

endif

if(glb_para.nlfd == NULL)
{
SHM_ERR("init_netlink::netlink_kernel_create error\n");
return (-1);
}

return (0);
}

static void kernel_receive(struct sk_buff* __skb)
{
struct sk_buff *skb;
struct nlmsghdr *nlh = NULL;
int invalid;

SHM_DBG("begin kernel_receive\n");
skb = skb_get(__skb);
invalid = 0;
if(skb->len >= sizeof(struct nlmsghdr))
{
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))
&& (skb->len >= nlh->nlmsg_len))
{
switch(nlh->nlmsg_type)
{
case SHM_GET_SHM_INFO:
SHM_DBG("receiv TA_GET_SHM_INFO\n");
nlk_get_mem_addr(nlh);
break;
default:
break;
}
}
}
kfree_skb(skb);
}

static int nlk_get_mem_addr(struct nlmsghdr *pnhdr)
{
int ret, size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct _nlk_msg *p;

glb_para.pid = pnhdr->nlmsg_pid; / get the user-space process's pid /

size = NLMSG_SPACE(sizeof(struct _nlk_msg)); / compute the needed memory size /
if( (skb = alloc_skb(size, GFP_ATOMIC)) == NULL) / allocate memory /
{
SHM_DBG("nlk_hello_test::alloc_skb error.\n");
return (-1);
}

old_tail = skb->tail;
nlh = NLMSG_PUT(skb, 0, 0, SHM_GET_SHM_INFO, size-sizeof(struct nlmsghdr)); / put netlink message structure into memory /

p = NLMSG_DATA(nlh); / get netlink message body pointer /
p->data.shm_info.mem_addr = __pa(glb_para.shm_para.mem_addr); / __pa:convert virtual address to physical address, which needed by /dev/mem /
p->data.shm_info.mem_size = glb_para.shm_para.mem_size;

nlh->nlmsg_len = skb->tail - old_tail;
NETLINK_CB(skb).pid = 0; / from kernel /
NETLINK_CB(skb).dst_group = 0;
read_lock_bh(&glb_para.lock);
ret = netlink_unicast(glb_para.nlfd, skb, glb_para.pid, MSG_DONTWAIT); / send message to user-space process /
read_unlock_bh(&glb_para.lock);
SHM_DBG("nlk_get_mem_addr ok.\n");
return (ret);

nlmsg_failure:
SHM_DBG("nlmsg_failure\n");
if(skb)
{
kfree_skb(skb);
}
return (-1);
}

static void clean_netlink(void)
{
if(glb_para.nlfd != NULL)
{
sock_release(glb_para.nlfd->sk_socket);
}
}

static int init_shm(void)
{
int i;
char *p;
uint32_t page_addr;

glb_para.shm_para.order = get_order(10248); / allocate 8kB */
glb_para.shm_para.mem_addr = __get_free_pages(GFP_KERNEL, glb_para.shm_para.order);
if(glb_para.shm_para.mem_addr == 0)
{
SHM_ERR("init_mem_pool::__get_free_pages error.\n");
glb_para.shm_para.mem_init_flag = 0;
return (-1);
}
else
{
glb_para.shm_para.page_cnt = (1< glb_para.shm_para.mem_size = glb_para.shm_para.page_cnt*PAGE_SIZE;
glb_para.shm_para.mem_init_flag = 1;
page_addr = glb_para.shm_para.mem_addr;
SHM_DBG("size=%08x, page_cnt=%d\n", glb_para.shm_para.mem_size, glb_para.shm_para.page_cnt);
for(i = 0; i < glb_para.shm_para.page_cnt; i++)
{
SetPageReserved(virt_to_page(page_addr)); / reserved for used /
page_addr += PAGE_SIZE;
}

p = (char *)glb_para.shm_para.mem_addr;
strcpy(p, SHM_WITH_NETLINK); / write /
SHM_DBG("__get_free_pages ok.\n");
}

return (0);
}

static void clean_shm(void)
{
int i;
uint32_t page_addr;

if(glb_para.shm_para.mem_init_flag == 1)
{
page_addr = glb_para.shm_para.mem_addr;
for(i = 0; i < glb_para.shm_para.page_cnt; i++)
{
ClearPageReserved(virt_to_page(page_addr));
page_addr += PAGE_SIZE;
}
free_pages(glb_para.shm_para.mem_addr, glb_para.shm_para.order);
}
}

static int __init init_shm_test(void)
{
init_glb_para();
if(init_netlink() < 0)
{
SHM_ERR("init_shm_test::init_netlink error.\n");
return (-1);
}
SHM_DBG("init_netlink ok.\n");

if(init_shm() < 0)
{
SHM_ERR("init_shm_test::init_mem_pool error.\n");
clean_shm_test();
return (-1);
}
SHM_DBG("init_mem_pool ok.\n");

return (0);
}

static void clean_shm_test(void)
{
clean_shm();
clean_netlink();

SHM_DBG("ta_exit ok.\n");
}
module_init(init_shm_test);
module_exit(clean_shm_test);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bripengandre (bripengandre@126.com)");
MODULE_DESCRIPTION("Memory Share between user-space and kernel-space with netlink.");

3、shm_u.c(用户进程)

include

include

include

include

include

include

include

include

include

include

include

include "common.h"

/ netlink /

define MAX_SEND_BUF_SIZE 2500

define MAX_RECV_BUF_SIZE 2500

define SHM_TEST_DEBUG

ifdef SHM_TEST_DEBUG

define SHM_DBG(args...) fprintf(stderr, "SHM_TEST: " args)

else

define SHM_DBG(args...)

endif

define SHM_ERR(args...) fprintf(stderr, "SHM_TEST: " args)

struct _glb_para
{
struct _shm_para
{
uint32_t mem_addr;
uint32_t mem_size;
}shm_para;

int nlk_fd;
char send_buf[MAX_SEND_BUF_SIZE];
char recv_buf[MAX_RECV_BUF_SIZE];
}glb_para;

static void init_glb_para(void);
static int create_nlk_connect(void);
static int nlk_get_shm_info(void);
static int init_mem_pool(void);

int main(int argc ,char *argv[])
{
char *p;

init_glb_para();
if(create_nlk_connect() < 0)
{
SHM_ERR("main::create_nlk_connect error.\n");
return (1);
}

if(nlk_get_shm_info() < 0)
{
SHM_ERR("main::nlk_get_shm_info error.\n");
return (1);
}

init_mem_pool();
/ printf the first 30 bytes /
p = (char *)glb_para.shm_para.mem_addr;
p[strlen(SHM_WITH_NETLINK)] = '\0';
printf("the first 30 bytes of shm are: %s\n", p);

return (0);
}

static void init_glb_para(void)
{
memset(&glb_para, 0, sizeof(glb_para));
}

static int create_nlk_connect(void)
{
int sockfd;
struct sockaddr_nl local;

sockfd = socket(PF_NETLINK, SOCK_RAW, SHM_NETLINK);
if(sockfd < 0)
{
SHM_ERR("create_nlk_connect::socket error:%s\n", strerror(errno));
return (-1);
}
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;
if(bind(sockfd, (struct sockaddr*)&local, sizeof(local)) != 0)
{
SHM_ERR("create_nlk_connect::bind error: %s\n", strerror(errno));
return -1;
}

glb_para.nlk_fd = sockfd;

return (sockfd);
}

static int nlk_get_shm_info(void)
{
struct nlmsghdr *nlh;
struct _nlk_msg *p;
struct sockaddr_nl kpeer;
int recv_len, kpeerlen;

memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;

memset(glb_para.send_buf, 0, sizeof(glb_para.send_buf));
nlh = (struct nlmsghdr *)glb_para.send_buf;
nlh->nlmsg_len = NLMSG_SPACE(0);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = SHM_GET_SHM_INFO;
nlh->nlmsg_pid = getpid();
sendto(glb_para.nlk_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&kpeer, sizeof(kpeer));
memset(glb_para.send_buf, 0, sizeof(glb_para.send_buf));
kpeerlen = sizeof(struct sockaddr_nl);
recv_len = recvfrom(glb_para.nlk_fd, glb_para.recv_buf, sizeof(glb_para.recv_buf), 0, (struct sockaddr*)&kpeer, &kpeerlen);
p = NLMSG_DATA((struct nlmsghdr *) glb_para.recv_buf);
SHM_DBG("%d, errno=%d.%s, %08x, %08x\n", recv_len, errno, strerror(errno), p->data.shm_info.mem_addr, p->data.shm_info.mem_size);
glb_para.shm_para.mem_addr = p->data.shm_info.mem_addr;
glb_para.shm_para.mem_size = p->data.shm_info.mem_size;

return (0);
}

static int init_mem_pool(void)
{
int map_fd;
void *map_addr;

map_fd = open("/dev/mem", O_RDWR);
if(map_fd < 0)
{
SHM_ERR("init_mem_pool::open %s error: %s\n", "/dev/mem", strerror(errno));
return (-1);
}

map_addr = mmap(0, glb_para.shm_para.mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, glb_para.shm_para.mem_addr);
if(map_addr == NULL)
{
SHM_ERR("init_mem_pool::mmap error: %s\n", strerror(errno));
return (-1);
}
glb_para.shm_para.mem_addr = (uint32_t)map_addr;
return (0);
}
4、Makefile

PREFIX = powerpc-e300c3-linux-gnu-

CC ?= $(PREFIX)gcc
KERNELDIR ?= /lib/modules/uname -r/build

all: modules app
obj-m:= shm_k.o
module-objs := shm_k.c
modules:
make -C $(KERNELDIR) M=pwd modules
app: shm_u.o
$(CC) -o shm_u shm_u.c
clean:
rm -rf .o Module.symvers modules.order shm_u shm_k.ko shm_k.mod.c .tmp_versions .shm_k.

目录
相关文章
|
3月前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
55 6
|
3天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
9天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
171 1
|
6天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
13 0
|
6月前
|
算法 安全 Linux
探索Linux内核的虚拟内存管理
【5月更文挑战第20天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分之一——虚拟内存管理。通过剖析其关键组件和运作机制,揭示虚拟内存如何提供高效的内存抽象,支持庞大的地址空间,以及实现内存保护和共享。文章将重点讨论分页机制、虚拟内存区域(VMAs)的管理、页面置换算法,并简要分析这些技术是如何支撑起现代操作系统复杂而多变的内存需求的。
|
2月前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
111 5
|
1月前
|
存储 算法 C语言
MacOS环境-手写操作系统-15-内核管理 检测可用内存
MacOS环境-手写操作系统-15-内核管理 检测可用内存
33 0
|
3月前
|
算法 安全 UED
探索操作系统的内核空间:虚拟内存管理
【7月更文挑战第50天】 在现代操作系统中,虚拟内存管理是核心功能之一,它允许操作系统高效地使用物理内存,并为应用程序提供独立的地址空间。本文将深入探讨操作系统虚拟内存管理的机制,包括分页、分段以及内存交换等关键技术,并分析它们如何共同作用以实现内存的有效管理和保护。通过理解这些原理,读者可以更好地把握操作系统的内部工作原理及其对应用程序性能的影响。
|
4月前
|
Java fastjson C++
JVM内存问题之JVM中元空间持续增长并且GC无法释放的原因可能是什么
JVM内存问题之JVM中元空间持续增长并且GC无法释放的原因可能是什么
157 2