1 进程会计介绍
启用进程会计选项后,当进程结束后内核会写一个会计记录。会计记录一般包括命令名,使用的CPU时间总量,用户ID,组ID和启动时间等。
root用户可以执行accton命令来启用会计处理。会计记录邪道指定的文件中,linux中该文件是/var/account/pacct。
acct结构定义在<sys/acct.h>中:
typedef u_short comp_t;
struct acct
{
char ac_flag; //flag
char ac_stat; //termination staus,Solaris only
uid_t ac_uid; //real user ID
gid_t ac_gid; //real group ID
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
char ac_comm[8]; //command name
...
}
ac_flag成员记录了进程执行期间的某些事件。linux支持的ac_flag如下:
ac_flag
|
说明
|
AFORK(F)
|
进程由fork产生,但未调用exec
|
ASU(S)
|
进程使用超级用户特权
|
ACORE(D)
|
进程转储core
|
AXSIC(X)
|
进程由一个信号杀死
|
会计记录由内核保存在进程表中。在进程新创建时初始化,在进程结束时写入会计记录。由此得出结论:
(1)对于没终止的进程,我们无法获取进程记录
(2)会计文件中记录的顺序对应进程终止的顺序,不是进程启动的顺序
(3)会计记录对应的是进程而不是程序。如果一个进程顺序执行了3个程序(A exec B, B exec C),则只会写C进程的会计记录
2 使用accton进行进程会计处理
如下test1程序fork了4个进程,每个进程处理不同的事情,生成会计记录:
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
printf("fork error");
else if (pid != 0) { /* parent */
sleep(2);
exit(2); /* terminate with exit status 2 */
}
if ((pid = fork()) < 0)
printf("fork error");
else if (pid != 0) { /* first child */
sleep(4);
abort(); /* terminate with core dump */
}
if ((pid = fork()) < 0)
printf("fork error");
else if (pid != 0) { /* second child */
execl("/bin/dd", "dd", "if=/etc/passwd", "of=/dev/null", NULL);
exit(7); /* shouldn't get here */
}
if ((pid = fork()) < 0)
printf("fork error");
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 */
}
如下test2程序从会计记录中选择一些字段打印:
#include <sys/acct.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#if defined(BSD)
#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: pracct filename \n");
exit(127);
}
if ((fp = fopen(argv[1], "r")) == NULL) {
printf("can't open %s \n", argv[1]);
exit(127);
}
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");
exit(0);
}
编译脚本如下:
#!/bin/bash
gcc -o test1 test1.c
gcc -o test2 test2.c -DLINUX
按照如下步骤进行测试:
(1)进入超级用户,新建会计记录文件,安装acct并启用会计记录
touch /var/log/pacct
apt install acct
accton -h
accton /var/log/pacct
(2)终止超级用户,进入普通用户,运行test1程序,这会追加6个记录到会计文件中(超级用户shell,父进程,4个子进程)
在第二个子进程中,execl并不是创建一个新进程,所以对于第二个子子进程来说只有一个会计记录
./test1
ls -lh /var/log/pacct
(3)进入超级用户,停用会计记录
accton off
(4)终止超级用户,进入普通用户,运行test2程序,从会计文件中选出字段并打印
./test2 /var/log/pacct
会计文件记录如下:
accton e = 0, chars = 0, S
bash e = 4303, chars = 0, S
su e = 4737, chars = 0, S
dd e = 0, chars = 0, //子进程2
test1 e = 200, chars = 0, //父进程
test1 e = 407, chars = 0, D X F //子进程1
test1 e = 600, chars = 0, X F //子进程4
test1 e = 800, chars = 0, F //子进程3
e表示墙上时钟时间值,例如父进程sleep(2)对应墙上时钟200个时钟滴答。
accton是由超级用户(ASU)关闭的。S。
除了第2个子进程(fork且exec),其他子进程都设置了F标志(AFORK)。F。
子进程1调用abort,abort产生信号(AXSIG)SIGABRT,产生core转储(ACORE)。D X F。
子进程4调用kill,kill产生信号SIGKILL,但不产生core转储。X F。