9.2 procfs

procfs之于Linux的重要程度就好比眼睛之于心。眼睛是心灵的窗口,直达心底;眼睛是心灵感知世界的大门,洞悉全局;眼睛是人与人之间心灵沟通的桥梁,展现彼此。

procfs是进程文件系统的缩写。这是一个伪文件系统(启动时动态生成的文件系统),用于用户空间通过内核访问进程信息。但是经过不断的演进,如今Linux提供的procfs已经不单单用于访问进程信息,还是一个用户空间与内核交换数据修改系统行为的接口。这个文件系统通常被挂接到/proc目录。

procfs并不是Linux的原创,它源自于UNIX世界,现在世上几乎所有类UNIX系统都提供。可能是历史太过悠久,如今好多人开始讨厌它,排挤它,发明了如sysfs这样的东西想要替代它。由于FreeBSD已经放弃了procfs,它默默的承受着:早就说分手,从未被遗弃的命运。因为procfs就像气质非凡的美女,虽然朱颜老去,但内在的神韵一直吸引着我们,无法抗拒。

9.2.1 神秘的9号计划

procfs最早在UNIX8版实现,后来又移植到了SVR4,最后由一个被称为“9号计划”的项目做了大量改进,使得/proc成为文件系统真正的一部分。“9号计划”是贝尔实验室创造的另外一个操作系统。这是一个“高尚”的操作系统,一个“纯粹”的操作系统,一个“有道德”的操作系统,一个“脱离了低级趣味”的操作系统,一个“有益于人民”的操作系统。

很久很久以前,贝尔实验室的一群人创造了至今最为重要的网络操作系统——UNIX。曾经有人说过:即便这是贝尔实验室做出的唯一贡献,那也足以让它名垂千古了!到上世纪80年代中期,计算的趋势从大的集中式的分时计算向更小的个人机器组成的网络方向转移。人们早已厌倦了既受管束又超载的分时机器,极其渴望使用一种小巧而又自由的系统,缺点就是有点慢。随着微型计算机越来越快,唯一的缺点也可以无视了,于是这种计算方式一直延续到了现在。UNIX是一个古老的分时系统,很难适应这种计算方式。即便可以让UNIX支持图形和网络功能,但办法有点糟,很难管理。更要命的是,这种集中到分散的转化无法做到无缝过度,因为分时是专政和资源集中化,个人计算是民主和资源分散化,而且是从根本上扩大了管理问题。于是,有一些愤青(包括Dennis RitchieKen Thompson),决心依靠自己的经验,超越UNIX,编写出最完美的操作系统,这就是他们的“9号计划”。

这是一个“高尚”的操作系统。它的“高尚”在于“对人无所求,给人的确是极好的东西”。它完整的源代码可以免费的在朗讯公共许可证1.02版的授权之下取得,而且被开放源代码促进会认为是开放源代码软件,被自由软件基金会认为是自由软件。

这是一个“纯粹”的操作系统。它的“纯粹”在于这么多年来仍作为一个“概念型”的系统存在。它一开始就作为一个完全的网络操作系统被设计。所以在“9号计划”背后的概念更多的是和网络而不是单个用户的需要相关,它因此而沦落成一种研究用的工具就一点都不奇怪了。所以有人调侃:“这不过是一个操作系统领域用来产生有趣论文的装置。”

这是一个“有道德”的操作系统。它的“有道德”在于它的代码是从底层写起的,并没有包含任何他人的代码。咋看起来它确实和UNIX极为相似,但“9号计划”并不是UNIX,也不是它的变种,这是一个完完全全的新操作系统。只是操作界面上受到了UNIX的很大影响。二者在底层的工作方式完全不同,“9号计划”最基本的概念是一切皆文件,此技术在UNIX下也有利用,但是远没有发展到“9号计划”的那种程度。

这是一个“脱离了低级趣味”的操作系统。它的“脱离了低级趣味”在于不提倡包干到底,而是要构建一个分工合作的运算环境。比如:单独使用一台具有极强运算能力的计算机用来为远程终端提供运算服务,即专门的CPU服务器;同时另有一台专门的的机器用来完成存储所有文件的任务,即专门的文件服务器。这样设计的好处就是能够获得管理上的便利和更高的安全性。今天的“云计算”,跟这种思想极为相似。

