使用 /proc 文件系统来访问 Linux 内核的内容

来源:互联网 发布:蜘蛛数据平台 编辑:程序博客网 时间:2024/06/05 16:06
 

/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux®内核空间和用户空间之间进行通信。在 /proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对 /proc虚拟文件系统进行了介绍,并展示了它的用法。

   最初开发 /proc文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。

/proc文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。

尽管像本文这样短小的一篇文章无法详细介绍 /proc的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下 /proc是多么强大。清单 1是对 /proc中部分元素进行一次交互查询的结果。它显示的是 /proc文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux中创建的第一个进程是init进程,因此它的process-id1。然后对这个目录执行一个ls命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。例如,要查看init command-line项的内容,只需对cmdline文件执行cat命令。

/proc 中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci,显示在 PCI 总线上找到的设备;modules,标识了当前加载到内核中的模块。

清单 1. /proc的交互过程

[root@plato]#ls /proc

1     2040  2347  2874  474          fb           mdstat      sys

104   2061  2356  2930  9            filesystems  meminfo     sysrq-trigger

113   2073  2375  2933  acpi         fs           misc        sysvipc

1375  21    2409  2934  buddyinfo    ide          modules     tty

1395  2189  2445  2935  bus          interrupts   mounts      uptime

1706  2201  2514  2938  cmdline      iomem        mtrr        version

179   2211  2515  2947  cpuinfo      ioports      net         vmstat

180   2223  2607  3     crypto       irq          partitions

181   2278  2608  3004  devices      kallsyms     pci

182   2291  2609  3008  diskstats    kcore        self

2     2301  263   3056  dma          kmsg         slabinfo

2015  2311  2805  394   driver       loadavg      stat

2019  2337  2821  4     execdomains  locks        swaps

[root@plato 1]#ls /proc/1

auxv     cwd      exe  loginuid  mem     oom_adj    root  statm   task

cmdline  environ  fd   maps      mounts  oom_score  stat  status  wchan

[root@plato]#cat /proc/1/cmdline

init [5]

[root@plato]#

 

清单 2展示了对 /proc中的一个虚拟文件进行读写的过程。这个例子首先检查内核的 TCP/IP栈中的 IP转发的目前设置,然后再启用这种功能。


清单 2. /proc进行读写(配置内核)

 

[root@plato]#cat /proc/sys/net/ipv4/ip_forward

0

[root@plato]#echo "1" > /proc/sys/net/ipv4/ip_forward

[root@plato]#cat /proc/sys/net/ipv4/ip_forward

1

[root@plato]#

 

另外,我们还可以使用sysctl来配置这些内核条目。有关这个问题的更多信息,请参阅参考资料一节的内容。

顺便说一下,/proc文件系统并不是 GNU/Linux系统中的惟一一个虚拟文件系统。在这种系统上,sysfs是一个与 /proc类似的文件系统,但是它的组织更好(从 /proc中学习了很多教训)。不过 /proc已经确立了自己的地位,因此即使 sysfs /proc相比有一些优点,/proc也依然会存在。还有一个debugfs文件系统,不过(顾名思义)它提供的更多是调试接口。debugfs的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。

内核模块简介

可加载内核模块(LKM)是用来展示 /proc 文件系统的一种简单方法,这是因为这是一种用来动态地向 Linux内核添加或删除代码的新方法。LKM也是 Linux内核中为设备驱动程序和文件系统使用的一种

流行机制。

如果您曾经重新编译过 Linux内核,就可能会发现在内核的配置过程中,有很多设备驱动程序和其他内核元素都被编译成了模块。如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间。有趣的是,对于 LKM 来说,我们不会注意到有什么性能方面的差异,因此这对于创建一个适应于自己环境的内核来说是一种功能强大的手段,这样可以根据可用硬件和连接的设备来加载对应的模块。

下面是一个简单的 LKM,可以帮助您理解它与在 Linux 内核中看到的标准(非动态可加载的)代码之间的区别。清单 3给出了一个最简单的 LKM。(可以从本文的下载一节中下载这个代码)。

