当前位置:Linux教程 - Linux - Solaris内核目录

Solaris内核目录

目录:

概述
◆ /proc/目录简介
◆ procfs的实现
后记

--------------------------------------------------------------------------

概述:

进程文件系统 -- procfs -- 允许像管理文件那样直接管理内核进程。

进程文件系统,procfs,是一个伪文件系统,提供了内核进程表的文件系统接口,
Jim Mauro 在这里概要介绍了procfs。

翻译本文的目的在于编写64-bit下的SLKM,欢迎对此感兴趣的朋友交流。

--------------------------------------------------------------------------

◆ /proc/目录简介

进程文件系统,procfs,是一个伪文件系统,它允许对一些非传统意义上的文件
通过标准文件I/O接口进行访问。procfs将Solaris内核进程架构进行了抽象,比如当
前系统中所有运行着的进程会在/proc/目录下有所体现。系统中每个进程对应/proc/
目录下的一个子目录,子目录名即相应进程号(PID),所有进程号子目录构成了
/proc/目录的全部内容。

许多提供进程数据和控制点的内核数据结构在/proc//子目录下有相应反映,
比如,多线程进程中每个LWP的相关数据和控制结构体现在/proc//lwp/
中。 /proc/目录下的对象不是真实磁盘文件,这些对象位于内核内存中,用户执行
ls(1)命令显示/proc/目录结构时,系统读取内核内存并返回相应内容。

通过/proc,相对简便地就可以获取进程信息,比如进程执行环境、内核资源利
用率。进程控制和procfs直接相关,procfs最初的设计目的很简单,就是为编写调试
器提供一组接口,现在已经有了相当大的改进。

Solaris系统在/usr/proc/bin/目录下提供了一组工具从/proc中析取进程信息,
同时可以进行简单的进程控制。可以参看proc(1)手册页。进程状态命令ps(1)也利用
了procfs接口。

下面列举可以通过/proc文件系统获取的控制和信息数据,关于这些文件的详细
信息参看proc(4)手册页。

/proc -- procfs的根目录

/proc/ -- 某一确定进程的根目录,进程PID正是子目录名

/proc//as -- 进程地址空间,即struct proc结构中p_as成员。换句话说,进
程地址空间以/proc//as文件的形式展现出来,通过这个伪文件系统接口可以访
问相应进程地址空间。

struct as * p_as; /* 进程地址空间指针 */

# ls -l /proc/53/as
-rw------- 1 root root 1458176 2月 8 17:34 /proc/53/as

struct proc结构定义在/usr/include/sys/proc.h文件中。

/proc//ctl -- 一个进程控制文件。可以只写打开该文件,然后给相应进程发
送控制信息。可以停止、启动进程,设置进程停止于某一特殊事件。这演示了procfs
的强大和便捷。进程控制、事件跟踪可以通过打开相应进程的控制文件完成,只需要
写入期待行为的控制信息。参看proc(4)手册了解控制信息和控制函数的详细介绍。

/proc//status -- 进程状态信息。对应/usr/include/sys/procfs.h文件里定
义的struct pstatus结构。proc(4)手册页里也有描述。这个结构中有一个成员

lwpstatus_t pr_lwp; /* status of the representative lwp */

该成员对应一个有代表性的LWP(轻量级进程)。单线程进程只有一个LWP,很容易选定
这个有代表性的LWP。那些多线程进程通常有多个LWPs,一个内核函数遍历当前进程
的所有LWPs,根据他们的状态选取这个有代表性的LWP。首先选取正在执行中的LWP,
如果不存在这样的LWP,按照可运行、休眠、停止的顺序选取LWP。

/proc//lstatus -- lwpstatus结构数组,进程中每个LWP对应一个lwpstatus结
构。struct lwpstatus结构定义在/usr/include/sys/procfs.h文件中。

/proc//psinfo -- 类似ps(1)命令提供的进程信息。对应struct psinfo结构,
类似struct pstatus结构,struct psinfo结构中有一个成员

lwpsinfo_t pr_lwp; /* information for representative lwp */