这是一个“有益于人民”的操作系统。它的“有益于人民”在于其虽并未像UNIX一样热门,但是它的精神一直在指引着后继各种操作系统前进的方向。比如微内核概念,Windows NTMac OS X受益颇多;/proc即我们在讲的procfs的前身,使得我们与系统内核通讯跟读写文件一样简单;提出“网络通透性”概念是现今所有分布式文件系统所追求的目标;引入Unicode编码机制,是目前应用最为广泛的文字编码之一。

虽然“9号计划”在2002年宣告终止了,但至目前为止仍在某些领域或被部分业余爱好者当成研究、开发或者使用的操作系统。它最引人注意的地方在于其本身代表了所有的系统接口,除了特殊的接口外,包含了网络接口、用户接口、文件系统接口等。

9.2.2 /proc目录

如今的/proc目录已经变得很复杂很复杂,这也是开始排挤它、讨厌它、找人替代它的一个主要出发点。因为如今的procfs已经无法满足UNIXKISS文化中简单这一条了。但是由于历史原因,至今也无法找到一个更好的办法来完全替代它。那么既然无法反抗,就只能默默享受吧。

大多数情况下,你在/proc目录下能够看到的文件差不多就是表9-1所列出的这些:

9-1 /proc目录下的文件

名称

功能

名称

功能

apm

高级电源管理信息

loadavg

负载均衡信息

buddyinfo

Buddy算法内存分配信息

locks

内核锁

compline

内核的命令行参数

mdstat

磁盘阵列状态

config.gz

当前内核的.config文件

meminfo

内存信息

cpuinfo

cpu信息

misc

杂项信息

devices

可以用到的设备(块设备/字符设备)

modules

系统已经加载的模块文本列表

diskstats

磁盘I/O统计信息

mounts

已挂接的文件系统列表

dma

使用的DMA通道

partitions

磁盘分区信息

execdomains

执行区域列表

pci

内核识别的PCI设备列表

fb

Frame buffer信息

self

访问proc文件系统的进程信息

filesystems

支持的文件系统

slabinfo

内核缓存信息

Interrupt

中断的使用情况,记录中断产生次数

splash

splash信息

iomem

I/O内存映射信息

stat

全面统计状态表

ioports

I/O端口分配情况

swaps

交换空间使用情况

kcore

内核核心映像,GDB可以利用它查看当前内核的所有数据结构状态

uptime

系统正常运行时间

key-users

密钥保留服务文件

version

内核版本

kmsg

内核消息

vmstat

虚拟内存统计表

ksyms

内核符号表

zoneinfo

内存管理区信息

除了可能会有这些文件外,/proc目录下还有好多目录。大多数系统会有表9-2所列出这些目录内容:

9-2 /proc目录下的子目录

名称

功能

名称

功能

[number]

进程信息

irq

中断请求设置接口

acpi

高级配置与电源接口

net

网络各种状态信息

asound

ALSA声卡驱动接口

scsi

SCSI设备信息

bus

系统中已安装的总线信息

sys

内核配置接口

dirver

驱动信息

sysvipc

中断的使用情况,记录中断产生次数

fs

文件系统特别信息

tty

tty驱动信息

Ide

IDE设备信息



这里很重要的是[number]这些目录,每个进程一个目录,目录名就是进程ID。里面包含了一些文件,这些文件描述着一个进程的方方面面,这是procfs最初目的的体现。这些文件都是只读的,你不能修改,仅用于获得系统中进程的运行信息。典型的工具如topps等,就是依据这些目录中的文件所提供的内容进行工作的。

这里有一个特别的目录就是sys目录,它所包含的文件大多是可以写的,通过改写这些文件的内容,可以起到修改内核参数的目的。实际上系统命令sysctl就是利用这个目录实现的全部功能。使用C语言编程时,系统调用sysctl是这个接口的封装。

9.2.3 procfs实战

对于procfs我说的就这么多,我们从实战中可以体会到更多东西,这部分我选择少说多练。

1.中断平衡

从前,在乡下的时候,是不用排队的,村里的人都很谦让,而且人本来就不多。后来到了县城,县城不大,走亲戚串门或去逛街不用坐车也不用排队,除了街上的游戏厅人多一点外,别的地方人都是不多的,陪妈妈去菜市场买菜也不用排队。后来,到了北京,发现去食堂吃饭要排队,开学报道要排队,在德胜门做345回学校更要排队。考试挂科去教务处交重修费要排队,甚至连追求一个女孩子也要排队,每次看见人群排成一条长龙时,才真正意识到自己是龙的传人。