清单 3包括了必须的模块头(它定义了模块的 API、类型和宏)。然后使用MODULE_LICENSE定义了这个模块使用的许可证。此处,我们定义的是GPL,从而防止会污染到内核。

清单 3然后又定义了这个模块的initcleanup函数。my_module_init函数是在加载这个模块时被调用的,它用来进行一些初始化方面的工作。my_module_cleanup函数是在卸载这个模块时被调用的,它用来释放内存并清除这个模块的踪迹。注意此处printk的用法:这是内核的printf函数。KERN_INFO符号是一个字符串,可以用来对进入内核回环缓冲区的信息进行过滤(非常类似于syslog)。

最后,清单 3使用module_initmodule_exit宏声明了入口函数和出口函数。这样我们就可以按照自己的意愿来对这个模块的initcleanup函数进行命名了,不过我们最终要告诉内核维护函数就是这些函数。


清单 3.一个简单的但可以正常工作的 LKMsimple-lkm.c

 

#include <linux/module.h>

 

/* Defines the license for this LKM */

MODULE_LICENSE("GPL");

 

/* Init function called on module entry */

int my_module_init( void )

{

 printk(KERN_INFO "my_module_init called.  Module is now loaded./n");

 

 return 0;

}

 

/* Cleanup function called on module exit */

void my_module_cleanup( void )

{

 printk(KERN_INFO "my_module_cleanup called.  Module is now unloaded./n");

 

 return;

}

 

/* Declare entry and exit functions */

module_init( my_module_init );

module_exit( my_module_cleanup );

 

清单 3尽管非常简单,但它却是一个真正的 LKM。现在让我们对其进行编译并在一个 2.6 版本的内核上进行测试。2.6版本的内核为内核模块的编译引入了一种新方法,我发现这种方法比原来的方法简单了很多。对于文件simple-lkm.c,我们可以创建一个 makefile,其惟一内容如下:

 

obj-m += simple-lkm.o

 

要编译 LKM,请使用make命令,如清单 4所示。


清单 4.编译 LKM

 

[root@plato]#make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules

make: Entering directory `/usr/src/linux-2.6.11'

 CC [M]  /root/projects/misc/module2.6/simple/simple-lkm.o

 Building modules, stage 2.

 MODPOST

 CC      /root/projects/misc/module2.6/simple/simple-lkm.mod.o

 LD [M]  /root/projects/misc/module2.6/simple/simple-lkm.ko

make: Leaving directory `/usr/src/linux-2.6.11'

[root@plato]#

 

结果会生成一个simple-lkm.ko文件。这个新的命名约定可以帮助将这些内核对象(LKM)与标准对象区分开来。现在可以加载或卸载这个模块了,然后可以查看它的输出。要加载这个模块,请使用insmod命令;反之,要卸载这个模块,请使用rmmod命令。lsmod可以显示当前加载的 LKM(参见清单 5)。


清单 5.插入、检查和删除 LKM

 

[root@plato]#insmod simple-lkm.ko

[root@plato]#lsmod

Module                 Size  Used by

simple_lkm             1536  0

autofs4               26244  0

video                 13956  0

button                 5264  0

battery                7684  0

ac                     3716  0

yenta_socket          18952  3

rsrc_nonstatic         9472  1 yenta_socket

uhci_hcd              32144  0

i2c_piix4              7824  0

dm_mod                56468  3

[root@plato]#rmmod simple-lkm

[root@plato]#

注意,内核的输出进到了内核回环缓冲区中,而不是打印到stdout上,这是因为stdout是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用dmesg工具(或者通过 /proc本身使用cat /proc/kmsg命令)。清单 6给出了dmesg显示的最后几条消息。


清单 6.查看来自 LKM的内核输出

 

[root@plato]#dmesg | tail -5

cs: IO port probe 0xa00-0xaff: clean.

eth0: Link is down