该成员的对应一个有代表性的LWP。

/proc//lpsinfo -- lwpsinfo结构数组,进程中每个LWP对应一个lwpsinfo结构

/proc//map -- 地址空间映射信息,可以用pmap(1)命令显示这些数据信息。

/proc//rmap -- 进程中保留地址空间段。用pmap -r命令显示这些数据信息。

/proc//xmap -- 扩展地址空间映射信息。用pmap -x命令显示这些数据信息。

/proc//cred -- 进程身份验证信息,对应/usr/include/sys/procfs.h文件中
定义的struct prcred结构。

/proc//sigact -- sigaction结构数组,描述和本进程相关的所有信号设置。
struct sigaction结构定义在/usr/include/sys/signal.h文件中。

/proc//auxv -- auxv_t结构数组,包含进程执行时传递给动态链接器的初始值。
auxv_t结构定义在/usr/include/sys/auxv.h文件中。

/proc//ldt -- 局部描述符表(LDT),仅存于Intel x86架构。

/proc//usage -- 进程资源利用率的相关数据,对应struct prusage结构,该
结构定义在/usr/include/sys/procfs.h文件中。

/proc//lusage -- prusage结构数组,对应各个LWP资源利用状况。

/proc//pagedata -- 进程地址空间的另外一种表现方式,可以用于跟踪页面级
的引用和修改。参看struct prpageheader结构定义。

/proc//watch -- prwatch结构数组。通过写控制文件/proc//ctl可以设
置PCWATCH操作,此时建立该文件。允许监视一个或多个地址空间范围,当访问这些
被监视页面时,产生一次陷入。

scz注:这个功能和SoftIce的BPR功能类似,adb支持这种陷入,不知是否利用了
procfs

/proc//cwd -- 到进程当前工作目录的符号链接

/proc//root -- 到进程根目录的符号链接(和上面那个什么区别)

/proc//fd -- 这是一个子目录,包含进程打开的文件句柄

/proc//fd/nn -- 对应进程打开的某个确定的文件句柄

/proc//object -- 这是一个子目录,包含进程相关的可执行文件以及动态链接
库。

/proc//object/nn -- 二进制目标文件。进程对应的可执行文件名为a.out,其
余是进程相关的动态链接库文件。

object目录提供的信息是进程级的,每个/proc//目录有一个lwp子目录,提供
了LWP级的信息:

/proc//lwp -- 这是一个子目录,包含进程中所有LWPs的信息

/proc//lwp/ -- 这是一个子目录,包含对应lwpid的LWP信息

/proc//lwp//lwpctl -- 一个控制文件,通过它可以在LWP级上针对每
个LWP发布控制操作

/proc//lwp//lwpstatus -- LWP状态信息,对应lwpstatus结构,该结
构定义在/usr/include/sys/procfs.h文件中

/proc//lwp//lwpsinfo -- 对应lwpsinfo结构,同样定义在
/usr/include/sys/procfs.h文件中

/proc//lwp//lwpusage -- LWP资源利用信息,对应prusage结构

/proc//lwp//xregs -- 这个文件是处理器架构相关的,某些平台上可
能没有这个文件。对于SPARC系统,这个文件对应/usr/include/sys/procfs_isa.h文
件中定义的prxregset结构。

/proc//lwp//gwindows -- 常规寄存器窗口。这个文件仅存于SPARC架
构的系统,描述LWP使用的常规寄存器组(硬件上下文的一部分),对应gwindows结构,
该结构定义在/usr/include/sys/regset.h文件中。

/proc//lwp//asrs -- 辅助寄存器组,仅存于SPARC V9(UltraSPARC)架
构,专为SPARC V9架构定义的一组额外的硬件寄存器,要求sun4u、64-bit内核
(Solaris 7及其后续版本)、64-bit进程。注意,64-bit内核可以运行32-bit进程,
但是32-bit进程没有这样一个文件与之对应。

--------------------------------------------------------------------------

◆ procfs的实现

