Linux系统中的/linux proc文件系统统有什么用?

Linux内核中的proc文件系统
procfs文件系统是内核中的一个特殊文件系统。它是一个虚拟文件系统: 它不是实际的存储设备中的文件,而是存在于内存中。procfs中的文件是用来允许用户空间的程序访问内核中的某些信息(比如进程信息在 /proc/[0-9]+/中),或者用来做调试用途(/proc/ksyms,这个文件列出了已经登记的内核符号,这些符号给出了变量或函数的地址。每行给出一个符号的地址,符号名称以及登记这个符号的模块。程序ksyms、insmod和kmod使用这个文件。它还列出了正在运行的任务数,总任务数和最后分配的PID。)
这个文档描述了内核中procfs文件系统的使用。它以介绍所有和管理文件系统相关的函数开始。在函数介绍后,它还展示了怎么和用户空间通信,和一些小技巧。在文档的最后,还给出了一个完整的例子。
注意/proc/sys中的文件属于sysctl文件,它们不属于procfs文件系统,被另外一套完全不同的api管理。
procfs在处理大文件时有点笨拙。为了清理procfs文件系统并且使内核编程简单些,引入了seq_file机制。seq_file机制提供了大量简单的接口去实现大内核虚拟文件。
seq_file机制适用于你利用结构序列去创建一个返回给用户空间的虚拟文件。要使用seq_file机制,你必须创建一个”iterator”对象,这个对象指向这个序列,并且能逐个指向这个序列中的对象,此外还要能输出这个序列中的任一个对象。它听起来复杂,实际上,操作过程相当简单。接下来将用实际的例子展示到底怎么做。
首先,你必须包含头文件&Linux/seq_file.h&。接下来,你必须创建迭代器方法:start, next, stop, and show。
start方法通常被首先调用。这个方法的函数原型是:
void *start(struct seq_file *sfile, loff_t *pos);
sfile没什么作用,通常被忽略。pos参数是一个整型,表示从哪个位置开始读。关于位置的定义完全取决于函数实现;它不一定要是结果文件中的一个字节位置。 由于seq_file机制通常是利用一个特定的结构序列实现的,所以位置通常是一个指向序列中下一个结构体的指针。在wing驱动中,每一个设备表示序列中的一个结构,所以,入参pos代表g_pstWingDevices数组的索引。因此,在wing驱动中start方法的实现为:
static void *wing_seq_start(struct seq_file *s, loff_t *pos){ if (*pos &= g_iWingDevicesNum) return NULL; /* No more to read */ return g_pstWingDevices + *}
返回值如果不为NULL,代表一个可以被迭代器使用的私有数据。
next函数应该移动迭代器到下一个位置,如果序列中没有数据,返回NULL。这个方法的函数原型为:
void *next(struct seq_file *sfile, void *v, loff_t *pos);
这里,参数v代表上一个函数调用(可能是start函数,或者是next函数)返回的迭代器,, 参数pos是文件中的当前位置。next函数应该改变pos的指向,具体是逐步改变还是跳跃改变取决于迭代器的工作机制。next函数在wing驱动中的实现为:
static void* wing_seq_next(struct seq_file *s, void *v, loff_t *pos){ (*pos)++; if (*pos &= g_iWingDevicesNum) return NULL; return g_pstWingDevices + *}
当内核停止了迭代器的工作,它调用stop函数清理现场:
void stop(struct seq_file *sfile, void *v);
wing驱动没有清理工作要做,所以stop函数为空。
void wing_seq_stop(struct seq_file *sfile, void *v){}
要是seq_file代码在调用start和stop时不执行睡眠或是非原子的操作,那么这种机制将毫无意义。你要保证从start函数调用到stop函数调用是很短暂的。因此,在开始函数中获得一个信号量或者自旋锁是比较安全的做法。要是seq_file其他方法是原子的,整个调用链必须是原子的。
在这些函数调用中,内核调用call函数向内核空间输出特性的信息。这个函数的函数原型是:
int show(struct seq_file *sfile, void *v);
这个方法应该创建序列中由指示器v指定项的输出。不能使用printk,而是使用以下这些特定函数:
int seq_printf(struct seq_file *sfile, const char *fmt, ...)这个函数是seq_file机制中类似于printf的实现;它使用通常的格式字符串和参数组成输出字符串。你必须把show函数中的seq_file结构体传给这个函数。如果它返回一个非零的值,表示buffer已经填充好,输出被丢出去了。在大多数实现中,都选择忽略返回值。
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);这两个函数相当于用户层的putc和puts。
int seq_escape(struct seq_file *m, const char *s, const char *esc);这个函数是 seq_puts 的对等体, 除了 s 中的任何也在 esc 中出现的字符以八进制格式打印. esc 的一个通用值是”tn”, 它使内嵌的空格不会搞乱输出和可能搞乱 shell 脚本.
int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);这个函数能够用来输出和给定命令项关联的文件名子. 它在设备驱动中不可能有用;我们是为了完整在此包含它.
wing设备中的show函数例子:
static int wing_seq_show(struct seq_file *s, void *v){ ST_Wing_Dev_Type* pDev = (ST_Wing_Dev_Type* ) seq_printf(s, "nThis Device is %in", pDev-&iData); return 0;}
在我的例子中,我将一个ST_Wing_Dev_Type结构体表示为迭代器。
上面就是完整的迭代器操作,wing驱动必须将它们打包到一起好连接到procfs文件系统。首先要做的就是利用它们组成一个seq_operations结构体:
static struct seq_operations s_stWingSeqOps = { .start = wing_seq_start, .next = wing_seq_next, .stop = wing_seq_stop, .show = wing_seq_show};
有了这个结构,我们必须创建一个内核能理解的文件实现。我们不使用前面说过的read_proc方法;在使用seq_file时, 最好在一个稍低的级别上连接到procfs。这意味着创建一个file_operations(和字符设备一样的结构),这个结构实现了内核对文件的reads和seeks操作。幸运的是,这个操做很简单。首先创建一个把文件和seq_file方法联接起来的open方法:
static int wing_proc_open(struct inode *inode, struct file *file){ return seq_open(file, &s_stWingSeqOps);}
调用seq_open函数的时候将文件和上面定义的序列操作关联到一起。open是唯一要我们实现的函数接口,所以我们的file_operations结构体是:
static struct file_operations s_stWingProcFops = { .owner = THIS_MODULE, .open = wing_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release};
最后我们要在procfs文件系统中创建文件:
proc_create("wingdevices", 0644, NULL, &s_stWingProcFops); 关键结构体
struct proc_dir_entry代表的是/proc目录下的一个目录或者文件,他是procfs文件系统的主要结构体,它的定义在/fs/internal.h中:
/* * This is not completely implemented yet. The idea is to * create an in-memory tree (like the actual /proc filesystem * tree) of these proc_dir_entries, so that we can dynamically * add new files to /proc. * * The "next" pointer creates a linked list of one /proc directory, * while parent/subdir create the directory structure (every * /proc file has a parent, but "subdir" is NULL for all * non-directory entries). */struct proc_dir_entry { unsigned int low_ umode_ nlink_ kuid_ kgid_ loff_ const struct inode_operations *proc_ const struct file_operations *proc_ struct proc_dir_entry *next, *parent, * void * atomic_ /* use count */ atomic_t in_ /* number of callers int */ /* negative -& it's going away RSN */ struct completion *pde_unload_ struct list_head pde_ /* who did -&open, but not -&release */ spinlock_t pde_unload_ /* proc_fops checks and pde_users bumps */ u8 char name[];}; 主要接口
procfs应该包含的头文件&linux/proc_fs.h&。
在3.x内核中procfs主要接口有:
proc_symlink
proc_mkdir
proc_mkdir_data
proc_mkdir_mode
proc_create_data
proc_create
proc_set_size
proc_set_user
proc_get_parent_data
proc_remove
remove_proc_entry
remove_proc_subtree
proc_mkdir
说明:在/proc下创建目录
函数原型:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
name要创建的目录名称
parent父目录,如果为NULL,表示直接在/proc下面创建目录。
proc_mkdir_data
说明:在/proc下创建目录
函数原型:
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode, struct proc_dir_entry *parent, void *data)
name要创建的目录名称
mode指定要创建目录的权限
parent父目录,如果为NULL,表示直接在/proc下面创建目录。
proc_create_data
说明:创建proc虚拟文件系统文件
函数原型:
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data)
name你要创建的文件名。
mode为创建的文件指定权限
parent为你要在哪个文件夹下建立名字为name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
proc_fops为struct file_operations
data保存私有数据的指针,如不要为NULL。
////////////////////////test.c////////////////////////////////////////#include &linux/init.h& #include &linux/module.h& #include &linux/types.h& #include &linux/slab.h& #include &linux/fs.h& #include &linux/proc_fs.h& #include &linux/seq_file.h& #include &net/net_namespace.h& #include &linux/mm.h& MODULE_LICENSE("GPL"); typedef struct { int data1; int data2; }ST_Data_Info_T static ST_Data_Info_Type g_astDataInfo[2]; static int test_proc_show(struct seq_file *m, void *v){ ST_Data_Info_Type* pInfo = (ST_Data_Info_Type*)m-& if(pInfo != NULL) { seq_printf(m, "%d----%dn", pInfo-&data1, pInfo-&data2); } return 0;}static int test_proc_open(struct inode *inode, struct file *file) { return single_open(file, test_proc_show, PDE_DATA(inode));} static const struct file_operations dl_file_ops = { .owner = THIS_MODULE, .open = test_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release,}; static struct proc_dir_entry *s_pstRootTestDvoid init_mem(void) { /* create /proc/test */ s_pstRootTestDir = proc_mkdir("test", NULL); if (!s_pstRootTestDir) g_astDataInfo[0].data1=1; g_astDataInfo[0].data2=2; proc_create_data("proc_test1", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[0]); g_astDataInfo[1].data1=3; g_astDataInfo[1].data2=4; proc_create_data("proc_test2", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[1]); } static int __init test_module_init(void) { printk("[test]: module initn"); init_mem(); return 0; } static void __exit test_module_exit(void) { printk("[test]: module exitn"); remove_proc_entry("proc_test1", s_pstRootTestDir); remove_proc_entry("proc_test2", s_pstRootTestDir); remove_proc_entry("test", NULL);} module_init(test_module_init); module_exit(test_module_exit); proc_create
说明:创建proc虚拟文件系统文件
函数原型:
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops)
name你要创建的文件名。
mode为创建的文件指定权限
parent为你要在哪个文件夹下建立名字为name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
proc_fops为struct file_operations
注意:这个接口和proc_create_data的区别在于他不能保存私有数据指针。
获取proc_create_data传入的私有数据。
proc_symlink
说明:这个函数在procfs目录下创建一个从name指向dest的符号链接. 它在用户空间等效为ln -s dest name。
函数原型:
struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest)
name原始符号。
parent符号所在的目录。
dest所要创建的符号链接名字。
remove_proc_entry
说明:删除procfs文件系统中的文件或者目录。
函数原型:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
name要删除的文件或者目录名。
parent符号所在的目录,如果为NULL,表示在/proc目录下。
嵌入式Linux中文站
最专业的中文嵌入式Linux网站,8年磨剑,注册用户数万人!
分享嵌入式 & Linux技术干货、教程、资讯、高薪职位
分享点击右上角按钮
投稿admin@
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
今日搜狐热点==========以下对应文字版==========
使用 /proc 文件系统来控制系统来源:网络转载
日期: 15:51 --&级别:中级作者:Graham White(gwhite at )IT 专家,Hursley,IBM2003 年 8 月/proc 文件系统是 Linux 的优秀特性之?,本文向你周详讲述了他的?些最实用的基础知识。使用/proc,你再也不用关闭并重新引导机器来管理操作系统的许多细节问题,这对那些需求系统的可用性尽可能高的管理员来说非常有用。所有管理过具有商业重要性的系统的人都知道计算机正常运行时间的价值 - 或反过来讲,知道用户因故障时间会给你带来诸多头痛问题。公司采用 UNIX 服务器的主要原因之?是由于他的可靠性和稳定性。如果仔细管理,通常能非常长时间不需重启这些服务器。为了做到尽善尽美,你能实时执行?些管理任务,甚至是内核这?级别的任务,从而保持服务器的可用性。虽然因升级硬件或因某人踢掉电源线而仍需要重启系统,但了解到许多管理任务能在不干扰服务的情况下执行,总是有益的。本文提供了不必重新引导就能够执行关于各种管理任务和更改系统的提示和技巧。Linux 提供了各种方法,用以在保持系统正常运行的情况下,更改底层操作系统的值和设置。这些方法有两种基本形式,?种形式对于所有 Linux 系统都是通用的,并在 Linux 内核中提供这?形式(你能在Linux Kernel Archives 上查找更多关于 Linux 内核的信息和下载内核原始码;请参阅参考资料,里面有至 Linux Kernel Archives 的链接),更有?种形式是各分发版所独有的,并且由供给商提供。本文将讨论这两种方法。更改运行中的内核的参数Linux 向管理员提供了非常好的方法,使他们能在系统运行时更改内核,而不必重新引导内核/系统。这是通过 /proc 虚拟文件系统实现的。Linux Gazette 给出了?份有关 /proc 的参考,他是我所看到过的最简单且最容易的参考之?。(请参阅参考资料,其中有至这篇文章的链接。)/proc文件系统主要能让你查看运行中的内核,这?点对于监视性能、查找系统信息、了解系统是怎么设置的及更改该设置非常有用。该文件系统被称为虚拟文件系统,因为他实际上根本不是个文件系统。他只是内核提供的?个映射,被附加在通常的文件系统结构之上,从而使你能够访问他。我们能采用某种方法在系统正常运行的同时更改运行中的内核的参数,这?事实赋予了系统管理员在更改内核设置方面强大的能力和高的灵活性。这种实现是出自部分 Linux 内核研发人员富有灵感的想法。但能力太大会是?件坏事吗?有时确实如此。如果准备更改 /proc 文件系统中的所有内容,你必须确保自己知道在更改什么及这会对系统产生什么影响。这些技术确实有用,但错误的举动会带来完全不希望得到的结果。如果你不熟悉这方面的内容,或不确定你所做的某项更改会带来什么影响,那么请在?台对你或你公司不重要的机器上进行实践。怎么更改首先,考虑怎样做不会对内核进行更改。有两条充分的理由说明了为什么不能直接转换至 /proc 文件系统,用文本编辑器打开?个文件,做?系列更改,然后保存该文件,再退出。这两条理由是: 数据完整性:所有这些文件描述了运行中的系统,由于内核能随时更改这些文件中的所有?个,因此如果打开?个编辑器,然后更改某些数据,而同时,系统也正在底层更改这些数据,那么无论你保存下来的所有内容都不可能是内核所期望的内容。虚拟文件:所有这些文件实际上都不存在。怎么使保存的数据同步,等等?所以,解决办法是,不使用编辑器来更改所有这些文件。每当更改 /proc 文件系统中的所有内容时,应该使用 echo 命令,然后从命令行将输出重定向至 /proc 下所选定的文件中。例如:echo &Your-New-Kernel-Value& & /proc/your/file类似的,如果希望查看 /proc 中的信息,应该使用专门用于此用途的命令,或使用命令行下的 cat命令。更改什么要非常好地使用 /proc 不必你是?位内核方面的高手,只需基本了解这个文件系统的结构就能极大地帮助你。直到有?天用户向你询问某些特定的功能,使你非常高兴曾下功夫了解过在哪里进行更改,你才可能会觉得有必要知道关于 /proc 中的所有事情。在这方面,/proc 文件系统通过其结构和文件许可权帮助系统管理员。/proc 中的每个文件都有?组分配给他的非常特别的文件许可权,并且每个文件属于特定的用户标识。这?点实现得非常仔细,从而提供给管理员和用户正确的功能。下面这个列表汇总了各个文件上有哪些特定的许可权: 只读:所有用户都不能更改该文件;他用于表示系统信息root 写:如果 /proc 中的某个文件是可写的,则通常只能由 root 用户来写root 读:有些文件对?般系统用户是不可见的,而只对 root 用户是可见的其他:出于各种原因,你可能会看到不同于上面常见的三种许可权的组合关于 /proc,你会发现最通常的情况是,他的大多数文件是只读的,除了 /proc/sys 目录。该目录下存放着大多数的内核参数(而不是信息),并且设计成能在系统运行的同时进行更改。因
猜你还喜欢developerWorks 社区
/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux® 内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对 /proc 虚拟文件系统进行了介绍,并展示了它的用法。
(), 资深首席软件工程师, Emulex
M. Tim Jones 是一名嵌入式软件工程师,他是 GNU/Linux Application Programming、AI Application Programming 和 BSD Sockets Programming from a Multilanguage Perspective 等书的作者。他的工程背景非常广泛,从对地同步宇宙飞船的内核开发到嵌入式架构设计,再到网络协议的开发。Tim 是 Emulex Corp. 的一名资深软件工程师。
最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。/proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。尽管像本文这样短小的一篇文章无法详细介绍 /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
filesystems
sysrq-trigger
interrupts
partitions
execdomains
[root@plato 1]# ls /proc/1
[root@plato]# cat /proc/1/cmdline
[root@plato]#清单 2 展示了对 /proc 中的一个虚拟文件进行读写的过程。这个例子首先检查内核的 TCP/IP 栈中的 IP 转发的目前设置,然后再启用这种功能。清单 2. 对 /proc 进行读写(配置内核)[root@plato]# cat /proc/sys/net/ipv4/ip_forward
[root@plato]# echo "1" & /proc/sys/net/ipv4/ip_forward
[root@plato]# cat /proc/sys/net/ipv4/ip_forward
[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 然后又定义了这个模块的 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");
/* Cleanup function called on module exit */
void my_module_cleanup( void )
printk(KERN_INFO "my_module_cleanup called.
Module is now unloaded.\n");
/* 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'
/root/projects/misc/module2.6/simple/simple-lkm.o
Building modules, stage 2.
/root/projects/misc/module2.6/simple/simple-lkm.mod.o
/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
simple_lkm
yenta_socket
rsrc_nonstatic
1 yenta_socket
[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 *
// virtual file name
// mode permissions
// File's user id
// File's group id
struct inode_operations *proc_ // Inode operations functions
struct file_operations *proc_ // File operations functions
struct proc_dir_entry *
// Parent directory
read_proc_t *read_
// /proc read function
write_proc_t *write_
// /proc write function
// Pointer to private data
// 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/procproc_net/proc/netproc_bus/proc/busproc_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
static struct proc_dir_entry *proc_
static char *cookie_
// Space for fortune strings
static int cookie_
// Index to write next fortune
static int next_
// 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;
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");
cookie_index = 0;
next_fortune = 0;
proc_entry-&read_proc = fortune_
proc_entry-&write_proc = fortune_
proc_entry-&owner = THIS_MODULE;
printk(KERN_INFO "fortune: Module loaded.\n");
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 +=
cookie_pot[cookie_index-1] = 0;
}对 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 )
if (off & 0) {
/* Wrap-around */
if (next_fortune &= cookie_index) next_fortune = 0;
len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);
next_fortune +=
}从这个简单的例子中,我们可以看出通过 /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 source2KB
参考资料 您可以参阅本文在 developerWorks 全球站点上的 。
“”(developerWorks,2003 年 5 月)详细介绍了 /proc 的基础知识,包括如何管理操作系统的众多细节,而不用关闭或重新启动机器。
探索 /proc 文件系统中的 。
有关 Linux 内核 2.6 版本的
的文章详细讨论了内核模块的问题。
是有关 Linux 内核信息的一个很好站点。
文件系统是除 /proc 之外另外一个调试选择。
“” (developerWorks,2004 年 2 月)对构成 2.6 版本内核的工具、测试以及技术进行了深入介绍。
“” (developerWorks,2004 年 8 月)介绍了 Kprobes 如何与 2.6 版本的内核结合使用,提供一种轻量级的非破坏性的强大机制来动态插入 printk 函数。
printk 函数和 dmesg 方法都是用来进行内核调试的常用方法。Allessando Rubini 撰写的 Linux Device Drivers 一书提供了一章有关内核调试技术的 。
命令是另外一种实现动态内核配置的方法。
中可以找到为 Linux 开发人员准备的更多参考资料。
随时关注 。
上提供最新的 Linux 内核。
上可以找到。
包提供了很多有关内核模块的工具。
用可直接从 developerWorks 下载的
构建您的下一个开发项目。
developerWorks 社区。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
免费下载、试用软件产品,构建应用并提升技能。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=LinuxArticleID=109527ArticleTitle=使用 /proc 文件系统来访问 Linux 内核的内容publish-date=}

我要回帖

更多关于 proc文件系统编程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信