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

简介: /proc 文件系统是一个虚拟文件系统,经过它可使用一种新的方法在 Linux® 内核空间和用户空间之间进行通讯。在 /proc 文件系统中,咱们能够将对虚拟文件的读写做为与内核中实体进行通讯的一种手段,可是与普通文件不一样的是,这些虚拟文件的内容都是动态建立的。本文对 /proc 虚拟文件系统进行了介绍,并展现了它的用法。 html

最初开发 /proc 文件系统是为了提供有关系统中进程的信息。可是因为这个文件系统很是有用,所以内核中的不少元素也开始使用它来报告信息,或启用动态运行时配置。 node

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

尽管像本文这样短小的一篇文章没法详细介绍 /proc 的全部用法,可是它依然对这两种用法进行了展现,从而可让咱们体会一下 /proc 是多么强大。清单 1 是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每一个实际上都是一个目录,表示系统中的一个进程。因为在 GNU/Linux 中建立的第一个进程是 init进程,所以它的 process-id 为 1。而后对这个目录执行一个 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 转发的目前设置,而后再启用这种功能。 cookie


清单 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 的一个优势是它将一个值导出给用户空间很是简单(实际上这不过是一个调用而已)。 app

内核模块简介 less

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

若是您曾经从新编译过 Linux 内核,就可能会发如今内核的配置过程当中,有不少设备驱动程序和其余内核元素都被编译成了模块。若是一个驱动程序被直接编译到了内核中,那么即便这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。可是若是这个驱动程序被编译成一个模块,就只有在须要内存并将其加载到内核时才会真正占用内存空间。有趣的是,对于 LKM 来讲,咱们不会注意到有什么性能方面的差别,所以这对于建立一个适应于本身环境的内核来讲是一种功能强大的手段,这样能够根据可用硬件和链接的设备来加载对应的模块。

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

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

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

最后,清单 3 使用 module_init 和 module_exit 宏声明了入口函数和出口函数。这样咱们就能够按照本身的意愿来对这个模块的 init和 cleanup 函数进行命名了,不过咱们最终要告诉内核维护函数就是这些函数。


清单 3. 一个简单的但能够正常工做的 LKM(simple-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 文件系统中

内核程序员可使用的标准 API,LKM 程序员也可使用。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 );
struct proc_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
	...
};
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );

稍后咱们就能够看到如何使用 read_proc 和 write_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)时,咱们须要使用 start 和 off 参数。当全部数据所有写入以后,就须要设置 eof(文件结束参数)。与 write 相似,data 表示的也是私有数据。此处提供的 page 缓冲区在内核空间中。所以,咱们能够直接写入,而不用调用 copy_to_user。

其余有用的函数

咱们还可使用 proc_mkdir、symlinks 以及 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 long copy_to_user( void __user *to,
                              const void *from,
                              unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_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 */
void vfree( 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 结构进行了初始化。咱们加载了 /proc read 和 write 函数(如清单 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 虚拟文件系统能够普遍地用来报告内核的信息,也能够用来进行动态配置。咱们会发现它对于驱动程序和模块编程来讲都是很是完整的。在下面的 参考资料 中,咱们能够学习到更多相关知识。


回页首

下载

描述 名字 大小 下载方法
Linux kernel module source l-proc-lkm.zip 2KB HTTP

关于下载方法的信息


参考资料

学习

  • 您能够参阅本文在 developerWorks 全球站点上的 英文原文

  • 实时管理 Linux”(developerWorks,2003 年 5 月)详细介绍了 /proc 的基础知识,包括如何管理操做系统的众多细节,而不用关闭或从新启动机器。 

  • 探索 /proc 文件系统中的 文件和子目录。 

  • 有关 Linux 内核 2.6 版本的 driver porting 的文章详细讨论了内核模块的问题。 

  • LinuxHQ 是有关 Linux 内核信息的一个很好站点。 

  • debugfs 文件系统是除 /proc 以外另一个调试选择。 

  • 内核比较: 从 2.4 到 2.6 内核开发中的改进” (developerWorks,2004 年 2 月)对构成 2.6 版本内核的工具、测试以及技术进行了深刻介绍。 

  • 使用 Kprobes 调试内核” (developerWorks,2004 年 8 月)介绍了 Kprobes 如何与 2.6 版本的内核结合使用,提供一种轻量级的非破坏性的强大机制来动态插入 printk 函数。 

  • printk 函数和 dmesg 方法都是用来进行内核调试的经常使用方法。Allessando Rubini 撰写的 Linux Device Drivers 一书提供了一章有关内核调试技术的 在线内容。 

  • sysctl 命令是另一种实现动态内核配置的方法。 

  • 在 developerWorks Linux 专区 中能够找到为 Linux 开发人员准备的更多参考资料。 

  • 随时关注 developerWorks 技术活动和网络广播。 

得到产品和技术

讨论

相关文章
相关标签/搜索