eth0: Link is up, running at 100Mbit half-duplex

my_module_init called. Module is now loaded.

my_module_cleanup called. Module is now unloaded.

[root@plato]#

 

可以在内核输出中看到这个模块的消息。现在让我们暂时离开这个简单的例子,来看几个可以用来开发有用 LKM的内核 API

集成到 /proc文件系统中

内核程序员可以使用的标准 APILKM程序员也可以使用。LKM甚至可以导出内核使用的新变量和函数。有关 API的完整介绍已经超出了本文的范围,因此我们在这里只是简单地介绍后面在展示一个更有用的 LKM时所使用的几个元素。

创建并删除 /proc

要在 /proc文件系统中创建一个虚拟文件,请使用create_proc_entry函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc文件系统中出现的位置。create_proc_entry的返回值是一个proc_dir_entry指针(或者为NULL,说明在create时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry的原型和proc_dir_entry结构中的一部分如清单 7所示。

 

清单 7.用来管理 /proc文件系统项的元素

 

struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,

                                            struct proc_dir_entry *parent );

 

structproc_dir_entry {

     const char *name;              // virtual file name

     mode_t mode;                   // mode permissions

     uid_t uid;                     // File's user id

     gid_t gid;                     // File's group id

     struct inode_operations *proc_iops;  // Inode operations functions

     struct file_operations *proc_fops;   // File operations functions

     struct proc_dir_entry *parent;       // Parent directory

     ...

     read_proc_t *read_proc;              // /proc read function

     write_proc_t *write_proc;            // /proc write function

     void *data;                    // Pointer to private data

     atomic_t count;                      // use count

     ...

};

 

voidremove_proc_entry( const char *name, struct proc_dir_entry *parent );

 

稍后我们就可以看到如何使用read_procwrite_proc命令来插入对这个虚拟文件进行读写的函数。

要从 /proc中删除一个文件,可以使用remove_proc_entry函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc文件系统中的位置(parent)。这个函数原型如清单 7 所示。

parent参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。表 1列出了可以使用的其他一些父proc_dir_entry,以及它们在这个文件系统中的位置。

 

1. proc_dir_entry快捷变量

proc_dir_entry

在文件系统中的位置

proc_root_fs

/proc

proc_net

/proc/net

proc_bus

/proc/bus

proc_root_driver

/proc/driver

 

回调函数

我们可以使用write_proc函数向 /proc中写入一项。这个函数的原型如下:

 

int mod_write( struct file *filp, const char __user *buff,

              unsigned long len, void *data );

 

filp参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len参数定义了在buff中有多少数据要被写入。data参数是一个指向私有数据的指针(参见清单 7)。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。

Linux提供了一组 API来在用户空间和内核空间之间移动数据。对于write_proc的情况来说,我们使用了copy_from_user函数来维护用户空间的数据。

读回调函数

我们可以使用read_proc函数从一个 /proc项中读取数据(从内核空间到用户空间)。这个函数的原型如下:

 

int mod_read( char *page, char **start, off_t off,

             int count, int *eof, void *data );

 

page参数是这些数据写入到的位置,其中count定义了可以写入的最大字符数。在返回多页数据(通常一页是 4KB)时,我们需要使用startoff参数。当所有数据全部写入之后,就需要设置eof(文件结束参数)。与write类似,data表示的也是私有数据。此处提供的page缓冲区在内核空间中。因此,我们可以直接写入,而不用调用copy_to_user

其他有用的函数

我们还可以使用proc_mkdirsymlinks以及proc_symlink /proc文件系统中创建目录。对于只需要一个read函数的简单 /proc项来说,可以使用create_proc_read_entry,这会创建一个 /proc项,并在一个调用中对read_proc函数进行初始化。这些函数的原型如清单 8所示。


清单 8.其他有用的 /proc函数

 

/* Create a directory in the proc filesystem */

struct proc_dir_entry*proc_mkdir( const char *name,

                                    struct proc_dir_entry *parent );

 