其实所有的排队,都是因为资源有限,为了公平所做的一种妥协。但是还有一种现象:在机场,你已经坐在飞机上了,跟你一起的其它飞机有序地等待着有限的跑道资源,时间一分一秒的过去,你的飞机已经晚点一个多小时了。突然你通过舷窗,看到一架飞机直奔云霄,可是你却发现,那架飞机本来是应该排在你后面的,只是因为上面坐着领导。这种情况对于我这种很阿Q的人来说,会念叨一句:“笨鸟先飞。”因为就是不公平了,你能怎么办呢?特权,这就是特权。其实不管你怎样想,特权其实是完全合理的,尤其在Linux的世界,合理的利用特权,可以给你的系统带来意想不到的性能提升。

一般情况下,一台计算机,只有一个CPU,所有设备为了获得CPU的青睐,就通过一种叫中断的机制来骚扰一下CPU。中断被划分成不同的等级,高级别的中断可以被CPU优先照顾,同等级别的中断就按照先后顺序排队处理,这在系统内部被称为中断请求队列。高级别中断相对于低阶别中断就有了一种特权。随着时代的变迁,单一CPU的设计遇到了瓶颈,无法再继续提高运算能力,计算机开始朝着多核和多CPU方向发展。当前主流的服务器配置都可以达到4CPU16核心。在这种情况下,相对于数量没有太多变化的外部设备来讲,CPU不算是一种稀缺资源,但是如何合理的将来自不同设备的中断请求划分给不同的CPU就成了一个新的问题。由此引入了一个新的概念,中断平衡。有了中断平衡,我们就又引入了一个新的特权,中断的CPU独享特权。即可以指定某颗具体的CPUCPU的某颗核心专门处理某个或某些中断请求。

大多数的主流Linux发行版都有一个默认的中断平衡策略。但是这些默认的中断平衡策略并不一定能够满足某个特定系统的性能需求,比如一个有着非常繁重的网络资源请求的系统。默认的策略是,网卡的中断请求在多CPU环境下,仅发给CPU0。在一些特定情况下,会导致CPU0的资源占用率达到了100%,而其他CPU资源却只有1~2%,甚至是0%。由于CPU0也要负责任务调度,那么遇到这种情况下,系统基本上就处于死机状态,无法继续正常工作了。解决的办法就是,让网卡把中断请求发给其他CPU,不过这也需要网卡配合才行,幸好现在大多数服务器所配备的网卡具备这个能力。

那么该如何操作呢?这里先要引入一个概念——中断的CPU亲缘性,即中断与哪些CPU亲缘。设置好中断的CPU亲缘关系,就可以让中断只发往那些它所亲缘的CPU

在进行这个设置之前,我们首先要搞清楚,我们的物理设备,到底使用的是那个中断,每个中断有一个唯一的编号,我们要找到这个编号。可以通过procfs/proc/interrupt文件来获得。这个文件中的内容差不多是这样的:

CPU0 CPU1

0: 34 0 IO-APIC-edge timer

1: 3435 0 IO-APIC-edge i8042

6: 3 0 IO-APIC-edge floppy

8: 0 0 IO-APIC-edge rtc0

9: 37614 0 IO-APIC-fasteoi acpi

12: 12139 0 IO-APIC-edge i8042

14: 0 0 IO-APIC-edge ata_piix

15: 0 0 IO-APIC-edge ata_piix

16: 30317 0 IO-APIC-fasteoi ahci

17: 3325 0 IO-APIC-fasteoi 82801BA-ICH2

18: 45 0 IO-APIC-fasteoi uhci_hcd:usb2

19: 0 0 IO-APIC-fasteoi ehci_hcd:usb1

21: 93253 0 IO-APIC-fasteoi prl_vtg

22: 26 0 IO-APIC-fasteoi prl_tg

23: 3259 0 IO-APIC-fasteoi eth0

文件的第一列就是中断号,第二列和第三列是所对应的CPU接收到的该中断的数量,最后一列则代表使用该中断的设备,至于倒数第二列,我们不用关心它。从这个例子中网卡,即eth0使用了23号中断。而且很明显的是只有CPU0接收到了中断,CPU1没有接到过。

找到了对应的中断号,我们可以开始设置它的CPU亲缘性了,具体的是设置procfs/proc/irq/[num]/smp_affinity文件的内容。这个路径下的[num]就是具体的中断号。smp_affinity文件非常简单,就一个十六进制数,一个位掩码。特定的位对应特定的CPU,这样,01就意味着只有第一个CPU(即CPU0)可以处理对应的中断,而0f(1111)就意味着四个CPU都会参与中断处理。这样,具体某个中断,可以根据实际系统需求,划分给不同的CPU进行处理。

