- 论坛徽章:
- 0
|
下面是在我的Sun工作站上找到的相应头文件内容: \r\n\r\n/usr/include/sys/proc/prdata.h \r\n\r\ntypedef struct prnode \r\n{ \r\nvnode_t * pr_next; /* list of all vnodes for process */ \r\nprcommon_t * pr_common; /* common data structure */ \r\nprcommon_t * pr_pcommon; /* \r\n* process common data structure \r\n* 和上面那个成员什么区别 \r\n*/ \r\nvnode_t ** pr_files; /* contained files array (directory) */ \r\nvnode_t pr_vnode; /* embedded vnode 这里不是指针 */ \r\n} prnode_t; \r\n\r\n/* \r\n* Common file object to which all /proc vnodes for a specific process \r\n* or lwp refer. One for the process, one for each lwp. \r\n*/ \r\ntypedef struct prcommon \r\n{ \r\nint prc_slot; /* process slot number */ \r\n} prcommon_t; \r\n\r\n/usr/include/sys/vnode.h \r\n\r\n/* \r\n* All of the fields in the vnode are read-only once they are initialized \r\n* (created) except for: \r\n* v_flag: protected by v_lock \r\n* v_count: protected by v_lock \r\n* v_pages: file system must keep page list in sync with file size \r\n* v_filocks: protected by flock_lock in flock.c \r\n* v_shrlocks: protected by v_lock \r\n*/ \r\ntypedef struct vnode \r\n{ \r\ncaddr_t v_data; /* private data for fs */ \r\n} vnode_t; \r\n\r\n/usr/include/sys/thread.h \r\n\r\ntypedef struct _kthread \r\n{ \r\nstruct vnode * t_trace; /* pointer to /proc lwp vnode */ \r\n} kthread_t; \r\n\r\n图3演示了打开一个procfs文件进行读写时部分相关procfs数据结构和它们之间 \r\n的关联。注意到一个进程相关的所有vnodes通过prnode结构的pr_next成员链接起来。当引用一个procfs目录以及目录下的文件对象时,内核动态创建必要的数据结构支持这种文件I/O请求,同时也是动态销毁相关数据结构。无论什么时候针对procfs目录或文件做open(2)请求或者列举procfs目录或文件,它们似乎总是在那里,类似冰箱里的灯,当你打开冰箱的时候它总是亮着的,但是关上冰箱门之后它事实上关闭着。 \r\n\r\n通过procfs所能访问到的数据显然总是位于内核proc结构以及其他一些数据结构 \r\n中,这些数据结构共同构成了Solaris内核中完整的进程模型。应用程序通过procfs可以获取进程数据,控制进程执行。这样做的好处是隐藏了内核进程模型的底层细节,以一种相对普通的方式析取感兴趣的数据、进行进程控制。请求发生时建立这种动态抽象,只要针对特定文件的访问存在,这种动态抽象就一直保持着。 \r\n\r\n针对procfs的文件I/O操作遵循传统方式,打开文件获取文件句柄,读写,关闭 \r\n文件句柄。通过vnode开关表机制进行procfs相关vnode操作时,创建并初始化prnode和prcommon结构,这通常是应用程序文件请求导致的结果。实际的procfs vnode操作由相关的查找、读写函数处理/proc目录下的对象。 \r\n\r\nprocfs遍历和读取请求采用一组函数指针实现,这组函数实现procfs文件类型相 \r\n关操作。文件类型分两层维护。在vnode的v_type成员中,procfs文件类型定义成VPROC。而prnode结构的pr_type成员定义了这个特定procfs文件的类型。procfs文件类型直接描述了/proc目录结构,参看/usr/include/sys/proc/prdata.h文件。 \r\n\r\n/* \r\n* Node types for /proc files (directories and files contained therein). \r\n*/ \r\ntypedef enum prnodetype \r\n{ \r\nPR_PROCDIR, /* /proc */ \r\nPR_PIDDIR, /* /proc/ */ \r\nPR_AS, /* /proc//as */ \r\nPR_CTL, /* /proc//ctl */ \r\nPR_STATUS, /* /proc//status */ \r\nPR_LSTATUS, /* /proc//lstatus */ \r\nPR_PSINFO, /* /proc//psinfo */ \r\nPR_LPSINFO, /* /proc//lpsinfo */ \r\nPR_MAP, /* /proc//map */ \r\n} prnodetype_t; \r\n\r\n打开一个procfs文件时的基本流程如图4所示。 \r\n\r\n-------------------------------------------------------------------------- \r\n\r\nopen( \"/proc//\", O_RDONLY ); \r\n| Specific procfs directory object \r\n代| vn_open() lookup functions are invoked \r\n| through the pr_lookup_function[] \r\n码| +---->; lookupxxx() array \r\n| | VOP_LOOKUP() ->; prlookup() \r\n流| | index based on type \r\n| | pr_lookup_function +-----------------------+ \r\n程| | | pr_lookup_piddir() |\\ \r\n| | Construct full path name, +-----------------------+ \\ \r\n| | looking up each element | pr_lookup_lwpdir() | \\ \r\n| | in the path. +-----------------------+ prgetnode() \r\n| | | pr_lookup_objectdir() | / \r\n| | +-----------------------+ / \r\n| | | | |/ \r\n| | ...... | ...... \r\n| | | \r\n| +-------------------------------------------+ \r\n| VOP_OPEN() ->; propen() \r\nV \r\n\r\n图4. 打开一个procfs文件时的基本流程 \r\n\r\n-------------------------------------------------------------------------- \r\n\r\n图4中流程从应用程序开始,针对一个procfs文件做open(2)系统调用。进入 \r\nvnode内核层(vn_open()),完成一系列查找以构建目标/proc文件的完整路径名。通过vnode层的宏进入文件系统相关操作。在上面的图例中,VOP_LOOKUP()解析成procfs的pr_lookup()函数。pr_lookup()完成访问权限检查并根据目录文件类型调用相应的procfs函数,比如pr_lookup_piddir()针对/proc/目录进行查找工作。每个pr_lookup_xxx()目录查找函数完成某些目录类型相关的工作,然后调用prgetnode()获取prnode。 \r\n\r\nprgetnode()为/proc文件创建prnode(其中内嵌了vnode),并初始化prnode和vnode结构的某些成员。对于/proc/和/proc//lwp/,还会创建 \r\nprcommon结构,挂接到prnode结构上,并部分初始化。注意,对于/proc下的目录文件,为了正确反映目录文件类型,vnode类型从VPROC(初始设置)改变成VDIR,表示这是一个procfs目录文件。 \r\n\r\n一旦完整路径名构建完毕,通过VOP_OPEN()宏进入文件系统相关的open()函数。procfs的propen()函数完成prnode和vnode结构的其余初始化以及针对特定文件类型的访问测试工作。一旦propen()完成,控制返回到vn_open()。最终一个代表procfs文件的文件句柄返回给主调者。 \r\n\r\n读取一个procfs数据文件(和目录文件相对)类型打开流程,read()系统调用最终 \r\n进入procfs的prread()函数。procfs实现为每个可用文件对象(不同的数据结构)定义了一个数据文件对象相关的读函数,比如pr_read_psinfo()、pr_read_pstatus()、 \r\npr_read_lwpsinfo()等等。这些函数指针构成一个数组,以文件类型做下标进行索引,prread()最终调用了它们。整个流程类似lookup操作。 \r\n\r\nSolaris 7 的procfs实现是基于64-bit内核的,但是同时支持32-bit和64-bit应 \r\n用,在/proc层次结构上提供了32-bit版本的可用数据文件。在64-bit Solaris 7内核中,描述每个/proc文件对象内容的数据结构同时拥有32-bit版本和64-bit版本,比如lwpstatus和lwpstatus32、psinfo和psinfo32等等。针对每个32-bit版本的结构定义,相应pr_read_xxx()函数做了支持32-bit数据模式的编码。 \r\n\r\nprocfs用户并不会意识到64-bit内核中多种数据模式实现。调用到prread()时, \r\n它会检查主调者使用的数据模式,并激活相应数据模式的函数。这里有一个例外,读取/proc//as(地址空间)文件时,主调者必须拥有与/proc//as文件一样的 \r\n数据模式,换句话说,64-bit内核中32-bit应用程序可以读取另外一个32-bit进程的AS(地址空间)文件,但是不能读取另外一个64-bit进程的AS文件。 \r\n\r\nscz注:我觉得这里倒不如说,/proc//as本身是拥有单一数据模式的,要么 \r\n32-bit,要么64-bit,不可得兼。而其他/proc数据文件对象可能同时支持两 \r\n种数据模式。 \r\n\r\npr_read_xxxx()函数从内核里读取相关数据,然后写入相应的procfs数据结构,最终返回给主调者。例如,pr_read_psinfo()从目标进程的proc结构、cred结构和as结构读取数据,写入psinfo结构中相应成员。访问内核数据时靠proc结构的p_lockp成员确定的互斥锁进行同步,这样确保每次只有一个客户线程能够访问per-process或per-lwp内核数据。 \r\n\r\n很少需要写访问procfs文件。姑且不考虑写目录创建数据文件,典型的写操作就 \r\n是为了发出某些控制消息写进程或LWP控制文件。控制消息(参看proc(1))包括stop/start消息,信号跟踪和控制,故障管理,执行控制(比如进入/退出某个系统调用时暂停)以及地址空间访问监视。 \r\n\r\n迄今为止,我们讨论的都是用标准系统调用对procfs文件进行I/O操作,目前从 \r\n普通应用级程序员编程访问/proc文件来说这是唯一的办法。然而另外有一组特定针对procfs的访问接口,proc(1)中介绍的/usr/proc/bin/下的命令(随Solaris分发)使用了这组接口。这组接口位于libproc.so动态链接库,属于未公开的接口。Sun公司正在着手准备关于这组接口的文档,做为标准Solaris APIs提供出来。图5展示了以前讨论过的内核中procfs模块与各层之间的接口关系。 \r\n\r\n-------------------------------------------------------------------------- \r\n\r\n+---------------------+--------------------------+ \r\n| custom /proc code | /usr/proc/bin/ | \r\n+------------------+ | +-----------------------+ \r\n| stdio interfaces | | |\\\\\\\\\\\\\\\\libproc\\\\\\\\\\\\\\\\| \r\n+------------------+--+--+--------------+\\\\\\\\\\\\\\\\| \r\n| system calls |\\\\\\\\\\\\\\\\| user \r\n----------------------------------------------------------------- \r\n+---------------------------------------+\\\\\\\\\\\\\\\\| kernel \r\n| vnode layer |\\\\\\\\\\\\\\\\| \r\n+---------------------------------------+--------+ \r\n| procfs | \r\n+------------------------------------------------+ \r\n\r\n图5. procfs模块与各层之间的接口关系 \r\n\r\n-------------------------------------------------------------------------- \r\n\r\n图5演示了多条到达procfs内核例程的路径。开发者通常通过系统调用进入vnode层,这是前面过介绍的方式。而proc(1)命令更多构建在libproc.so提供的接口上。为什么需要这组动态链接库接口呢,提供一组简单易用的例程用于应用程序开发,减少直接使用内核机制带来的复杂性。控制一个进程的执行,尤其是多线程进程,非常复杂,需要一组真正属于API层的编码接口,而不是内核层的编码接口。 \r\n\r\n向控制文件的头8个字节(如果是LP64内核,就是头16个字节)写入一个操作码和 \r\n可选的操作数,完成进程控制。写进程控制文件的路径也要经过vnode层,最终调用了procfs的prwritectl()函数。允许在一次写调用中向控制文件写入多个控制消息(操作码和操作数),prwritectl()会将一次写入的多个控制消息分成独立的操作码/操作数对,顺序提交给内核的pr_control()函数,pr_control()函数将设置进程或LWP相应的标志,以指明控制机制启动,比如某一事件发生时暂停。控制函数在proc(4)手册页中介绍。 \r\n\r\n进程/LWP控制的实现与内核中进程/LWP子系统紧密结合,P区、U区、LWP和内核线程结构中各种域一起协作完成通过procfs进行的进程管理和控制。建立进程控制包括设置标志和位掩码字段,用于跟踪那些导致进程、线程进入、离开内核的事件,包括信号、系统调用、故障情形。对应这些事件的进入、离开内核的点定义得比较充分,为进程状态改变提供了自然的控制机制。 \r\n\r\n系统调用、信号和故障分别对应数据类型sysset_t、sigset_t和fltset_t。如果 \r\n指定发生某系统调用时暂停,此时尚未从进程读取提供给该系统调用的参数。如果指定离开某系统调用时暂停,此时来自系统调用的返回值已经提交给进程。可以指定发生某种故障时进入内核陷门处理程序。可以指定接收到某个信号时暂停或者从系统调用、内核陷门处理程序返回,可以通过信号唤醒进程。 \r\n\r\n可以在进程虚拟地址空间中指定一片区域处在监视中,当针对这片区域进行被监 \r\n视类型的操作(比如读、写访问),也就是监视事件发生时,产生一次监视点陷入,典型地导致进程、LWP暂停,这通过跟踪FLTWATCH故障或者捕捉非阻塞的SIGTRAP信号实现。 \r\n\r\n某些情况下为了析取进程信息、进行进程控制,控制进程可能需要目标进程临时 \r\n完成某种特殊的操作。例如,pfiles(1)命令可以列出目标进程打开的每个文件的信息,这需要目标进程针对每个打开的文件句柄做stat(2)系统调用。运行在Solaris系统上的进程典型地花费大量时间阻塞在某个系统调用上,为了获得目标进程的控制权完成控制进程提交的任务,需要在目标进程阻塞时抢夺CPU,保护当前系统调用状态,当控制进程提交的任务完成后恢复保存的系统调用状态继续执行目标进程原来的任务。 \r\n\r\n为了达到这个目的,procfs实现了另外一个代理LWP,而不是使用目标进程中现 \r\n有LWP,否则状态保存、恢复更加复杂。procfs提供了一种机制创建代理LWP(注意PCAGENT控制消息)。代理LWP创建成功后将是目标进程中唯一可运行LWP,直到它消亡。 \r\n目标进程中执行代理LWP以完成控制进程提交的任务,比如在目标进程中执行系统调用。然后销毁代理LWP,恢复保存的进程/LWP状态。proc结构中有一个成员p_agenttp,指向创建的代理LWP。内核代码通过检查该指针判断目标进程中是否存在代理LWP。 \r\n\r\nkthread_t * p_agenttp; /* thread ptr for /proc agent lwp */ \r\n\r\nproc(4)手册页介绍了进程控制的更多细节。 \r\n\r\n-------------------------------------------------------------------------- \r\n\r\n后记: \r\n\r\n本篇与<>;都是<>;的一部分,由于很多东西缺乏内核Hacking经验和常用术语约定,翻译得相当牵强,好在可以对照/usr/include/下的头文件反复理解。<<[805-3024] Solaris设备驱动程序编程指南>;>;和<<[805-4038] Solaris流编程指南>;>;是对理解<>;很好的补充。此外可以在comp.unix.programmer和comp.unix.solaris上向Sun开发人员请教。 |
|