/* Create a symlink in the proc filesystem */

struct proc_dir_entry *proc_symlink( const char *name,

                                      struct proc_dir_entry *parent,

                                      const char *dest );

 

/* Create a proc_dir_entry with a read_proc_t in one call */

struct proc_dir_entry *create_proc_read_entry( const char *name,

                                                 mode_t mode,

                                                 struct proc_dir_entry *base,

                                                 read_proc_t *read_proc,

                                                 void *data );

 

/* Copy buffer to user-space from kernel-space */

unsigned longcopy_to_user( void __user *to,

                             const void *from,

                             unsigned long n );

 

/* Copy buffer to kernel-space from user-space */

unsigned longcopy_from_user( void *to,

                               const void __user *from,

                               unsigned long n );

 

/* Allocate a 'virtually' contiguous block of memory */

void *vmalloc( unsigned long size );

 

/* Free a vmalloc'd block of memory */

voidvfree( void *addr );

 

/* Export a symbol to the kernel (make it visible to the kernel) */

EXPORT_SYMBOL( symbol );

 

/* Export all symbols in a file to the kernel (declare before module.h) */

EXPORT_SYMTAB

通过 /proc文件系统实现内核访问

下面是一个可以支持读写的 LKM。这个简单的程序提供了一个内核访问。在加载这个模块之后,用户就可以使用echo命令向其中导入文本财富,然后再使用cat命令逐一读出。

清单 9给出了基本的模块函数和变量。init函数(init_fortune_module)负责使用vmalloc来为这个点心罐分配空间,然后使用memset将其全部清零。使用所分配并已经清空的cookie_pot内存,我们在 /proc中创建了一个proc_dir_entry项,并将其称为fortune。当proc_entry成功创建之后,对自己的本地变量和proc_entry结构进行了初始化。我们加载了 /procreadwrite函数(如清单 9和清单 10所示),并确定这个模块的所有者。cleanup函数简单地从 /proc文件系统中删除这一项,然后释放cookie_pot所占据的内存。

cookie_pot是一个固定大小(4KB)的页,它使用两个索引进行管理。第一个是cookie_index,标识了要将下一个 cookie写到哪里去。变量next_fortune标识了下一个 cookie应该从哪里读取以便进行输出。在所有的 fortune项都读取之后,我们简单地回到了next_fortune


清单 9.模块的 init/cleanup和变量

 

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/proc_fs.h>

#include <linux/string.h>

#include <linux/vmalloc.h>

#include <asm/uaccess.h>

 

MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("Fortune Cookie Kernel Module");

MODULE_AUTHOR("M. Tim Jones");

 

#define MAX_COOKIE_LENGTH      PAGE_SIZE

static struct proc_dir_entry *proc_entry;

 

static char *cookie_pot; // Space for fortune strings

static int cookie_index; // Index to write next fortune

static int next_fortune; // Index to read next fortune

 

 

int init_fortune_module( void )

{

 int ret = 0;

 

 cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );

 

 if (!cookie_pot) {

   ret = -ENOMEM;

 } else {

 

   memset( cookie_pot, 0, MAX_COOKIE_LENGTH );

 

   proc_entry = create_proc_entry( "fortune", 0644, NULL );

 

   if (proc_entry == NULL) {

 

     ret = -ENOMEM;

     vfree(cookie_pot);

     printk(KERN_INFO "fortune: Couldn't create proc entry/n");

 

   } else {

 

     cookie_index = 0;

     next_fortune = 0;

 

     proc_entry->read_proc = fortune_read;

     proc_entry->write_proc = fortune_write;

     proc_entry->owner = THIS_MODULE;

     printk(KERN_INFO "fortune: Module loaded./n");

 

   }

 

 }

 

 return ret;

}

void cleanup_fortune_module( void )

{

 remove_proc_entry("fortune", &proc_root);

 vfree(cookie_pot);

 printk(KERN_INFO "fortune: Module unloaded./n");

}