让专门的CPU处理专门的中断可以让系统在某些情况下获得极大的性能提升。这也是现代多处理器系统管理上的一个重要观点。比如上面网卡的例子,可以在很大程度上提高系统的可用性和网络吞吐能力,面对突发情况由于CPU0依然没有太多负载,系统任务还可以调度,管理员就有机会登陆系统,采取必要的维护措施。

2.获取绝对路径

作为Linux程序员,一定会接触到各种各样的配置文件或者日志文件。而且自己写程序,很多时候也喜欢使用配置文件和输出日志文件。在Linux中,或者说类UNIX系统中,/etc目录是专门放置配置文件的地方,/var目录是专门放置日志文件的地方。但是写这两个目录一般需要root权限才行,作为小品级别的程序一般是不会考虑使用/etc/var目录的。因此,大多数人会选择跟程序相同的路径或一个跟程序相关的路径。这就引发了一个问题——如何确定配置文件或日志文件的路径。

方法一,使用相对路径。采用相对路径是比较容易也很容易想到的一种方法。通常情况下程序工作的也很好,调试起来也不会有问题。不过当程序投入使用后,会发现一个问题就是:要想正确执行这个程序,就必须进入这个程序所在的目录才行,否则就会找不到配置文件或者日志文件输出路径不对。想让自己的程序成为一个顺手工具,在任意路径下都能正确执行的希望就此破灭。究其原因是因为fopenopen等这些打开文件的函数或系统调用在使用相对路径时,默认的当前路径是程序的执行路径,而不是程序所在的路径。换句话说,你在什么路径下执行这个程序,那么相对路径就是相对于你当前所在的这个路径的。所以自然就会出问题了。可见,这种方法不是一个好方法。

方法二,绝对路径写死。既然采用相对路径会相对于当前执行路径的问题,那么干脆使用绝对路径。如果你准备使用/etc/var目录,并且具有root执行权限,这是一个不错的主意。但是你要是选择别的路径,就会出现×××烦。因为不同的管理员有不同的管理规则。比如有些管理员会考虑将一些用户程序统一保存在远程文件服务器中,通过NFS共享给所有其它需要这些程序的系统。这时候写死的绝对路径就会出问题,因为不同的系统所挂接的NFS磁盘的位置可能不同,那么你的程序就依然无法获得正确配置文件路径或日志文件输出路径。发生重名的时候,还可能带来更大的麻烦。可见,这依然不是一个好方法。

方法三,获取相对路径转化为绝对路径。具体思路就是,首先决定配置文件或日志文件相对于程序可执行文件的路径,然后在获取当前可执行文件的绝对路径相结合,即可得到一个新的绝对路径。这个绝对路径在任何情况下都是正确的,不会产生方法一或方法二的问题。该如何获得当前至可行文件的绝对路径呢?最简单直观的就是利用procfs

procfs有一个/proc/self文件,这实际上是一个神奇的连接。为什么说它神奇呢?是因为不同进程访问这个连接所指向的目标是不同的。为了说明它的神奇,我们先说明一下/proc/[number]目录。其中的[number]是系统中正在运行的进程的PID,只要启动一个新的进程,在/proc目录下就会有一个对应的[number]目录出现。那么访问/proc/self文件的进程所获得的连接目标就是对应的/proc/[pid]路径,即进程所属的进程描述信息目录中。

进程描述信息目录中有一个exe文件,即/proc/[pid]/exec文件,这也是一个连接,这个连接的目标是进程可执行文件的绝对路径。只要通过readlink系统调用就可以获得一个连接的目标,那么通过/proc/[pid]/exec文件,就可以获得指定pid进程的可执行文件的绝对路径。

获得了绝对路径之后,需要去掉相应的可执行文件名,然后结合相对路径,最终获得配置文件或日志文件的绝对路径。

需要注意的的是,你的程序如果是基于多进程方式,并且采用Linux守护进程模式运行的,那么一定要在主进程或调用daemon调用之前完成上述操作,否则将无法获得这个绝对路径。因为对于一个已经丧失亲生父母的孤儿来说,继父无法提供体贴入微的关怀,它的身心怎么会健全呢?

3.手工释放内存

我在新浪供职时,负责的产品线是新浪UC。在IM领域最为著名的视频聊天室非UC莫属。曾在国内的市场占有率高达90%,直接PQQ不在话下。虽然近年来情况有些飞流直下,但目前其婚恋交友区可以保证每天至少有一对新人成为合法化夫妻。

