进程会计

大多数 UNIX 系统都提供了一个选项以进行进程会计处理。启用该选项后,每当进程结束时内核就会写一个会计记录。典型的会计记录包含总量较小的二进制数据,一般包括命令名、所使用的 CPU 时间总量、用户 ID 和组 ID、启动时间等。函数 acct 是用来启用和禁用进程会计,唯一使用这一函数的是 accton 命令。超级用户执行一个带路径名参数的 accton 命令启用会计处理。会计记录会写到指定的文件中,在 FreeBSD 和 Mac OS X 中,该文件通常是 /var/account/acct,Linux 中是 /var/account/pacct,Solaris 中是 /var/adm/pacct。执行不带任何参数的 accton 命令则停止会计处理。
会计记录结构定义在头文件<sys/acct.h>中,虽然每种系统的实现各不相同,但会计记录样式基本如下:

#include <sys/acct.h>
typedef u_short comp_t; // 3-bit base 8 exponent; 13-bit fraction
type acct{
char ac_flag; // flag, see below
char ac_stat; // termination status(signal & core flag only)
// (Solaris only)
uid_t ac_uid; // real user ID
gid_t ac_gid; // real group ID
dev_t ac_tty; // controlling terminal
time_t ac_btime; // starting calendar time
comp_t ac_utime; // user CPU time
comp_t ac_stime; // system CPU time
comp_t ac_etime; // elapsed time
comp_t ac_mem; // average memory usage
comp_t ac_io; // bytes transferred (by read and write)
// "blocks" on BSD systems
comp_t ac_rw; // blocks read or written (not present on BSD systems)
char ac_comm[8]; // command name: [8] for Solaris, [10] for Mac OS X,
// [16] for FreeBSD and [17] for Linux
};

其中 ac_flag 成员记录了进程在执行期间的如下事件。
[img]http://dl2.iteye.com/upload/attachment/0126/9442/b38e67de-80f0-3503-af2d-014bb00e4ae6.png[/img]
会计记录所需的各个数据(各 CPU 时间、传输的字符数等)都由内核保存在进程表中,并在一个新进程被创建时初始化。进程终止时写一个会计记录。这产生两个结果。
(1)不能获取永远不终止的进程的会计记录,如 init 进程和内核守护进程等。
(2)在会计文件中记录的顺序对应于进程终止的顺序,而不是它们启动的顺序。为了确定启动顺序,需要读取全部会计文件,然后按启动日历时间进行排序。
会计记录对应于进程而不是程序。在 fork 之后,内核为子进程初始化一个记录,而不是在一个新程序被执行时初始化。虽然 exec 并不创建一个新的会计记录,但相应记录中的命令名改变了,AFORK 标志则被清除。这意味着,如果一个进程顺序执行了 3 个程序(A exec B、B exec C,最后是 C exit),只会写一个会计记录,在该记录中的命令名对应与 C,但 CPU 时间是 A、B 和 C 之和。
下面这个程序 acctDemo 可用来生成会计数据。它按下图调用了 4 次 frok,每个子进程做不同的事情,然后终止。
[img]http://dl2.iteye.com/upload/attachment/0126/9449/f9590bbe-2881-327c-8d28-2704d02ce892.png[/img]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int main(void){
pid_t pid;

if((pid=fork()) < 0){
printf("fork 1 error\n");
}else if(pid != 0){ // parent
sleep(2);
exit(2); // terminate with exit status 2
}

if((pid=fork()) < 0){
printf("fork 2 error\n");
}else if(pid != 0){ // first child
sleep(4);
abort(); // terminate with core dump
}

if((pid=fork()) < 0){
printf("fork 3 error\n");
}else if(pid != 0){ // second child
execl("/bin/dd", "if=/etc/passwd", "of=/dev/null", NULL);
exit(7); // shouldn't get here
}

if((pid=fork()) < 0){
printf("fork 4 error\n");
}else if(pid != 0){ // third child
sleep(8);
exit(0); // normal exit
}

sleep(6); // fourth child
kill(getpid(), SIGKILL); // terminate w/signal, no core dump
exit(6); // shouldn't get here
}

然后可使用下面这个程序 printAcct 来打印运行上面程序后产生的会计数据。

#include <stdio.h>
#include <stdlib.h>
#include <sys/acct.h>

//#define BSD // 如果是 BSD 系统就取消该行注释
//#define LINUX // 如果是 Linux 系统就取消该行注释
//#define HAS_AC_STAT // 如果 acct 结构有 ac_stat 这个成员就取消该行注释
//#define HAS_ACORE // 如果 ac_flag 标记支持 ACORE 选项就取消该行注释
//#define HAS_AXSIG // 如果 ac_flag 标记支持 AXSIG 选项就取消该行注释