module_init( init_fortune_module );

module_exit( cleanup_fortune_module );

 

向这个罐中新写入一个 cookie非常简单(如清单 10所示)。使用这个写入 cookie的长度,我们可以检查是否有这么多空间可用。如果没有,就返回-ENOSPC,它会返回给用户空间。否则,就说明空间存在,我们使用copy_from_user将用户缓冲区中的数据直接拷贝到cookie_pot中。然后增大cookie_index(基于用户缓冲区的长度)并使用 NULL来结束这个字符串。最后,返回实际写入cookie_pot的字符的个数,它会返回到用户进程。

清单 10. fortune 进行写入操作所使用的函数

 

ssize_t fortune_write( struct file *filp, const char __user *buff,

                       unsigned long len, void *data )

{

 int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;

 

 if (len > space_available) {

 

   printk(KERN_INFO "fortune: cookie pot is full!/n");

   return -ENOSPC;

 }

 

 if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {

   return -EFAULT;

 }

 

 cookie_index += len;

 cookie_pot[cookie_index-1] = 0;

 

 return len;

}

 

fortune进行读取也非常简单,如清单 11所示。由于我们刚才写入数据的缓冲区(page)已经在内核空间中了,因此可以直接对其进行操作,并使用sprintf来写入下一个 fortune。如果next_fortune索引大于cookie_index(要写入的下一个位置),那么我们就将next_fortune返回为 0,这是第一个 fortune 的索引。在将这个 fortune写入用户缓冲区之后,在next_fortune索引上增加刚才写入的 fortune的长度。这样就变成了下一个可用 fortune的索引。这个 fortune的长度会被返回并传递给用户.

清单 11. fortune进行读取操作所使用的函数

 

int fortune_read( char *page, char **start, off_t off,

                  int count, int *eof, void *data )

{

 int len;

 

 if (off > 0) {

   *eof = 1;

   return 0;

 }

 

 /* Wrap-around */

 if (next_fortune >= cookie_index) next_fortune = 0;

 

 len = sprintf(page, "%s/n", &cookie_pot[next_fortune]);

 

 next_fortune += len;

 

 return len;

}

 

从这个简单的例子中,我们可以看出通过 /proc文件系统与内核进行通信实际上是件非常简单的事情。现在让我们来看一下这个 fortune模块的用法(参见清单 12)。


清单 12.展示 fortune cookie LKM的用法

 

[root@plato]#insmod fortune.ko

[root@plato]#echo "Success is an individual proposition. Thomas Watson" > /proc/fortune

[root@plato]#echo "If a man does his best, what else is there? Gen. Patton" > /proc/fortune

[root@plato]#echo "Cats: All your base are belong to us. Zero Wing" > /proc/fortune

[root@plato]#cat /proc/fortune

Success is an individual proposition. Thomas Watson

[root@plato]#cat /proc/fortune

If a man does his best, what else is there? General Patton

[root@plato]#

 

/proc虚拟文件系统可以广泛地用来报告内核的信息,也可以用来进行动态配置。我们会发现它对于驱动程序和模块编程来说都是非常完整的。在下面的参考资料中,我们可以学习到更多相关知识。

----------------------------------------------------------------------------------------------------------------------------------------------

一、内核空间的代码,经过编译后生成内核模块,利用insmod mmap_kernelspace.o进行该模块的加载
/*
  * The program is used for testing communication between
  * kernel-space and user-space in linux operating system.
  *
  * mmap_kernelspace.c
  * Author: jiangyb (101381@gmail.com)
  * Date: 11/23/2005
  * gcc -c mmap_kernelspace.c -I/usr/src/linux-2.4/include -o mmap_kernelspace.o
  */

#define __KERNEL__

#define MODULE

#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/wrapper.h>
#include <asm/page.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>

#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif

#define PAGES_ORDER 3
#define PAGES 8
#define MEM_WIDTH    1500

unsigned long mem_addr;

