因为procfs是一个文件系统,它构建在虚拟文件系统VFS和Solaris vnode构架之上,文件系统实例作为一个VFS对象存在,其中的文件通过vnode描述。procfs创建VFS和vnode结构,通过它们可以针对procfs进行文件系统相关操作,比如mount和umount,可以针对/proc目录及文件对象进行open、read和write操作。
除了VFS和vnode结构,procfs实现中主要定义了两个数据结构用于描述/proc目录下文件对象。第一个是prnode结构(/usr/include/sys/proc/prdata.h),描述那些最终链接到vnode的文件系统相关数据。内核UFS实现定义了一个inode做为描述一个UFS文件的文件系统相关数据结构,类似的,内核procfs实现定义了一个prnode描述一个procfs文件。/proc目录下的每个文件有一个vnode和prnode对应。
第二个是prcommon结构,遍布整个/proc目录结构,换句话说,每个/proc/
和/proc//lwp/目录本身都对应一个prcommon结构,但是这些目录下的
文件对象并没有对应一个prcommon结构,因为访问这些文件对象时必然与一个确定的进程或者LWP相关。prcommon结构对这些目录下的文件对象共性进行抽象。prnode和prcommon结构定义在/usr/include/sys/proc/prdata.h文件中。参看图2。
--------------------------------------------------------------------------
/proc
\
\ prnode prcommon
+--+ +--+
+---------------------------------+ | | | |
| | +--+ +--+
| |
| +------------------------------------------------------+
| prnode prcommon | prnode prnode prnode per-process |
lwp +--+ +--+ | as +--+ cred +--+ psinfo +--+ ... file objects |
/ | | | | | | | | | | | |
/ +--+ +--+ | +--+ +--+ +--+ |
/ prnode prcommon +------------------------------------------------------+
+--+ +--+
\ | | | |
\ +--+ +--+
\
+-------------------------------------------------------------------------------+
| prnode prnode prnode prnode per-lwp |
| lwpctl +--+ lwpinfo +--+ lwpstatus +--+ lwpusage +--+ ... file objects |
| | | | | | | | | |
| +--+ +--+ +--+ +--+ |
+-------------------------------------------------------------------------------+
图2. prnode和prcommon结构定义
--------------------------------------------------------------------------
每个进程有自己的主/proc vnode(就是说这个vnode对应/proc/文件),进程中
每个LWP有自己的vnode对应/proc//lwp/文件,参看图3。
--------------------------------------------------------------------------
回指到proc结构
<-------------+
prnode prcommon |
+-------------+ +->+-------------+ +->+----------+ procdir |
| proc | | | pr_next ----|--+ | | prc_slot ---->+---------+ |
| structure | /proc | | pr_common --|--|-+ +----------+ | pe_proc --+
| +---------+ | /| | pr_files ---|--|----------------+ | pe_next |
| | | | vnode | | pr_vnode | | | +---------+
一 | | p_trace -------------->+---------+ | | | | pe_proc |
| | p_plist | | | | |vnode | | | | | pe_next |
个 | | | | | | |structure| | | | +---------+
| +---------+ | | | | | | | | | pe_proc |
多 | | +--|-|v_data | | | | | pe_next |
| kthread LWP | /proc | +---------+ | | | +---------+
线 | +---------+ | / +-------------+ | | | pe_proc |
| | | | /lwp/ vnode | | | pe_next |
程 | | t_trace -----+ prnode | | +---------+
| | | | | +->+-------------+<-+ | | pe_proc |
进 | +---------+ | | | | pr_next ----|--+ prcommon | | pe_next |
| | | | | pr_common --|--|--->+----------+| +---------+
程 | kthread LWP | | | | pr_files | | | prc_slot || | |
| +---------+ | | | | pr_vnode | | +----------+| | |
| | | | +-------->+---------+ | | | | |
| | t_trace -----+ | | |vnode | | | |
| | | | | | | |structure| | | |
| +---------+ | | | | | | | | +-->+---+ array of
+-------------+ | +--|-|v_data | | | | | pointers
| | +---------+ | | +---+ to vnodes
| +-------------+ | | | for all files
/proc//lwp | | +---+ within the
/ vnode | prnode | | | directory
| +->+-------------+<-+ .....
| | | pr_next | prcommon | |
| | | pr_common --|------>+----------+ +---+
| | | pr_files | | prc_slot | | |
| | | pr_vnode | +----------+ +---+
+-------->+---------+ |
| | |vnode | |
| | |structure| |
| | | | |
+--|-|v_data | |
| +---------+ |
+-------------+
图3. 一个多线程进程所涉及结构之间的关联
--------------------------------------------------------------------------
下面是在我的Sun工作站上找到的相应头文件内容:
/usr/include/sys/proc/prdata.h
typedef struct prnode
{
vnode_t * pr_next; /* list of all vnodes for process */
prcommon_t * pr_common; /* common data structure */
prcommon_t * pr_pcommon; /*
* process common data structure
* 和上面那个成员什么区别
*/
vnode_t ** pr_files; /* contained files array (directory) */
vnode_t pr_vnode; /* embedded vnode 这里不是指针 */
} prnode_t;
/*
* Common file object to which all /proc vnodes for a specific process
* or lwp refer. One for the process, one for each lwp.
*/
typedef struct prcommon
{
int prc_slot; /* process slot number */
} prcommon_t;
/usr/include/sys/vnode.h
/*
* All of the fields in the vnode are read-only once they are initialized
* (created) except for:
* v_flag: protected by v_lock
* v_count: protected by v_lock
* v_pages: file system must keep page list in sync with file size
* v_filocks: protected by flock_lock in flock.c
* v_shrlocks: protected by v_lock
*/
typedef struct vnode
{
caddr_t v_data; /* private data for fs */
} vnode_t;
/usr/include/sys/thread.h
typedef struct _kthread
{
struct vnode * t_trace; /* pointer to /proc lwp vnode */
} kthread_t;
图3演示了打开一个procfs文件进行读写时部分相关procfs数据结构和它们之间
的关联。注意到一个进程相关的所有vnodes通过prnode结构的pr_next成员链接起来。当引用一个procfs目录以及目录下的文件对象时,内核动态创建必要的数据结构支持这种文件I/O请求,同时也是动态销毁相关数据结构。无论什么时候针对procfs目录或文件做open(2)请求或者列举procfs目录或文件,它们似乎总是在那里,类似冰箱里的灯,当你打开冰箱的时候它总是亮着的,但是关上冰箱门之后它事实上关闭着。
通过procfs所能访问到的数据显然总是位于内核proc结构以及其他一些数据结构
中,这些数据结构共同构成了Solaris内核中完整的进程模型。应用程序通过procfs可以获取进程数据,控制进程执行。这样做的好处是隐藏了内核进程模型的底层细节,以一种相对普通的方式析取感兴趣的数据、进行进程控制。请求发生时建立这种动态抽象,只要针对特定文件的访问存在,这种动态抽象就一直保持着。
针对procfs的文件I/O操作遵循传统方式,打开文件获取文件句柄,读写,关闭
文件句柄。通过vnode开关表机制进行procfs相关vnode操作时,创建并初始化prnode和prcommon结构,这通常是应用程序文件请求导致的结果。实际的procfs vnode操作由相关的查找、读写函数处理/proc目录下的对象。
procfs遍历和读取请求采用一组函数指针实现,这组函数实现procfs文件类型相
关操作。文件类型分两层维护。在vnode的v_type成员中,procfs文件类型定义成VPROC。而prnode结构的pr_type成员定义了这个特定procfs文件的类型。procfs文件类型直接描述了/proc目录结构,参看/usr/include/sys/proc/prdata.h文件。
/*
* Node types for /proc files (directories and files contained therein).
*/
typedef enum prnodetype
{
PR_PROCDIR, /* /proc */
PR_PIDDIR, /* /proc/ */
PR_AS, /* /proc//as */
PR_CTL, /* /proc//ctl */
PR_STATUS, /* /proc//status */
PR_LSTATUS, /* /proc//lstatus */
PR_PSINFO, /* /proc//psinfo */
PR_LPSINFO, /* /proc//lpsinfo */
PR_MAP, /* /proc//map */
} prnodetype_t;
打开一个procfs文件时的基本流程如图4所示。
--------------------------------------------------------------------------
open( "/proc//", O_RDONLY );
| Specific procfs directory object
代| vn_open() lookup functions are invoked
| through the pr_lookup_function[]
码| +----> lookupxxx() array
| | VOP_LOOKUP() -> prlookup()
流| | index based on type
| | pr_lookup_function +-----------------------+
程| | | pr_lookup_piddir() |\
| | Construct full path name, +-----------------------+ \
| | looking up each element | pr_lookup_lwpdir() | \
| | in the path. +-----------------------+ prgetnode()
| | | pr_lookup_objectdir() | /
| | +-----------------------+ /
| | | | |/
| | ...... | ......
| | |
| +-------------------------------------------+
| VOP_OPEN() -> propen()
V
图4. 打开一个procfs文件时的基本流程
--------------------------------------------------------------------------
图4中流程从应用程序开始,针对一个procfs文件做open(2)系统调用。进入
vnode内核层(vn_open()),完成一系列查找以构建目标/proc文件的完整路径名。通过vnode层的宏进入文件系统相关操作。在上面的图例中,VOP_LOOKUP()解析成procfs的pr_lookup()函数。pr_lookup()完成访问权限检查并根据目录文件类型调用相应的procfs函数,比如pr_lookup_piddir()针对/proc/目录进行查找工作。每个pr_lookup_xxx()目录查找函数完成某些目录类型相关的工作,然后调用prgetnode()获取prnode。
prgetnode()为/proc文件创建prnode(其中内嵌了vnode),并初始化prnode和vnode结构的某些成员。对于/proc/和/proc//lwp/,还会创建
prcommon结构,挂接到prnode结构上,并部分初始化。注意,对于/proc下的目录文件,为了正确反映目录文件类型,vnode类型从VPROC(初始设置)改变成VDIR,表示这是一个procfs目录文件。
一旦完整路径名构建完毕,通过VOP_OPEN()宏进入文件系统相关的open()函数。procfs的propen()函数完成prnode和vnode结构的其余初始化以及针对特定文件类型的访问测试工作。一旦propen()完成,控制返回到vn_open()。最终一个代表procfs文件的文件句柄返回给主调者。
读取一个procfs数据文件(和目录文件相对)类型打开流程,read()系统调用最终
进入procfs的prread()函数。procfs实现为每个可用文件对象(不同的数据结构)定义了一个数据文件对象相关的读函数,比如pr_read_psinfo()、pr_read_pstatus()、
pr_read_lwpsinfo()等等。这些函数指针构成一个数组,以文件类型做下标进行索引,prread()最终调用了它们。整个流程类似lookup操作。
Solaris 7 的procfs实现是基于64-bit内核的,但是同时支持32-bit和64-bit应
用,在/proc层次结构上提供了32-bit版本的可用数据文件。在64-bit Solaris 7内核中,描述每个/proc文件对象内容的数据结构同时拥有32-bit版本和64-bit版本,比如lwpstatus和lwpstatus32、psinfo和psinfo32等等。针对每个32-bit版本的结构定义,相应pr_read_xxx()函数做了支持32-bit数据模式的编码。
procfs用户并不会意识到64-bit内核中多种数据模式实现。调用到prread()时,
它会检查主调者使用的数据模式,并激活相应数据模式的函数。这里有一个例外,读取/proc//as(地址空间)文件时,主调者必须拥有与/proc//as文件一样的
数据模式,换句话说,64-bit内核中32-bit应用程序可以读取另外一个32-bit进程的AS(地址空间)文件,但是不能读取另外一个64-bit进程的AS文件。
scz注:我觉得这里倒不如说,/proc//as本身是拥有单一数据模式的,要么
32-bit,要么64-bit,不可得兼。而其他/proc数据文件对象可能同时支持两
种数据模式。
pr_read_xxxx()函数从内核里读取相关数据,然后写入相应的procfs数据结构,最终返回给主调者。例如,pr_read_psinfo()从目标进程的proc结构、cred结构和as结构读取数据,写入psinfo结构中相应成员。访问内核数据时靠proc结构的p_lockp成员确定的互斥锁进行同步,这样确保每次只有一个客户线程能够访问per-process或per-lwp内核数据。
很少需要写访问procfs文件。姑且不考虑写目录创建数据文件,典型的写操作就
是为了发出某些控制消息写进程或LWP控制文件。控制消息(参看proc(1))包括stop/start消息,信号跟踪和控制,故障管理,执行控制(比如进入/退出某个系统调用时暂停)以及地址空间访问监视。
迄今为止,我们讨论的都是用标准系统调用对procfs文件进行I/O操作,目前从
普通应用级程序员编程访问/proc文件来说这是唯一的办法。然而另外有一组特定针对procfs的访问接口,proc(1)中介绍的/usr/proc/bin/下的命令(随Solaris分发)使用了这组接口。这组接口位于libproc.so动态链接库,属于未公开的接口。Sun公司正在着手准备关于这组接口的文档,做为标准Solaris APIs提供出来。图5展示了以前讨论过的内核中procfs模块与各层之间的接口关系。
--------------------------------------------------------------------------
+---------------------+--------------------------+
| custom /proc code | /usr/proc/bin/ |
+------------------+ | +-----------------------+
| stdio interfaces | | |\\\\\\\\libproc\\\\\\\\|
+------------------+--+--+--------------+\\\\\\\\|
| system calls |\\\\\\\\| user
-----------------------------------------------------------------
+---------------------------------------+\\\\\\\\| kernel
| vnode layer |\\\\\\\\|
+---------------------------------------+--------+
| procfs |
+------------------------------------------------+
图5. procfs模块与各层之间的接口关系
--------------------------------------------------------------------------
图5演示了多条到达procfs内核例程的路径。开发者通常通过系统调用进入vnode层,这是前面过介绍的方式。而proc(1)命令更多构建在libproc.so提供的接口上。为什么需要这组动态链接库接口呢,提供一组简单易用的例程用于应用程序开发,减少直接使用内核机制带来的复杂性。控制一个进程的执行,尤其是多线程进程,非常复杂,需要一组真正属于API层的编码接口,而不是内核层的编码接口。
向控制文件的头8个字节(如果是LP64内核,就是头16个字节)写入一个操作码和
可选的操作数,完成进程控制。写进程控制文件的路径也要经过vnode层,最终调用了procfs的prwritectl()函数。允许在一次写调用中向控制文件写入多个控制消息(操作码和操作数),prwritectl()会将一次写入的多个控制消息分成独立的操作码/操作数对,顺序提交给内核的pr_control()函数,pr_control()函数将设置进程或LWP相应的标志,以指明控制机制启动,比如某一事件发生时暂停。控制函数在proc(4)手册页中介绍。
进程/LWP控制的实现与内核中进程/LWP子系统紧密结合,P区、U区、LWP和内核线程结构中各种域一起协作完成通过procfs进行的进程管理和控制。建立进程控制包括设置标志和位掩码字段,用于跟踪那些导致进程、线程进入、离开内核的事件,包括信号、系统调用、故障情形。对应这些事件的进入、离开内核的点定义得比较充分,为进程状态改变提供了自然的控制机制。
系统调用、信号和故障分别对应数据类型sysset_t、sigset_t和fltset_t。如果
指定发生某系统调用时暂停,此时尚未从进程读取提供给该系统调用的参数。如果指定离开某系统调用时暂停,此时来自系统调用的返回值已经提交给进程。可以指定发生某种故障时进入内核陷门处理程序。可以指定接收到某个信号时暂停或者从系统调用、内核陷门处理程序返回,可以通过信号唤醒进程。
可以在进程虚拟地址空间中指定一片区域处在监视中,当针对这片区域进行被监
视类型的操作(比如读、写访问),也就是监视事件发生时,产生一次监视点陷入,典型地导致进程、LWP暂停,这通过跟踪FLTWATCH故障或者捕捉非阻塞的SIGTRAP信号实现。
某些情况下为了析取进程信息、进行进程控制,控制进程可能需要目标进程临时
完成某种特殊的操作。例如,pfiles(1)命令可以列出目标进程打开的每个文件的信息,这需要目标进程针对每个打开的文件句柄做stat(2)系统调用。运行在Solaris系统上的进程典型地花费大量时间阻塞在某个系统调用上,为了获得目标进程的控制权完成控制进程提交的任务,需要在目标进程阻塞时抢夺CPU,保护当前系统调用状态,当控制进程提交的任务完成后恢复保存的系统调用状态继续执行目标进程原来的任务。
为了达到这个目的,procfs实现了另外一个代理LWP,而不是使用目标进程中现
有LWP,否则状态保存、恢复更加复杂。procfs提供了一种机制创建代理LWP(注意PCAGENT控制消息)。代理LWP创建成功后将是目标进程中唯一可运行LWP,直到它消亡。
目标进程中执行代理LWP以完成控制进程提交的任务,比如在目标进程中执行系统调用。然后销毁代理LWP,恢复保存的进程/LWP状态。proc结构中有一个成员p_agenttp,指向创建的代理LWP。内核代码通过检查该指针判断目标进程中是否存在代理LWP。
kthread_t * p_agenttp; /* thread ptr for /proc agent lwp */
proc(4)手册页介绍了进程控制的更多细节。
--------------------------------------------------------------------------
后记:
本篇与<>都是<>的一部分,由于很多东西缺乏内核Hacking经验和常用术语约定,翻译得相当牵强,好在可以对照/usr/include/下的头文件反复理解。<<[805-3024] Solaris设备驱动程序编程指南>>和<<[805-4038] Solaris流编程指南>>是对理解<>很好的补充。此外可以在comp.unix.programmer和comp.unix.solaris上向Sun开发人员请教。
自由广告区 |
分类导航 |
邮件新闻资讯: IT业界 | 邮件服务器 | 邮件趣闻 | 移动电邮 电子邮箱 | 反垃圾邮件|邮件客户端|网络安全 行业数据 | 邮件人物 | 网站公告 | 行业法规 网络技术: 邮件原理 | 网络协议 | 网络管理 | 传输介质 线路接入 | 路由接口 | 邮件存储 | 华为3Com CISCO技术 | 网络与服务器硬件 操作系统: Windows 9X | Linux&Uinx | Windows NT Windows Vista | FreeBSD | 其它操作系统 邮件服务器: 程序与开发 | Exchange | Qmail | Postfix Sendmail | MDaemon | Domino | Foxmail KerioMail | JavaMail | Winwebmail |James Merak&VisNetic | CMailServer | WinMail 金笛邮件系统 | 其它 | 反垃圾邮件: 综述| 客户端反垃圾邮件|服务器端反垃圾邮件 邮件客户端软件: Outlook | Foxmail | DreamMail| KooMail The bat | 雷鸟 | Eudora |Becky! |Pegasus IncrediMail |其它 电子邮箱: 个人邮箱 | 企业邮箱 |Gmail 移动电子邮件:服务器 | 客户端 | 技术前沿 邮件网络安全: 软件漏洞 | 安全知识 | 病毒公告 |防火墙 攻防技术 | 病毒查杀| ISA | 数字签名 邮件营销: Email营销 | 网络营销 | 营销技巧 |营销案例 邮件人才:招聘 | 职场 | 培训 | 指南 | 职场 解决方案: 邮件系统|反垃圾邮件 |安全 |移动电邮 |招标 产品评测: 邮件系统 |反垃圾邮件 |邮箱 |安全 |客户端 |