#if defined(BSD) // different structure in FreeBSD
#define acct acctv2
#define ac_flag ac_trailer.ac_flag
#define FMT "%-*.*s e = %.0f, chars = %.0f, %c %c %c %c\n"
#elif defined(HAS_AC_STAT)
#define FMT "%-*.*s e = %6ld, chars = %7ld, stat = %3u: %c %c %c %c\n"
#else
#define FMT "%-*.*s e = %6ld, chars = %7ld, %c %c %c %c\n"
#endif

#if defined(LINUX)
#define acct acct_v3 // different structure in Linux
#endif

#if !defined(HAS_ACORE)
#define ACORE 0
#endif
#if !defined(HAS_AXSIG)
#define AXSIG 0
#endif

#if !defined(BSD)
static unsigned long
compt2ulong(comp_t comptime){ // convert comp_t to unsigned long
unsigned long val;
int exp;

val = comptime & 0x1fff; // 13-bit fraction
exp = (comptime >> 13) & 7; // 3-bit exponent (0-7)
while(exp-- > 0)
val *= 8;
return val;
}
#endif

int main(int argc, char *argv[]){
struct acct acdata;
FILE *fp;

if(argc != 2){
printf("usage: %s filename\n", argv[0]);
exit(1);
}
if((fp=fopen(argv[1], "r")) == NULL){
printf("can't open %s\n", argv[1]);
exit(2);
}
while(fread(&acdata, sizeof(acdata), 1, fp) == 1){
printf(FMT, (int)sizeof(acdata.ac_comm),
(int)sizeof(acdata.ac_comm), acdata.ac_comm,
#if defined(BSD)
acdata.ac_etime, acdata.ac_io,
#else
compt2ulong(acdata.ac_etime), compt2ulong(acdata.ac_io),
#endif
#if defined(HAS_AC_STAT)
(unsigned char)acdata.ac_stat,
#endif
acdata.ac_flag & ACORE ? 'D': '-',
acdata.ac_flag & AXSIG ? 'X': '-',
acdata.ac_flag & AFORK ? 'F': '-',
acdata.ac_flag & ASU ? 'S': '-');
}
if(ferror(fp))
printf("read error\n");
exit(0);
}

BSD 派生的平台不支持 ac_stat 成员,所以我们在支持该成员的平台上定义了 HAS_AC_STAT 产量。同理,我们还定义了类似的常量以判断该平台是否支持 ACORE 和 AXSIG 会计标志,不能直接使用这两个标志符号,因为它们在 Linux 中被定义为 enum 类型值,而在 #ifdef 中不能使用此种类型值。
为进行测试,需要执行下列操作步骤。
(1)成为超级用户,用 accton 命令启用会计处理。注意,当此命令结束时,会计处理已经启用,因此会计文件中的第一个记录应来自这一命令。
(2)终止超级用户 shell,运行上面的 acctDemo 程序,这会追加 6 个记录到会计文件中(超级用户 shell 一个、父进程一个、4 个子进程各一个)。注意,第二个子进程中的 execl 并不创建一个新进程,所以只有一个会计记录。
(3)成为超级用户,停止会计处理。因为在 accton 命令终止时已经停止会计处理,所以不会在会计文件中增加一个记录。
(4)运行 printAcct 程序,从会计文件中选出字段并打印。
整个流程在 Solaris 上的运行结果如下:

$ ls -l /var/adm/pacct
-rw-r--r--. 1 root root 0 9月 13 23:57 /var/adm/pacct
$
$ su
密码:
# accton /var/adm/pacct # 打开进程会计
# ./acctDemo.out
# accton # 关闭进程会计
#
# ./printAcct.out /var/adm/pacct
accton e = 1, chars = 336, stat = 0: - - - S
sh e = 1550, chars = 20168, stat = 0: - - - S
dd e = 2, chars = 1585, stat = 0: - - - - # 第二个子进程
acctDemo.out e= 202, chars= 0, stat = 0: - - - - # 父进程
acctDemo.out e= 420, chars= 0, stat = 134: - - F - # 第一个子进程
acctDemo.out e= 600, chars= 0, stat = 0: - - F - # 第四个子进程
acctDemo.out e= 801, chars= 0, stat = 0: - - F - # 第三个子进程

注意,ac_stat 成员并不是真正的终止状态,而只是其中的一部分。如果进程异常终止,则此字节包含的信息只是 core 标志位(一般是最高位)以及信号编号数(一般是低 7 位);如果进程正常终止,则从会计文件不能得到进程的退出(exit)状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值