启用进程会计选项后,当进程结束后内核会写一个会计记录。会计记录一般包括命令名,使用的CPU时间总量,用户ID,组ID和启动时间等。
root用户可以执行accton命令来启用会计处理。会计记录邪道指定的文件中,linux中该文件是/var/account/pacct。
acct结构定义在
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进程的会计记录
如下test1程序fork了4个进程,每个进程处理不同的事情,生成会计记录:
#include#include #include #include int main(void) { pid_t pid; if ((pid = fork()) < 0) printf("fork error"); else if (pid != 0) { sleep(2); exit(2); } if ((pid = fork()) < 0) printf("fork error"); else if (pid != 0) { sleep(4); abort(); } if ((pid = fork()) < 0) printf("fork error"); else if (pid != 0) { execl("/bin/dd", "dd", "if=/etc/passwd", "of=/dev/null", NULL); exit(7); } if ((pid = fork()) < 0) printf("fork error"); else if (pid != 0) { sleep(8); exit(0); } sleep(6); kill(getpid(), SIGKILL); exit(6); }
如下test2程序从会计记录中选择一些字段打印:
#include#include #include #include #include #if defined(BSD) #define acct acctv2 #define ac_flag ac_trailer.ac_flag #define FMT "%-*.*s e = %.0f, chars = %.0f, %c %c %c %cn" #elif defined(HAS_AC_STAT) #define FMT "%-*.*s e = %6ld, chars = %7ld, stat = %3u: %c %c %c %cn" #else #define FMT "%-*.*s e = %6ld, chars = %7ld, %c %c %c %cn" #endif #if defined(LINUX) #define acct acct_v3 #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) { unsigned long val; int exp; val = comptime & 0x1fff; exp = (comptime >> 13) & 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。



