Chinaunix
标题: 基于NETLINK的内核与用户空间共享内存的实现 [打印本页]
作者: bripengandre 时间: 2009-05-26 21:24
标题: 基于NETLINK的内核与用户空间共享内存的实现
ps:刚不小把文章发《内核问题》版去了,在这个版找不到还以为被删了,小气了下~~
还是不会从blog导入文章,只好复制粘贴了,原文在my blog:http://blog.chinaunix.net/u3/94771/showart_1945422.html
基于NETLINK的内核与用户空间共享内存的实现
author:bripengandre Email:bripengandre@126.com
一、前言
前些日子,开发中用到了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 withnetlink"字样。shm_u.c工作是:读取共享内存的前几十个字节,将内容输出在stdout上。
特别说明:该程序只适用于2.6.25左右的新版本!用__get_free_pages分配连续内存时,宜先用get_order获取页数,然后需将各页都SetPageReserved下,同样地,释放内存时,需要对每一页调用ClearPageReserved。
我成功用该程序分配了4MB共享内存,运行还比较稳定。因为linux内核的默认设置,一般情况下用get_free_pages只能分配到4MB内存左右,如需增大,可能需改相应的参数并重新编译内核。
五、运行结果
我是在mpc8377上运行的,全过程如下(有少许编辑,如dmesg信息只列出新增的)。
-sh-2.05b# insmod shm_k.ko
-sh-2.05b# dmesg
SHM_TEST: linux version:00020619
SHM_TEST: init_netlink ok.
SHM_TEST: size=00002000, page_cnt=2
SHM_TEST: __get_free_pages ok.
SHM_TEST: init_mem_pool ok.
-sh-2.05b# ./shm_u
SHM_TEST: 24, errno=0.Success, 0defe000, 00002000
the first 30 bytes of shm are: hello, use share memory with netlink
-sh-2.05b# dmesg
SHM_TEST: begin kernel_receive
SHM_TEST: receiv TA_GET_SHM_INFO
SHM_TEST: nlk_get_mem_addr ok
-sh-2.05b# rmmod shm_k
-sh-2.05b# dmesg
SHM_TEST: ta_exit ok.
-sh-2.05b#
六、内核源码
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 <linux/init.h>
- #include <linux/module.h>
- #include <linux/version.h>
- #include <linux/types.h>
- #include <linux/skbuff.h>
- #include <linux/netlink.h>
- #include <net/sock.h>
- #include <linux/spinlock.h>
- #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(1024*8); /* 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.order);
- 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 <stdio.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <linux/netlink.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #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.*
[ 本帖最后由 bripengandre 于 2009-5-27 10:26 编辑 ]
作者: Godbach 时间: 2009-05-26 23:19
多谢LZ分享。建议编辑一下帖子,禁用Smilies。
作者: wuasiam 时间: 2009-05-27 09:17