新浪很穷,真的很穷。UC视频聊天室在其鼎盛时期,最高同时在线人数超过60万,可是只有区区200多台2003年就开始服役的服务器。排除非业务服务器,平均下来,每台服务器要为至少3千人提供优良的语音视频服务,每台服务器的网口流量超过600Mbps。我曾多次建议并申请设备采购,但都被驳回。业务在发展,用户数量在不断的增加,后果就是故障率的显著增加。高企的故障率已经开始严重的影响了业务的持续发展。上头下来命令:如果不降低故障率,统统回家!

顿时倍感压力,呼吸困难。于是请各路大神出手帮忙。八路神仙,各显神通使出浑身解数,但是百思不得其钥匙。终于一位大神语出惊吓了一大帮人:你们的程序有内存泄漏,看,空闲内存已经没多少了。于是所有人都将焦点转移到了内存上面。顿时上下翻飞,齐心协力,发现的确任何一台服务器的空闲内存都所剩无几。这下问题大了,就连上头都派人来慰问我们的工作进展如何,并且放话:只要解决内存泄漏问题,今年给你们一等奖金。听到一等奖金,所有人比打鸡血还要兴奋百倍。那就开始吧。不过兴师动众了几个礼拜后,结论是程序没有内存泄漏,但是内存总是被莫名的占用后不被释放。是谁动了我的蛋糕?好吧,请我这个Linux伪专家出马吧。

我这里真有一个办法——手工释放内存。

操作方法是这样的:

1.使用free命令查看内存。

2.执行sync命令。

3.执行echo 3 >/proc/sys/vm/drop_caches

好了,这就操作完成了。再次使用free命令查看内存,会发现剩余内存已经具有惊人的数量了。于是内存真的没有问题了,一定奖金我们拿到了。

不过这个故事没有完。因为故障率就是没有降下来。用户还在抱怨,新业务无法拓展。不过有一个好的消息是,经过这么一折腾,上头居然批准了新设备采购计划。直到新设备上架之后,故障率才显著降低。不过这还不是这个实战的终结。

其实这是一个伪命题。

Linux中,这种手工释放内存的方式是根本解决不了什么内存泄漏问题的,而且还会严重影响系统性能。这是为什么呢?

我先来解释一下这三个步骤的作用:

第一步,使用free命令查看内存,这其实没有什么实际作用,就是做个前后对比;

第二步,执行sync命令,是为了确保文件系统的完整性(sync命令将所有未写的系统缓存写道磁盘中);

第三步,执行echo 3 > /proc/sys/vm/drop_caches就开始释放内存了。

这里说明一下/proc/sys/vm/drop_caches的作用:当写入1时,释放页面缓存;写入2时,释放目录文件和inodes;写入3时,释放页面缓存、目录文件和inodes。可见,整个操作过程就是释放磁盘缓存。

Linux系统与Windows在对待内存的问题上是持不同意见的。Linux会尽量使用内存来提高效率,free查看剩余内存小并不是说内存不够用,还应该看swap是否被大量使用了。实际项目的经验告诉我们,如果是因为应用程序有内存泄漏、溢出的问题,从swap的使用情况是可以比较快速判断的,查看剩余内存是没有意义的或十分困难的。

/proc/sys/vm/drop_caches是直到2.6.16以后的内核版本才开始提供的,我个人认为是内核开发团队对很多用户对Linux内存管理方面的疑问的一个妥协,对于是否需要使用这个接口,或向用户提供这个接口,我是持保留意见的。因为当你告诉一个用户,修改一个系统参数可以“释放内存”,剩余内存就多了,用户会怎么想?不会觉得这个操作系统“有问题”吗?

这个实战的目的就是想让大家了解,这个接口虽然有提供,但是不要用,因为真的没用。

9.2.4 小结

FreeBSD的开发者们看来,procfs已经开始背弃了UNIXKISS文化,放弃了procfs的实现。不过既然我们是来说Linux的事儿的,就必须得说procfs,因为它真的很有用。至于将来procfs会怎样,谁也说不准。

Linux已经开始引入devfssysfs了,这两个怪物有觊觎procfs地位之嫌。其中一个已经未老先衰,另一个风华正茂。我在后面会对它们进行介绍。不过我不想这么连续的跟大家讲述这么多跟系统底层相关的部分,毕竟太过乏味。我们先歇歇脚,体验一下tmpfs带来的风驰电掣。待轻松之余,我们再去回味一下“底层”工作者们的那份凄凉吧。