【摘要】本文介绍了Linux proc文件系统的一些细节。首先介绍了proc文件系统的基本概念/存储位置/读取时机/加载方式,接着介绍了如何利用proc文件系统读取系统/内核中的各种信息以及进程相关的各种信息,分析了各个文件的作用及可能对应的系统调用命令等。最后介绍了驱动程序及内核模块如何利用proc 文件系统向用户空间提供调试接口输出调试信息。
【关键字】Proc文件系统,Procfs,伪文件系统,内核属性,调试接口
1 /proc虚拟文件系统
Linux 内核提供了一种通过 /proc 文件系统在运行时访问内核内部数据结构、改变内核设置的机制和接口。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间,其为基于随机访问存储器 (RAM) 的文件系统,类似系统还有tmpfs,swapfs等。/proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 (所以叫做 /proc)。这个伪文件系统让你可以和内核内部数据结构进行交互,获取有关进程的有用信息,在运行中改变设置 (通过改变内核参数)。与其他文件系统不同,/proc 存在于内存之中而不是硬盘上。如果你察看文件 /proc/mounts (和 mount 命令一样列出所有已经加载的文件系统),你会看到其中一行是这样的:
grep proc /proc/mounts
/proc /proc proc rw 0 0
/proc 由内核控制,没有承载 /proc 的设备。因为 /proc 主要存放由内核控制的状态信息,所以大部分这些信息的逻辑位置位于内核控制的内存,通常是动态改变的,。对 /proc 进行一次 'ls -l' 可以看到大部分文件都是 0 字节大的;不过察看这些文件的时候,确实可以看到一些信息。这怎么可能?这是因为 /proc 文件系统和其他常规的文件系统一样把自己注册到虚拟文件系统层 (VFS) 了。然而,直到当 VFS 调用它,请求文件、目录的 i-node 的时候,/proc 文件系统才根据内核中的信息建立相应的文件和目录,其动态的从系统内核读出所需信息并提交。
2 加载 proc 文件系统
一般系统启动时会自动加载proc文件系统,在fstab中指定待加载的各种文件系统
/dev/root / ext2 ro 0 0
/dev/ram0 /tmp ext2 defaults 0 0
none /proc proc defaults 0 0
none /dev/pts devpts mode=0622 0 0
第一行为实际的承载物理设备,由于proc无需实际内存存储,故none可为任意
如果系统中还没有加载 proc 文件系统,可以通过如下命令加载 proc 文件系统:
mount -t proc proc /proc
上述命令将成功加载你的 proc 文件系统。更多细节请阅读 mount 命令的 man page。
3 察看 /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
这是一个从双 CPU 的系统中得到的结果,上述大部分的信息十分清楚地给出了这个系统的有用的硬件信息。有些 /proc 的文件是经过编码的,不同的工具可以被用来解释这些编码过的信息并输出成可读的形式。这样的工具包括:'top', 'ps', 'apm'以及 uptime等。
4 得到有用的系统/内核信息
proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。/proc 下的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容。下面是一些重要的文件:
目录名称 目录内容
apm 高级电源管理信息
cmdline系统启动时输入的内核命令行参数
Cpuinfo CPU 的信息 (型号, 家族, 缓存大小等)
Devices主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备)
Dma 使用的DMA通道
Filesystems当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统
Interrupts 中断的使用及触发次数,调试中断时很有用
Ioports I/O当前在用的已注册 I/O 端口范围
Kcore该伪文件以 core 文件格式给出了系统的物理内存映象,可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB
Kmsg可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令
Ksyms 内核符号表,该文件保存了内核输出的符号定义, modules(X) 使用该文件动态地连接和捆绑可装载的模块
Loadavg 负载均衡,平均负载数给出了在过去的 1, 5, 15 分钟里在运行队列里的任务数
Locks 内核锁
Meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令
Misc 杂项
Modules 已经加载的模块列表,对应lsmod命令
Mounts已加载的文件系统的列表,对应mount命令,无参数
Partitions 系统识别的分区表
Rtc 实时时钟
Slabinfo Slab池信息
Stat 全面统计状态表,对应ps命令
Swaps 对换空间的利用情况
Version指明了当前正在运行的内核版本,对应uname –v,例如:
Linux version 1.0.9 (
[email=quinlan@phaze]quinlan@phaze[/email]
) #1 Sat May 14 01:51:54 EDT 1994
Uptime 系统正常运行时间,对应date命令
proc 中的文件远不止上面列出的这么多。想要进一步了解的读者可以对 /proc 的每一个文件都'more'一下
并不是所有这些目录在你的系统中都有,这取决于你的内核配置和装载的模块。另外,在/proc下还有三个很重要的目录:net,scsi和 sys。Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则 scsi目录不存在。
net
该子目录包括多个 ASCII 格式的网络伪文件, 描述了网络层的部分情况。 可以用 cat 来察看这些文件, 但标准的 netstat(8) 命令组更清晰地给出了这些文件的信息
arp
该文件以 ASCII 格式保存了内核 ARP 表, 用于地址解析, 包括静态和动态 arp 数据. 文件格式如下:
IP address HW type Flags HW address
10.11.100.129 0x1 0x6 00:20:8A:00:0C:5A
10.11.100.5 0x1 0x2 00:C0:EA:00:00:4E
44.131.10.6 0x3 0x2 GW4PTS
其中 'IP address' 是机器的 IPv4 地址; 'HW type' 是地址的硬件类型, 遵循 RFC 826; flags 是 ARP 结构的内部标志, 在 /usr/include/linux/if_arp.h 中定义; 'HW address' 是该 IP 地址的物理层映射(如果知道的话).
dev
该伪文件包含网络设备状态信息, 给出了发送和收到的包的数目, 错误和冲突的数目, 以及别的一些基本统计数据. ifconfig(8) 利用了该文件来报告网络设备状态. 文件格式如下:
Interface | Receive | Transmit|packets errs drop fifo frame|packets errs drop fifo colls carrier
lo: 0 0 0 0 0 2353 0 0 0 0 0
eth0: 644324 1 0 0 1 563770 0 0 0 581 0
rarp
该文件具有和 arp 同样的格式, 包含当前的逆向地址映射数据. rarp(8) 利用这些数据来作逆向地址查询服务. 只有将 RARP 配置进内核, 该文件才存在.
route
没有信息, 但是看上去类似于 route(8)
tcp
该文件保存了 TCP 套接字表, 大部分信息除用于调试以外没有什么用. "local address" 包括本地地址和端口号; "remote address" 包括远地地址和端口号(如果有连接的话); 'tx_queue' 和 'rx_queue' 是内核存储器使用意义上的输入输出数据队列;
udp
该文件保存了 UDP 套接字表, 大部分信息除用于调试以外没有什么用
5 有关运行中的进程的信息
/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 的目录
$ ls -l /proc/32558
total 0
-r--r--r-- 1 root root 0 Dec 25 22:59 cmdline
-r--r--r-- 1 root root 0 Dec 25 22:59 cpu
lrwxrwxrwx 1 root root 0 Dec 25 22:59 cwd -> /proc/
-r-------- 1 root root 0 Dec 25 22:59 environ
lrwxrwxrwx 1 root root 0 Dec 25 22:59 exe -> /usr/bin/mozilla*
dr-x------ 2 root root 0 Dec 25 22:59 fd/
-r--r--r-- 1 root root 0 Dec 25 22:59 maps
-rw------- 1 root root 0 Dec 25 22:59 mem
-r--r--r-- 1 root root 0 Dec 25 22:59 mounts
lrwxrwxrwx 1 root root 0 Dec 25 22:59 root -> //
-r--r--r-- 1 root root 0 Dec 25 22:59 stat
-r--r--r-- 1 root root 0 Dec 25 22:59 statm
-r--r--r-- 1 root root 0 Dec 25 22:59 status
目录名称 目录内容
Cmdline 命令行参数,包含启动进程时调用的命令行,如果该进程已经被交换出内存, 或者该进程已经僵死, 那么就没有任何东西在该文件里, 这时候对该文件的读操作将返回零个字符
Environ该文件保存进程的环境变量, 各项之间以空字符分隔
Fd进程所打开的每个文件都有一个符号连接在该子目录里, 以文件描述符命名, 这个名字实际上是指向真正的文件的符号连接
Mem 进程的内存被利用情况
Stat 进程状态
Status 进程当前状态,包括启动进程的用户的用户ID (UID) 和组ID(GID) ,父进程ID (PPID),还有进程当前的状态,比如"Sleelping"和"Running"
Cwd一个符号连接, 指向进程当前的工作目录。例如, 要找出进程 20 的 cwd, cd /proc/20/cwd; /bin/pwd
Exe 指向该进程的执行命令文件
Maps该文件包含当前的映象内存区及他们的访问许可
Statm 进程内存状态信息
Root 链接此进程的root目录
self 当某进程访问 /proc 目录时, 该目录就指向 /proc 下以该进程 ID 命名的目录.
6 通过 /proc 改变内核属性
上面讨论的大部分 /proc 的文件是只读的。而实际上 /proc 文件系统通过 /proc 中可读写的文件提供了对内核的交互机制。写这些文件可以改变内核的状态,因而要慎重改动这些文件。/proc/sys 目录存放所有可读写的文件的目录,可以被用于改变内核行为。要改变内核的参数,只要用vi编辑或echo参数重定向到文件中即可。
/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/sys/net。这个目录中的文件可以用于修改机器/网络的网络属性。比如,简单修改一个文件,你可以在网络上隐藏你的计算机。
$ echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
这将在网络上瘾藏你的机器,因为它不响应 icmp_echo。主机将不会响应其他主机发出的 ping 查询。
$ ping machinename.domainname.com
no answer from machinename.domainname.com
要改回缺省设置,只要
$ echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
/proc/sys 下还有许多其它可以用于改变内核属性的条目,如# echo 8 > /proc/sys/kernel/printk修改控制台显示消息的级别。
7 模块如何利用 /proc输出调试信息
一些设备驱动也通过 /proc 输出信息, /proc 文件系统是动态的, 因此模块可以在任何时候添加或去除条目。模块可以通过此接口向用户空间传递一些调试信息,这样便于用户分析问题,避免通过控制台频繁打印信息,只有在需要的时候才去读取对应的信息。
所有使用 /proc 的模块应当包含 来定义正确的函数。要创建一个只读 /proc 文件,驱动必须实现一个函数来在文件被读时产生数据。当某个进程读文件时(使用 read 系统调用), 这个请求通过这个函数到达你的模块
当一个进程读你的 /proc 文件, 内核分配了一页内存(就是说, PAGE_SIZE 字节), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个称为 read_proc 的方法:
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data)
page 指针是你写你的数据的缓存区; start 是这个函数用来说有关的数据写在页中哪里; offset 和 count 对于 read 方法有同样的含义. eof 参数指向一个整数, 必须由驱动设置来指示它不再有数据返回, data 是驱动特定的数据指针, 你可以用做内部用途
一旦你有一个定义好的 read_proc 函数, 你应当连接它到 /proc 层次中的一个入口项。使用一个 creat_proc_read_entry 调用:
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);
这里name 是要创建的文件名子, mod 是文件的保护掩码(缺省系统范围时可以作为 0 传递), base 指出要创建的文件的目录( 如果 base 是 NULL, 文件在 /proc 根下创建 ), read_proc 是实现文件的 read_proc 函数, data 被内核忽略( 但是传递给 read_proc)
create_proc_read_entry("scullmem", 0 /* default mode */,
NULL /* parent dir */, scull_read_procmem,
NULL /* client data */);
这里, 我们创建了一个名为 scullmem 的文件, 直接在 /proc 下, 带有缺省的, 全局可读的保护,文件为/proc/scullmem
但是按照上述create_proc_read_entry实现方式在 /proc 下的大文件的实现有点麻烦。作为一种清理 /proc 代码以及使内核开发者活得轻松些的方法, 添加了 seq_file 接口。这个接口提供了简单的一套函数来实现大内核虚拟文件。
seq_file 接口假定你在创建一个虚拟文件, 它涉及一系列的必须返回给用户空间的项。为使用 seq_file, 你必须创建一个简单的 "iterator" 对象, 它能在序列里建立一个位置, 向前进, 并且输出序列里的一个项.
使用seq_file 接口需包含 ,接着你必须创建 4 个 iterator 方法, 称为 start, next, stop, 和 show。在这些调用中, 内核调用 show 方法来真正输出有用的东西给用户空间。这个方法的原型是:
int show(struct seq_file *sfile, void *v);
现在已有了一个完整的 iterator 操作的集合, scull 必须包装起它们, 并且连接它们到 /proc 中的一个文件. 第一步是填充一个 seq_operations 结构:
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
但是我们必须创建一个内核理解的文件实现. 我们不使用前面描述过的 read_proc 方法;在使用 seq_file 时, 最好在一个稍低的级别上连接到 /proc。那意味着创建一个 file_operations 结构来实现所有内核需要的操作, 来处理文件上的读和移动
创建一个 open 方法连接文件到 seq_file 操作:
static int scull_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}
调用 seq_open 连接文件结构和我们上面定义的序列操作。事实证明, open 是我们必须自己实现的唯一文件操作, 因此我们现在可以建立我们的 file_operations 结构:
static struct file_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
这里我们指定我们自己的 open 方法, 但是使用预装好的方法 seq_read, seq_lseek, 和 seq_release
.
最后的步骤是创建 /proc 中的实际文件:
entry = create_proc_entry("scullseq", 0, NULL);
if (entry)
entry->proc_fops = &scull_proc_ops;
不是使用 create_proc_read_entry, 我们调用低层的 create_proc_entry, 我们有这个原型:
struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent);
有了上面代码, scull 有一个新的 /proc/ scullseq 入口,它是高级的, 因为它不管它的输出有多么大, 它正确处理移动, 并且通常它是易读和易维护的。详细的实现方式可以参考LDD3的调试章节。也可以参考内核中的现成代码,如\kernel\kallsyms.c
8 参考鸣谢
http://blog.chinaunix.net/u/19412/showart_279883.html
http://tb.donews.net/TrackBack.aspx?PostId=313190
/proc虚拟文件系统
理解 Proc 文件系统
LDD3,linux_farmer
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/94284/showart_1993647.html