Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1727196
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-29 13:46:00

多数UNIX系统提供了一个执行进程记帐的选项。当被启动时,内核每次在一个进程终止时写一个记帐记录。这些记帐记录一般是命令名的少量二进制数据、使用 的CPU时间量、用户ID和组ID、开始时间,等等。我们将在本节更深入地看下这些记帐信息,因为它给我们一个机会,再次看下进程并使用5.9节的 fread函数。


进程记账不被任何标准规定。因而,所有实现都有恼人的区别。例如,Solaris 9维护的I/O计数以字节为单位,而FreeBSD 5.2.1和Mac OS X 10.3维护块的单位,尽管没有不同块尺寸的区别,但仍使得计数器完全没用。另一方面,Linux2.4.22完全不维护I/O计数。


每个实现都有它自己的管理命令集来处理裸记账信息。例如,Solaris提供了runacct和acctcom,而FreeBSD提供了sa命令来处理和总结裸记账信息。


一个我们没有提过的函数(acct)启用和禁用记账。这个函数的唯一用户是accton命令(它碰巧是各平台之间仅有的几个相似点之一。)一个超级用户用 路径名参数执行accton来启用记账。记账记录被写到指定文件,通常在FreeBSD和Mac OS X上是/var/account/acct,在Linux上是/var/account/pacct,在Soaris上是/var/adm/pacct。 记账通过没有执行没有参数的accton来关闭。


记账记录的结构体定义在头文件里,并看起来像:


typedef u_short comp_t;  /* 3-bit base 8 exponent; 13-bit fraction */

struct acct
{
  char ac_flag;  /* flag (see following Figure) */
  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 (clock ticks) */
  comp_t ac_stime;  /* system CPU time (clock ticks) */
  comp_t ac_etime;  /* elapsed time (clock ticks) */
  comp_t ac_mem;  /* average memory usage */
  comp_t ac_io;  /* bytes transfered (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成员记录了在进程执行期间的特定事件。这些事件在下表中描述。

记账记录的ac_flag值
ac_flag 描述 FreeBSD 5.2.1 Linux 2.4.22 Linux OS X 10.3 Solaris 9
AFORK 进程是fork的结果,但没有调用来exec * * * *
ASU 进程使用了超级用户特权   * * *
ACOMPAT 进程使用了兼容模式        
ACORE 进程dump了核心 * * *  
AXSIG 进程被一个信号杀死 * * *  
AEXPND 扩展的记账项       *
 


记账记录所需的信息,比如CPU时间和字符传输的数量,被内核保存在进程表里,并当任何一个新进程创建时被初始化,比如在一个fork后的子进程里。每个 记账记录当一个进程终止时都被写入。这意味着在记账文件里记录的顺序对应着进程终止的顺序,而不是它们启动的顺序。为了知道启动顺序,我们需要遍历记账文 件并通过开始日历时间排序。但这样并不完美,因为日历时间以秒做单例,而可能许多进程在同一秒内被启动。另一个方案是,逝去的时间以时钟滴嗒为单位,它通 常是每秒60到128个滴嗒。但是我们不知道一个进程的终止时间,我们知道的所有事是它的开始时间和终止顺序,我们仍然不能根据一个记账文件的数据重组出 各种进程的确切的开始顺序。


记账记录对应着进程,而不是程序。一个新的记录由内核在fork后为子进程初始化,而不是当一个新的程序被执行时。虽然exec没有创建一个新的记账记 录,但命令名改变了,而且AFORK标志也被清除。这意味着如果我们有三个程序的链--A exec B,然后B exec C,而C退出--只有一个记账记录被写入。这个记录的命令名对应着程序C,但CPU时间却是A、B、C的总和。


下面的代码检查了一些记账数据:



  1. #include <unistd.h>
  2. #include <signal.h>

  3. int
  4. main(void)
  5. {
  6.     pid_t pid;

  7.     if ((pid = fork()) < 0) {
  8.         printf("fork error\n");
  9.         exit(1);
  10.     } else if (pid != 0) { /* parent */
  11.         sleep(2);
  12.         exit(2); /* terminate with exit status 2 */
  13.     }
  14.                         /* first child */
  15.     if ((pid = fork()) < 0) {
  16.         printf("fork error\n");
  17.         exit(1);
  18.     } else if (pid != 0) {
  19.         sleep(4);
  20.         abort(); /* terminate with core dump */
  21.     }

  22.                         /* second child */
  23.     if ((pid = fork()) < 0) {
  24.         printf("fork error\n");
  25.         exit(1);
  26.     } else if (pid != 0) {
  27.         execl("/bin/dd", "dd", "if=/etc/termcap", "of=/dev/null", NULL);
  28.         exit(7); /* shouldn't get here */
  29.     }

  30.                         /* third child */
  31.     if ((pid = fork()) < 0) {
  32.         printf("fork error\n");
  33.         exit(1);
  34.     } else if (pid != 0) {
  35.         sleep(8);
  36.         exit(0); /* normal exit */
  37.     }

  38.                         /* fourth child */
  39.     sleep(6);
  40.     kill(getpid(), SIGKILL); /* terminate w/signal, no core dump */
  41.     exit(6); /* shouldn't get here */
  42. }