struct MEM_DATA {
 unsigned short width;
 unsigned short length;
 unsigned short wptr;
 unsigned short rptr;
} *mem_data;

struct MEM_PACKET {
 unsigned int len;
 unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};

void init_mem()
{
 char *addr;
 char *buff;
 int pages = 0;
 int i;
 struct MEM_PACKET *pdatapack;

 mem_addr = __get_free_pages(GFP_KERNEL,PAGES_ORDER);
 printk("inti_mem(), memory address that get from free pages is %x/n", mem_addr);
 addr = (char *)mem_addr;
 
 while(pages < PAGES) {
  
  mem_map_reserve(virt_to_page(addr));
  addr = addr + PAGE_SIZE;
         pages++;
 }
 mem_data = (struct MEM_DATA *)mem_addr;
 mem_data[0].length = PAGES*4*1024 / MEM_WIDTH;
 mem_data[0].width = MEM_WIDTH;
 mem_data[0].rptr = 1;
 mem_data[0].wptr = 1;
 
 for( i = 1; i <= mem_data[0].length; i++) {
  
         buff = ( void * )( (char *)mem_addr + MEM_WIDTH * i );
        pdatapack = (struct MEM_PACKET *)buff;
         pdatapack->len = 0;
     }   
}

void del_mem()
{
 int pages = 0;
 char *addr;
 addr = (char *)mem_addr;

 while( pages < PAGES ) {
  
  mem_map_unreserve(virt_to_page(addr));
  addr = addr + PAGE_SIZE;
  pages ++;
 }
 free_pages(mem_addr, PAGES_ORDER);
}   

int put_mem(char *aBuf,unsigned int pack_size)
{
 register int mem_ptr;
 register int width,length;
 register int s = 0;
 register int i;
 char *buf;

 struct MEM_PACKET *pdatapack;

 printk("put_mem(), content: %s, size: %d/n", aBuf, pack_size);

 mem_data = (struct MEM_DATA *)mem_addr;

 width  = mem_data[0].width;
 length = mem_data[0].length;
 mem_ptr  = mem_data[0].wptr;

 buf = (void *)( (char *)mem_addr + width * mem_ptr);
 
 for ( i=1; i < length; i++ ) {
  
  pdatapack = (struct MEM_PACKET *)buf;

  if ( pdatapack->len == 0) {
   
   memcpy(pdatapack->packetp,aBuf,pack_size);
   pdatapack->len = pack_size;
   s = mem_ptr;
   mem_ptr++;
   if  (mem_ptr >= length)
   mem_ptr = 1;
   mem_data[0].wptr = mem_ptr;
   break;
  }
  mem_ptr++;
  if  (mem_ptr >= length) {
   
   mem_ptr = 1;
   buf = (void *)((char *)mem_addr + width);
  }
  else buf = (char *)mem_addr + width*mem_ptr;
 }

 if(i >= length)
  s = 0;
 return s;
}

int read_procaddr(char *buf,char **start,off_t offset,
      int count,int *eof,void *data)
{
    sprintf(buf,"%u/n",__pa(mem_addr));
    printk("Memory address is : %u/n", __pa(mem_addr));
    *eof = 1;
    return 9;
}

int init_module(void)
{
 printk("---------------------------/n");
 printk("Corem module is installed/n");
 init_mem();
 put_mem("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26);
 create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);
 return 0;
}

void cleanup_module(void)
{
 del_mem();
 remove_proc_entry("nf_addr",NULL);
 printk("Corem module is uninstalled/n");
}

MODULE_LICENSE("GPL");

 

二、用户空间的代码,编译成可执行文件
/*
  * The program is used for testing communication between
  * kernel-space and user-space in linux operating system.
  *
  * mmap_userspace.c
  * Author: jiangyb (101381@gmail.com)
  * Date: 11/23/2005
  * gcc mmap_userspace.c  -o mmap_userspace
  */

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#define PAGES 8
#define MEM_WIDTH 1500

char *mem_addr;