作者: thomas_ar 时间: 2009-05-27 09:39
我觉得啊,通过映射/dev/mem的方式并不能实现真正意义上的共享内存,只能算共享数据而已。
作者: Godbach 时间: 2009-05-27 09:41
原帖由 thomas_ar 于 2009-5-27 09:39 发表 
我觉得啊,通过映射/dev/mem的方式并不能实现真正意义上的共享内存,只能算共享数据而已。
那LS认为共享内存和共享数据的区别是什么呢?
作者: Godbach 时间: 2009-05-27 09:42
帮LZ编辑了一下,禁用了Smilies
作者: thomas_ar 时间: 2009-05-27 09:51
原帖由 Godbach 于 2009-5-27 09:41 发表 
那LS认为共享内存和共享数据的区别是什么呢?
如果两个用户进程 共享内存的话,他们是真正地操作同一块物理内存,一个进程对这个内存的改变可以被另一个进程立刻看到
而现在是通过打开/dev/mem文件并映射到用户进程的地址空间,对用户进程来说,这个字符设备文件与普通文件没有区别(这是VFS的设计目的),所以内核会为这个文件建立Page Cache,进程会先读Page Cache里的数据,如果里面没有,再从真实文件中读取(而且会将读到的数据放到Page Cache中),写的时候也是一样,先写到Page Cache中,并设置该页为dirty,稍后再由其他内核线程写入文件。
所以这里就有一个延时,内核往该“共享内存”写的数据不能被用户进程立刻看到,反之亦然
[ 本帖最后由 thomas_ar 于 2009-5-27 09:57 编辑 ]
作者: Godbach 时间: 2009-05-27 09:51
#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);
这个init函数中的代码,应该说是实现了不同内核版本的兼容吧。
作者: Godbach 时间: 2009-05-27 09:59
标题: 回复 #8 Godbach 的帖子
2.6.18上编译了一下试试,结果提示kernel_receive函数的类型不对。看了楼主的代码,该函数之定义了一个参数。2.6.18下需要两个参数。
作者: ShadowStar 时间: 2009-05-27 10:01
原帖由 thomas_ar 于 2009-5-27 09:51 发表 
如果两个用户进程 共享内存的话,他们是真正地操作同一块物理内存,一个进程对这个内存的改变可以被另一个进程立刻看到
而现在是通过打开/dev/mem文件并映射到用户进程的地址空间,对用户进程来说,这个字 ...
即使“真正地操作同一块物理内存”,也不能保证可以被另一个进程立刻看到。
别忘了CPU缓存。
除非变量设定为volatile。
作者: bripengandre 时间: 2009-05-27 10:07
原帖由 Godbach 于 2009-5-26 23:19 发表 
多谢LZ分享。建议编辑一下帖子,禁用Smilies。
谢谢斑竹了,newbie,操作不熟~~~
作者: bripengandre 时间: 2009-05-27 10:09
原帖由 thomas_ar 于 2009-5-27 09:51 发表 
如果两个用户进程 共享内存的话,他们是真正地操作同一块物理内存,一个进程对这个内存的改变可以被另一个进程立刻看到
而现在是通过打开/dev/mem文件并映射到用户进程的地址空间,对用户进程来说,这个字 ...
谢谢谢谢~~内核还没弄过,很多问题不清楚,以后还请多指教~~
作者: thomas_ar 时间: 2009-05-27 10:09
原帖由 ShadowStar 于 2009-5-27 10:01 发表 
即使“真正地操作同一块物理内存”,也不能保证可以被另一个进程立刻看到。
别忘了CPU缓存。
除非变量设定为volatile。
恩,这个因素我没考虑到,不过CPU缓存带来的延时远小于Page cache带来的延时吧。假设,用户进程只负责读,内核不停的写,而Page Cache一直不更新,那不是一直看不到内核的改动
作者: Godbach 时间: 2009-05-27 10:09
我这里没法测试,你能否把测试结果提出来。
BTW,我把你的源码也编辑一下吧,更适合阅读。
作者: bripengandre 时间: 2009-05-27 10:10
原帖由 Godbach 于 2009-5-27 09:51 发表 
这个init函数中的代码,应该说是实现了不同内核版本的兼容吧。
是创建时注意了下版本,但之后的kernel_receive等接口都写死了,所以不兼容啊~~
作者: bripengandre 时间: 2009-05-27 10:12
原帖由 ShadowStar 于 2009-5-27 10:01 发表 
即使“真正地操作同一块物理内存”,也不能保证可以被另一个进程立刻看到。
别忘了CPU缓存。
除非变量设定为volatile。
嗯,这方面不清楚,谢谢指教~~
作者: Godbach 时间: 2009-05-27 10:13
原帖由 bripengandre 于 2009-5-27 10:10 发表 
是创建时注意了下版本,但之后的kernel_receive等接口都写死了,所以不兼容啊~~
恩,注意到这个问题了。
作者: Godbach 时间: 2009-05-27 10:19
原帖由 thomas_ar 于 2009-5-27 09:51 发表 
如果两个用户进程 共享内存的话,他们是真正地操作同一块物理内存,一个进程对这个内存的改变可以被另一个进程立刻看到
而现在是通过打开/dev/mem文件并映射到用户进程的地址空间,对用户进程来说,这个字 ...
通常我们使用的所谓共享内存的方式,也大致就是这种方式吧。如果真的要保证即时数据的更新,是否是使用barrier系列的函数。
作者: thomas_ar 时间: 2009-05-27 10:23
而且 2.6.26 就增加了默认禁止访问/dev/mem的选项
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=ae531c26c5c2a28ca1b35a75b39b3b256850f2c8
+config NONPROMISC_DEVMEM
+ bool "Disable promiscuous /dev/mem"
+ default y
+ help
+ The /dev/mem file by default only allows userspace access to PCI
+ space and the BIOS code and data regions. This is sufficient for
+ dosemu and X and all common users of /dev/mem. With this config
+ option, you allow userspace access to all of memory, including
+ kernel and userspace memory. Accidental access to this is
+ obviously disasterous, but specific access can be used by people
+ debugging the kernel.
+
可见内核开发者并不希望提供这样一种机制
作者: bripengandre 时间: 2009-05-27 10:28
原帖由 Godbach 于 2009-5-27 10:09 发表 
我这里没法测试,你能否把测试结果提出来。
BTW,我把你的源码也编辑一下吧,更适合阅读。
已经加上了,斑竹美容辛苦了,我得工作了,晚上再见~~
作者: bripengandre 时间: 2009-05-27 10:29
默认禁止,可能是出于安全的考虑吧,窃以为,后门很多时候都还是会开的~~
作者: weily0000 时间: 2009-05-27 11:23
标题: 回复 #1 bripengandre 的帖子
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);
}
在楼主这段代码中,你对mmap过来的内存,没有取消mmap,你是怎么考虑的呢?
作者: Godbach 时间: 2009-05-27 11:41
在楼主这段代码中,你对mmap过来的内存,没有取消mmap,你是怎么考虑的呢?
应该在程序退出的时候取消。这个地方应该是LZ遗漏了。
作者: weily0000 时间: 2009-05-27 11:52
原帖由 Godbach 于 2009-5-27 11:41 发表 
应该在程序退出的时候取消。这个地方应该是LZ遗漏了。
那就是在用户进程退出前这个mmap是一直保存的吧:)
作者: Godbach 时间: 2009-05-27 12:53
原帖由 weily0000 于 2009-5-27 11:52 发表 
那就是在用户进程退出前这个mmap是一直保存的吧:)
应该是的。
作者: bripengandre 时间: 2009-05-27 15:27
原帖由 weily0000 于 2009-5-27 11:52 发表 
那就是在用户进程退出前这个mmap是一直保存的吧:)
这里没有munmap确实是遗漏了,但程序退出时,这个映射关系自然解除:
摘自man:
The munmap() system call deletes the mappings for the specified address range, and causes further references to
addresses within the range to generate invalid memory references. The region is also automatically unmapped
when the process is terminated. On the other hand, closing the file descriptor does not unmap the region.
对我这样一个对内核不了解的人来说,mmap就是把一段共用的东西映射到进程用户地址空间,即mmap时,这段共用的东东会增加一个引用,用户程序退出时,地址映射等都会被正常回收,所以自动解除了映射(我这程序里的描述符就没有显示关闭)。
如果你要问内核部分分配的那部分供共享的内存什么时候释放?那遵循“解铃还需系铃人”的原则,释放也由内核部分负责即可,本示例程序中就在rmmod shm_k.ko时进行~~
anyway,非常感谢你指出我的不细心~~
作者: Godbach 时间: 2009-05-27 15:37
恩。个人觉得程序中自己申请的东东,还是自己显示的释放比较好。
作者: weily0000 时间: 2009-05-27 15:57
原帖由 bripengandre 于 2009-5-27 15:27 发表 
这里没有munmap确实是遗漏了,但程序退出时,这个映射关系自然解除:
摘自man:
The munmap() system call deletes the mappings for the specified address range, and causes further references to
...
哥们太细心了。。我最近在做的一个小东西也用了NETLINK进行内存共享,做的事情和你差不多,我在内核里面申请了一段内存空间进行缓存,如果缓存满了就会通过netlink将缓存地址和缓存大小通知用户态进程去读取数据。在用户态我会做一个映射,如下:
- map_addr = mmap(0, buffer_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, buffer_addr);
- memcpy(buff_pos, map_addr, buffer_size); /* copy kernel data to user buffer, wait for transmiting */
- munmap(map_addr, buffer_size);
复制代码
看了你的代码后,我有了一些启发,我想我这个有点多余了,因为缓存从一申请开始它的大小和地址就应该不变了,所以只用到退出程序时接触映射就够了,而我这样效率更低了。
另外我有一个问题就是,我采用这种方式,如果当内核里面往缓存写数据速度太快时候,用户进程读取速度过慢,这样就会有数据的丢失的情况有时候会发生,我现在能做的就是增大内核缓存的申请,这种方法可能还是有一个限制,不知道大家有没有什么好的解决方法,谢谢了。
作者: Godbach 时间: 2009-05-27 16:11
另外我有一个问题就是,我采用这种方式,如果当内核里面往缓存写数据速度太快时候,用户进程读取速度过慢,这样就会有数据的丢失的情况有时候会发生,我现在能做的就是增大内核缓存的申请,这种方法可能还是有一个限制,不知道大家有没有什么好的解决方法,谢谢了。
恩,用get_free_pages申请的连续内存大小是有限制的。你要最多写多少数据啊?
作者: weily0000 时间: 2009-05-27 21:36
原帖由 Godbach 于 2009-5-27 16:11 发表 
恩,用get_free_pages申请的连续内存大小是有限制的。你要最多写多少数据啊?
我做的和raid1有点像,会把写的数据都存起来做一个备份,如果用户写一个100M的文件,我就要写100M的数据。。这个量不是我所能预测的,实际情况下,如果系统写得太多太快,会丢失大量的数据,而且我在内核里面用了多缓冲,这个还是有问题。。
作者: bripengandre 时间: 2009-05-27 22:28
这么大空间,当空间满了,能不能让用户的写什么的返回失败了,当然你可在空间达到一定门限(如占满80%)时,通知你的用户部分处理吧~~
内核里面不是有那么多缓冲吗(比如mmap之后调用msync就是这个原因)?是不是可以参考这些实现?
内核的东西不懂,以上建议是随便想的~~Lz将这个问题解决后,发篇文章给我们分享下吧~期待中~
原帖由 weily0000 于 2009-5-27 21:36 发表 
我做的和raid1有点像,会把写的数据都存起来做一个备份,如果用户写一个100M的文件,我就要写100M的数据。。这个量不是我所能预测的,实际情况下,如果系统写得太多太快,会丢失大量的数据,而且我在内核里 ...
作者: weily0000 时间: 2009-05-28 22:17
原帖由 bripengandre 于 2009-5-27 22:28 发表 
这么大空间,当空间满了,能不能让用户的写什么的返回失败了,当然你可在空间达到一定门限(如占满80%)时,通知你的用户部分处理吧~~
内核里面不是有那么多缓冲吗(比如mmap之后调用msync就是这个原因)? ...
谢谢你的建议,假期没怎么看这个,才回复:)
恩,你说的“当然你可在空间达到一定门限(如占满80%)时,通知你的用户部分处理”,我是用了这个的,但是用户进程有的时候还是来不及处理
“当空间满了,能不能让用户的写什么的返回失败了”这是一个考虑的方法。
对于你说的“mmap之后调用msync就是这个原因”这个我就不大了解了,呵呵,有空去看看
作者: foochow 时间: 2009-05-29 10:15
要实现内核/用户态共享内存直接实现一个杂类字符设备,实现mmap方法不就可以了?引入NETLINK交互来交互去多此一举。
作者: platinum 时间: 2009-05-29 17:17
感觉楼主意在体现 netlink 来通知用户空间共享内存的起始地址而已,之后的事情与传统做法无异
主要区别,就是通过 netlink 通知,而非 proc、非 ioctl、非字符设备
作者: Godbach 时间: 2009-05-29 20:39
原帖由 platinum 于 2009-5-29 17:17 发表 
感觉楼主意在体现 netlink 来通知用户空间共享内存的起始地址而已,之后的事情与传统做法无异
主要区别,就是通过 netlink 通知,而非 proc、非 ioctl、非字符设备
我之前曾经总结过一篇用proc文件物理地址和size传输来的帖子。
作者: weily0000 时间: 2009-05-30 13:01
原帖由 Godbach 于 2009-5-29 20:39 发表 
我之前曾经总结过一篇用proc文件物理地址和size传输来的帖子。
版大这个文章拜读过,也试验了,很好用的
作者: raysmile 时间: 2009-06-02 07:39
有人测试过netlink传输message时的性能开销吗?
我初步测试了一下,发现从几微秒到几百微秒不等,取决于payload的大小和系统负载,不知道大家有测试的结果吗?
作者: coneagoe 时间: 2009-06-02 07:55
先mark一下
作者: zx_wing 时间: 2009-06-02 10:12
原帖由 ShadowStar 于 2009-5-27 10:01 发表 
即使“真正地操作同一块物理内存”,也不能保证可以被另一个进程立刻看到。
别忘了CPU缓存。
除非变量设定为volatile。
乱说
作者: prettywolf 时间: 2009-06-10 09:38
不错,正是我需要的,多谢了。
作者: dreamice 时间: 2009-06-11 09:38
原帖由 raysmile 于 2009-6-2 07:39 发表 
有人测试过netlink传输message时的性能开销吗?
我初步测试了一下,发现从几微秒到几百微秒不等,取决于payload的大小和系统负载,不知道大家有测试的结果吗?
这个测试没有太大的通用性,这个跟处理器性能、系统内存等硬件的关系比较大,同时,与当前系统负载的也有很大关系。
作者: miniuinx 时间: 2009-07-12 10:06
谢谢LZ分享
作者: 超人慢慢飞 时间: 2011-09-17 13:32
[attachimg]506671[/attachimg
]谢谢LZ,但我运行结果如下,
-
ww.jpg
(12.99 KB, 下载次数: 25)
欢迎光临 Chinaunix (http://bbs.chinaunix.net/) |
Powered by Discuz! X3.2 |