上面的代码调用了fork四次。每个子进程都做一些不同的事,然后退出。

我们将运行这个测试程序,然后使用下面的程序来打印从记账记录选定的域:



  1. #include <sys/acct.h>
  2. #include <unistd.h>
  3. #include <stdio.h>

  4. #ifdef HAS_SA_STAT
  5. #define FMT "%-*.*s e = %6ld, chars = %7ld, stat = %3u: %c %c %c %c\n"
  6. #else
  7. #define FMT "%-*.*s e = %6ld, chars = %7ld, %c %c %c %c\n"
  8. #endif
  9. #ifndef HAS_ACORE
  10. #define ACORE 0
  11. #endif
  12. #ifndef HAS_AXSIG
  13. #define AXSIG 0
  14. #endif

  15. static unsigned long
  16. compt2ulong(comp_t comptime) /* convert comp_t to unsigend long */
  17. {
  18.     unsigned long val;
  19.     int exp;

  20.     val = comptime & 0x1fff; /* 13-bit fraction */
  21.     exp = (comptime >> 13) & 7; /* 3-bit exponent (0-7) */
  22.     while (exp-- > 0)
  23.         val *= 8;
  24.     return(val);
  25. }
  26. int
  27. main(int argc, char *argv[])
  28. {
  29.     struct acct acdata;
  30.     FILE *fp;

  31.     if (argc != 2) {
  32.         printf("usage: parcct filename\n");
  33.         exit(1);
  34.     }
  35.     if ((fp = fopen(argv[1], "r")) == NULL) {
  36.         printf("can't open %s\n", argv[1]);
  37.         exit(1);
  38.     }
  39.     while (fread(&acdata, sizeof(acdata), 1, fp) == 1) {
  40.         printf(FMT, (int)sizeof(acdata.ac_comm),
  41.             (int)sizeof(acdata.ac_comm), acdata.ac_comm,
  42.             compt2ulong(acdata.ac_etime), compt2ulong(acdata.ac_io),
  43. #ifdef HAS_SA_STAT
  44.             (unsigned char) acdata.ac_stat,
  45. #endif
  46.             acdata.ac_flag & ACORE ? 'D' : ' ',
  47.             acdata.ac_flag & AXSIG ? 'X' : ' ',
  48.             acdata.ac_flag & AFORK ? 'F' : ' ',
  49.             acdata.ac_flag & ASU ? 'S' : ' ');
  50.     }
  51.     if (ferror(fp)) {
  52.         printf("read error\n");
  53.         exit(1);
  54.     }
  55.     exit(0);
  56. }

BSD衍生的平台不支持ac_flag成员,所以我们定义HAS_SA_STAT常量来支持这个成员。令定义的符号基于特性而不是平台会更好地读,并允许我们通过加入额外的定义到我们的编译命令是来简单修改这个程序。另一种替代方案是使用:


#if defined(BSD) || defined(MACOS)
它变得笨拙,因为我们要移植我们的应用程序到其它的平台。


我们定义了类似的常量来决定平台是否支持ACORE和AXSIG记账标志。我们不能使用标志符号本身,因为在Linux,它们被定义为enum值,这样我们不能使用一个#ifdef表达式。


为了执行我们的测试,我们进行如下操作:


1、成为超级用户并开启记账,通过使用accton命令。注意当这个命令终止时,记账应该开启;因此,记账文件里的第一个记录应该是从这个命令来的。


2、退出超级用户shell并运行上面的第一个程序。这应该添加6个记录到记账文件:一个是超级用户shell的,一个是测试父进程的,剩下是四个测试子进程的。在第二个子进程里一个新进程不是由execl创建的。对于第二个子进程只有一个记账记录。


3、成为超级用户并关闭记账。因为当accton命令终止时记账被关半,它不应该出现在记账文件里。


4、运行上面的第二个程序来打印记账文件的选定的域。

根据上面的步骤在Linux上的运行结果为(文件名被打印为乱码……,数值好像是错误的……):


阅读(2855) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~