struct MEM_DATA {
    unsigned short width;
    unsigned short length;
    unsigned short wptr;
    unsigned short rptr;
} *mem_data;

struct MEM_PACKET {
    unsigned int len;
    unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};

int get_mem(char *memaddr,char *inbuff,unsigned int *size)
{
 register int mem_ptr;
 register int width, length;
 register int i;
 register int s = 0;

 char *buf;
 struct MEM_PACKET *pdatapack;

 mem_data = (void *)memaddr;

 width  = mem_data[0].width;
 length = mem_data[0].length;
 mem_ptr = mem_data[0].rptr;

 buf = (void *)(memaddr + width * mem_ptr);
 
 pdatapack = (struct MEM_PACKET *)buf;
 
 if(pdatapack->len != 0) {
  
  memcpy(inbuff,pdatapack->packetp,pdatapack->len);
  *size = pdatapack->len;
  pdatapack->len = 0;
  s = mem_data[0].rptr;
  mem_data[0].rptr++;
  if(mem_data[0].rptr >= length)
   mem_data[0].rptr = 1;
  goto ret;
 }
   
 for (i=1;i<length;i++) {
  
  mem_ptr++;
  if  (mem_ptr >= length)
   mem_ptr = 1;
  
  buf = (void *)(memaddr + width*mem_ptr);
  pdatapack = (struct MEM_PACKET *)buf;
  
  if  (pdatapack->len == 0)
   continue;
  
  memcpy(memaddr, pdatapack->packetp,pdatapack->len);
  *size = pdatapack->len;
  pdatapack->len = 0;
  s = mem_data[0].rptr = mem_ptr;
  mem_data[0].rptr++;
  
  if(mem_data[0].rptr >= length)
   mem_data[0].rptr = 1;
  break;
 }
ret:
 return s;
}

int put_mem(char *mem_addr, char *aBuf, unsigned int pack_size)
{
 register int mem_ptr;
 register int width,length;
 register int s = 0;
 register int i;
 char *buf;

 struct MEM_PACKET *pdatapack;

 mem_data = (struct MEM_DATA *)mem_addr;

 width  = mem_data[0].width;
 length = mem_data[0].length;
 mem_ptr  = mem_data[0].wptr;

 buf = (void *)( (char *)mem_addr + width * mem_ptr);
 
 for(i=1; i < length; i++) {
  
  pdatapack = (struct MEM_PACKET *)buf;

  if(pdatapack->len == 0) {
   
   memcpy(pdatapack->packetp,aBuf,pack_size);
   pdatapack->len = pack_size;
   s = mem_ptr;
   mem_ptr++;
   if  (mem_ptr >= length)
    mem_ptr = 1;
   mem_data[0].wptr = mem_ptr;
   break;
  }
  mem_ptr++;
  if  (mem_ptr >= length) {
   
   mem_ptr = 1;
   buf = (void *)((char *)mem_addr + width);

  } else
   buf = (char *)mem_addr + width*mem_ptr;
 }
 
 if(i >= length)
  s = 0;
 return s;
}

int main()
{
 char *mem_addr;
 char inbuff[1500];

 int fd;
 int fd_procaddr;
 unsigned int size;
 char addr[9];
 unsigned long proc_addr;

 fd = open("/dev/mem", O_RDWR);

 /* read procaddr from nf_addr file in proc directory */
 fd_procaddr = open("/proc/nf_addr", O_RDONLY);
 read(fd_procaddr, addr, 9);
 proc_addr = atol(addr);
 close(fd_procaddr);
 printf("read proc address is : %u[%8lx]/n", proc_addr, proc_addr);

 /* Map the address in kernel to user space, use mmap function*/
 mem_addr = mmap(0, PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, proc_addr);
 
 put_mem(mem_addr, "hello world", 12);

 get_mem(mem_addr, inbuff, &size);

 printf("Get data from core space, content: %s/n", inbuff);
 
 munmap(mem_addr,PAGES*4*1024);
 close(fd);
 return 0;
}

<完>