procfs是通过动态可加载内核模块的方式实现的。系统启动时自动加载
/kernel/fs/procfs,/etc/vfstab文件中存在缺省/proc入口,系统启动过程中/proc
将被mount上来。mount过程中将调用procfs的prinit()和prmount()函数,它们为
procfs初始化vfs(虚拟文件系统)结构,为根目录/proc/创建并初始化一个vnode。
vfs结构定义在/usr/include/sys/vfs.h文件中,vnode结构定义在
/usr/include/sys/vnode.h文件中。

/proc目录所涉及的内核内存空间绝大部分是动态分配的。但是系统支持的最大
进程数(可以通过/etc/system的max_nprocs参数配置)决定了/proc下子目录插槽数目,
这个是静态分配初始化的。内核变量procdir是一个指向procent结构数组的指针,
每个procent结构对应一个procfs目录入口,procent结构数组元素数量源自系统启动
时初始化的v.v_proc变量的值,也就是系统支持的最大进程数。

scz注:在Solaris Kernel Hacking过程中,应该习惯使用下面这两种命令,很多未
公开内核数据结构在/usr/include下的头文件中有相当体现,然后利用nm命
令确认当前内核正在使用这些内核数据结构或者内核函数。

# find /usr/include -name ""*"" | xargs grep -i ""procdir""
# /usr/ccs/bin/nm -x /dev/ksyms | grep -i ""|procdir""
[1433] |0x0000104570d0|0x000000000008|OBJT |LOCL |0 |ABS |procdir
# /usr/ccs/bin/nm -x /dev/ksyms | grep -i ""|v$""
[8741] |0x00001041e1f4|0x00000000003c|OBJT |GLOB |0 |ABS |v
[6634] |0x00001041e1f4|0x00000000003c|OBJT |GLOB |0 |ABS |v

每个procent结构中pe_proc成员指向对应的proc结构,pe_next成员指向数组的
下一个元素(scz:不但是数组,也形成链表)。整个procdir数组由进程PID结构的
pid_prslot成员索引。创建进程时(fork())系统在procdir数组中为之分配一个元素,
参看图1。

--------------------------------------------------------------------------

+--------+ +---->+---------+<----procdir(procent结构数组)
| proc |<-----------------------|-----| pe_proc |
| | | | pe_next |--+ 0
| p_pidp |----> +------------+ | +---------+ | |
内 +--------+ | pid_prslot |----+ | pe_proc |<-+ |
| ^ | pid_id | | pe_next |--+ |
核 V | +------------+ +---------+ | |
+--------+ | pe_proc |<-+ |
进 | proc | | pe_next |--+ |
| | +---->+---------+ | |
程 | p_pidp |----> +------------+ | | pe_proc |<-+ |
+--------+ | pid_prslot |----+ | pe_next |--+ |
表 | ^ | pid_id | +---------+ | |
V | +------------+ +-----| pe_proc |<-+ |
+--------+ | | pe_next | |
| proc |<-----------------------+ +---------+ |
| | | | |
| p_pidp | | | V
+--------+ | | v.v_proc


图1. 进程PID结构的pid_prslot成员用于索引procdir数组

--------------------------------------------------------------------------

下面是在我的Sun工作站上找到的相应头文件内容:

/usr/include/sys/proc.h

struct proc
{
struct proc * p_next; /* active chain link next */
struct proc * p_prev; /* active chain link prev */
struct pid * p_pidp; /* process ID info */
struct vnode * p_trace; /* pointer to primary /proc vnode */
struct vnode * p_plist; /* list of /proc vnodes for process */
}

/* process ID info */
struct pid
{
unsigned int pid_prslot :24;
pid_t pid_id;
};

procfs内核代码描述了procfs目录入口的精确格式,这个格式以典型的磁盘文件
系统为模板,内核中每个入口对应一个目录名。目录入口包含在目录中的偏移(第几
个入口)、长度域以及inode号。/proc下文件对象的inode号源自文件对象类型和进程
号。注意,/proc目录入口不会出现在目录名搜索缓存中,根据定义,/proc目录入口
总是位于物理内存中(不会被扇出?)。

因为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开发人员请教。