淡定从容,宁静致远
全部博文(329)
分类: LINUX
2012-09-26 08:49:46
/proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 (所以叫做 /proc)。这个伪文件系统让你可以和内核内部数据结构进行交互,获取 有关进程的有用信息,在运行中 (on the fly) 改变设置 (通过改变内核参数)。 与其他文件系统不同,/proc 存在于内存之中而不是硬盘上。如果你察看文件 /proc/mounts (和 mount 命令一样列出所有已经加载的文件系统),你会看到其中 一行是这样的:
/proc 由内核控制,没有承载 /proc 的设备。因为 /proc 主要存放由内核控制的状态信息,所以大部分这些信息的逻辑位置位于内核控制的内存。对 /proc 进行一次 'ls -l' 可以看到大部分文件都是 0 字节大的;不过察看这些文件的时候,确实可以看到一些信息。这怎么可能?这是因为 /proc 文件系统和其他常规的文件系统一样把自己注册到虚拟文件系统层 (VFS) 了。然而,直到当 VFS 调用它,请求文件、目录的 i-node 的时候,/proc 文件系统才根据内核中的信息建立相应的文件和目录。
加载 proc 文件系统如果系统中还没有加载 proc 文件系统,可以通过如下命令加载 proc 文件系统:
mount -t proc proc /proc
上述命令将成功加载你的 proc 文件系统。更多细节请阅读 mount 命令的 man page。察看 /proc 的文件
/proc 的文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的状态等信息。大部分 /proc 中的文件和目录提供系统物理环境最新的信息。尽管 /proc 中的文件是虚拟的,但它们仍可以使用任何文件编辑器或像'more', 'less'或 'cat'这样的程序来查看。当编辑程序试图打开一个虚拟文件时,这个文件就通过内核中的信息被凭空地 (on the fly) 创建了。这是一些我从我的系统中得到的一些有趣结果:
$ ls -l /proc/cpuinfo -r--r--r-- 1 root root 0 Dec 25 11:01 /proc/cpuinfo $ file /proc/cpuinfo /proc/cpuinfo: empty $ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 8 model name : Pentium III (Coppermine) stepping : 6 cpu MHz : 1000.119 cache size : 256 KB fdiv_bug : no hlt_bug : no sep_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 2 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 mmx fxsr xmm bogomips : 1998.85 processor : 3 vendor_id : GenuineIntel cpu family : 6 model : 8 model name : Pentium III (Coppermine) stepping : 6 cpu MHz : 1000.119 cache size : 256 KB fdiv_bug : no hlt_bug : no sep_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 2 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 mmx fxsr xmm bogomips : 1992.29得到有用的系统/内核信息
proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。下面是一些重要的文件:
有关运行中的进程的信息
/proc 文件系统可以用于获取运行中的进程的信息。在 /proc 中有一些编号的子目录。每个编号的目录对应一个进程 id (PID)。这样,每一个运行中的进程 /proc 中都有一个用它的 PID 命名的目录。这些子目录中包含可以提供有关进程的状态和环境的重要细节信息的文件。让我们试着查找一个运行中的进程。
$ ps -aef | grep mozilla root 32558 32425 8 22:53 pts/1 00:01:23 /usr/bin/mozilla 上述命令显示有一个正在运行的 mozilla 进程的 PID 是 32558。相对应的,/proc 中应该有一个名叫 32558 的目录
/proc/self 是一个有趣的子目录,它使得程序可以方便地使用 /proc 查找本进程地信息。/proc/self 是一个链接到 /proc 中访问 /proc 的进程所对应的 PID 的目录的符号链接。
通过 /proc 与内核交互
上面讨论的大部分 /proc 的文件是只读的。而实际上 /proc 文件系统通过 /proc
中可读写的文件提供了对内核的交互机制。写这些文件可以改变内核的状态,因而要慎重改动这些文件。/proc/sys
目录存放所有可读写的文件的目录,可以被用于改变内核行为。
/proc/sys/kernel - 这个目录包含反通用内核行为的信息。 /proc/sys/kernel/{domainname, hostname} 存放着机器/网络的域名和主机名。这些文件可以用于修改这些名字。
$ hostname machinename.domainname.com $ cat /proc/sys/kernel/domainname domainname.com $ cat /proc/sys/kernel/hostname machinename $ echo "new-machinename" > /proc/sys/kernel/hostname $ hostname new-machinename.domainname.com 这样,通过修改 /proc 文件系统中的文件,我们可以修改主机名。很多其他可配置的文件存在于 /proc/sys/kernel/。这里不可能列出所有这些文件,读者可以自己去这个目录查看以得到更多细节信息。结论
/proc 文件系统提供了一个基于文件的 Linux 内部接口。它可以用于确定系统的各种不同设备和进程的状态。对他们进行配置。因而,理解和应用有关这个文件系统的知识是理解你的 Linux 系统的关键。
参考文献
http://www.ibm.com/developerworks/cn/linux/l-proc.html
这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口 |
级别: 初级 M. Tim Jones (), 资深首席软件工程师, Emulex 2006 年 4 月 24 日 /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux® 内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对 /proc 虚拟文件系统进行了介绍,并展示了它的用法。 最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。 /proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。 尽管像本文这样短小的一篇文章无法详细介绍 /proc 的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下 /proc 是多么强大。清单 1 是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux 中创建的第一个进程是 init 进程,因此它的 process-id 为 1。然后对这个目录执行一个 ls 命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。例如,要查看 init 的 command-line 项的内容,只需对 cmdline 文件执行 cat 命令。 /proc 中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci,显示在 PCI 总线上找到的设备;modules,标识了当前加载到内核中的模块。
清单 2 展示了对 /proc 中的一个虚拟文件进行读写的过程。这个例子首先检查内核的 TCP/IP 栈中的 IP 转发的目前设置,然后再启用这种功能。
另外,我们还可以使用 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 然后又定义了这个模块的 init 和 cleanup 函数。my_module_init 函数是在加载这个模块时被调用的,它用来进行一些初始化方面的工作。my_module_cleanup 函数是在卸载这个模块时被调用的,它用来释放内存并清除这个模块的踪迹。注意此处 printk 的用法:这是内核的 printf 函数。KERN_INFO 符号是一个字符串,可以用来对进入内核回环缓冲区的信息进行过滤(非常类似于 syslog)。 最后,清单 3 使用 module_init 和 module_exit 宏声明了入口函数和出口函数。这样我们就可以按照自己的意愿来对这个模块的 init 和 cleanup 函数进行命名了,不过我们最终要告诉内核维护函数就是这些函数。
清单 3 尽管非常简单,但它却是一个真正的 LKM。现在让我们对其进行编译并在一个 2.6 版本的内核上进行测试。2.6 版本的内核为内核模块的编译引入了一种新方法,我发现这种方法比原来的方法简单了很多。对于文件 simple-lkm.c,我们可以创建一个 makefile,其惟一内容如下:
要编译 LKM,请使用 make 命令,如清单 4 所示。
结果会生成一个 simple-lkm.ko 文件。这个新的命名约定可以帮助将这些内核对象(LKM)与标准对象区分开来。现在可以加载或卸载这个模块了,然后可以查看它的输出。要加载这个模块,请使用 insmod 命令;反之,要卸载这个模块,请使用 rmmod 命令。lsmod 可以显示当前加载的 LKM(参见清单 5)。
注意,内核的输出进到了内核回环缓冲区中,而不是打印到 stdout 上,这是因为 stdout 是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用 dmesg 工具(或者通过 /proc 本身使用 cat /proc/kmsg 命令)。清单 6 给出了 dmesg 显示的最后几条消息。
可以在内核输出中看到这个模块的消息。现在让我们暂时离开这个简单的例子,来看几个可以用来开发有用 LKM 的内核 API。 内核程序员可以使用的标准 API,LKM 程序员也可以使用。LKM 甚至可以导出内核使用的新变量和函数。有关 API 的完整介绍已经超出了本文的范围,因此我们在这里只是简单地介绍后面在展示一个更有用的 LKM 时所使用的几个元素。 要在 /proc 文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的位置。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry 的原型和 proc_dir_entry 结构中的一部分如清单 7 所示。
稍后我们就可以看到如何使用 read_proc 和 write_proc 命令来插入对这个虚拟文件进行读写的函数。 要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc 文件系统中的位置(parent)。这个函数原型如清单 7 所示。 parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。表 1 列出了可以使用的其他一些父 proc_dir_entry,以及它们在这个文件系统中的位置。
我们可以使用 write_proc 函数向 /proc 中写入一项。这个函数的原型如下:
filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff 参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len 参数定义了在 buff 中有多少数据要被写入。data 参数是一个指向私有数据的指针(参见 清单 7)。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。 Linux 提供了一组 API 来在用户空间和内核空间之间移动数据。对于 write_proc 的情况来说,我们使用了 copy_from_user 函数来维护用户空间的数据。 我们可以使用 read_proc 函数从一个 /proc 项中读取数据(从内核空间到用户空间)。这个函数的原型如下:
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 所示。
下面是一个可以支持读写的 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。
向这个罐中新写入一个 cookie 非常简单(如清单 10 所示)。使用这个写入 cookie 的长度,我们可以检查是否有这么多空间可用。如果没有,就返回 -ENOSPC,它会返回给用户空间。否则,就说明空间存在,我们使用 copy_from_user 将用户缓冲区中的数据直接拷贝到 cookie_pot 中。然后增大 cookie_index(基于用户缓冲区的长度)并使用 NULL 来结束这个字符串。最后,返回实际写入 cookie_pot 的字符的个数,它会返回到用户进程。
对 fortune 进行读取也非常简单,如清单 11 所示。由于我们刚才写入数据的缓冲区(page)已经在内核空间中了,因此可以直接对其进行操作,并使用 sprintf 来写入下一个 fortune。如果 next_fortune 索引大于 cookie_index(要写入的下一个位置),那么我们就将 next_fortune 返回为 0,这是第一个 fortune 的索引。在将这个 fortune 写入用户缓冲区之后,在 next_fortune 索引上增加刚才写入的 fortune 的长度。这样就变成了下一个可用 fortune 的索引。这个 fortune 的长度会被返回并传递给用户。
从这个简单的例子中,我们可以看出通过 /proc 文件系统与内核进行通信实际上是件非常简单的事情。现在让我们来看一下这个 fortune 模块的用法(参见清单 12)。
/proc 虚拟文件系统可以广泛地用来报告内核的信息,也可以用来进行动态配置。我们会发现它对于驱动程序和模块编程来说都是非常完整的。 |