acct
Rev.1を表示中。最新版はこちら。
プロセス毎のpidネームスペースns->ns->bacctに、acctファイルおよびかかる情報を有していて、プロセスの起動/終了毎にその情報が設定/出力されます。プロセスのネームスペース毎に独自に管理していて、ネームスペース間で異なるログファイルを設定する事ができます。通常システム起動時、initのネームスペースを継承していて、任意のプロセスで設定したログファイルが、すべてのログファイルとなっていますが。
acctはログファイル名を引数にして、acctシステムコールが呼ばれます。nameが設定されていたらacct_on()でacctの設定を、NULLなら引数をNULLとしてacct_file_reopen()をコールします。引数がNULLならacctファイルのクローズを行っています。
SYSCALL_DEFINE1(acct, const char __user *, name) { int error = 0; if (!capable(CAP_SYS_PACCT)) return -EPERM; if (name) { char *tmp = getname(name); if (IS_ERR(tmp)) return (PTR_ERR(tmp)); error = acct_on(tmp); putname(tmp); } else { struct bsd_acct_struct *acct; acct = task_active_pid_ns(current)->bacct; if (acct == NULL) return 0; spin_lock(&acct_lock); acct_file_reopen(acct, NULL, NULL); spin_unlock(&acct_lock); } return error; }
acct_on()でacctファイルの設定を行います。acctファイルは書きこみ/追加/ラージファイル属性でオープンされます。オープンできなければエラーです。従ってacctonする場合、前もって通常ファイルを作成しておく必要があり、ログファイルの参照はこのファイル権限となります。OKならacct_file_reopen()をコールして、ログファイルの設定を行います。
mnt_pin(mnt)は、real_mount(mnt)->mnt_pinned++とし、これは、ログファイルを作成したファイルシステムがアンマウントした時、acctファイルのクローズするためです。
static int acct_on(char *name) { struct file *file; struct vfsmount *mnt; struct pid_namespace *ns; struct bsd_acct_struct *acct = NULL; /* Difference from BSD - they don't do O_APPEND */ file = filp_open(name, O_WRONLY|O_APPEND|O_LARGEFILE, 0); if (IS_ERR(file)) return PTR_ERR(file); if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) { filp_close(file, NULL); return -EACCES; } if (!file->f_op->write) { filp_close(file, NULL); return -EIO; } ns = task_active_pid_ns(current); if (ns->bacct == NULL) { acct = kzalloc(sizeof(struct bsd_acct_struct), GFP_KERNEL); if (acct == NULL) { filp_close(file, NULL); return -ENOMEM; } } spin_lock(&acct_lock); if (ns->bacct == NULL) { ns->bacct = acct; acct = NULL; } mnt = file->f_path.mnt; mnt_pin(mnt); acct_file_reopen(ns->bacct, file, ns); spin_unlock(&acct_lock); mntput(mnt); /* it's pinned, now give up active reference */ kfree(acct); return 0; }全ログファイルはacct_listにリストされます。if (acct->file)ならこのプロセスはaccton状態です。これはログファイルを別のログファイルに置き換えようとしています。acct構造体を初期化し、
if (file)なら、新規にログファイルを設定します。acct構造体をそれで初期化した後、acct_listにリストします。
if (old_acct)なら、旧ログファイルをlist_del()でacct_listからアンリストします。先の処理でold_acct = acct->fileとしたものです。mnt_unpin()でreal_mount(mnt)->mnt_pinned--し、現在の状態を書き出してファイルクローズします。
static void acct_file_reopen(struct bsd_acct_struct *acct, struct file *file, struct pid_namespace *ns) { struct file *old_acct = NULL; struct pid_namespace *old_ns = NULL; if (acct->file) { old_acct = acct->file; old_ns = acct->ns; acct->active = 0; acct->file = NULL; acct->ns = NULL; list_del(&acct->list); } if (file) { acct->file = file; acct->ns = ns; acct->needcheck = jiffies + ACCT_TIMEOUT*HZ; acct->active = 1; list_add(&acct->list, &acct_list); } if (old_acct) { mnt_unpin(old_acct->f_path.mnt); spin_unlock(&acct_lock); do_acct_process(acct, old_ns, old_acct); filp_close(old_acct, NULL); spin_lock(&acct_lock); } }プロセス終了時、do_exit()がコールされ、ここから、まずacct_collect()でacct構造体を最新の状態にした後、acct_process()でその内容を書き出しします。
group_dead=1は、対象のプロセスはグループリーダで、他のプロセスが使用した全仮想メモリサイズを取得し、ユーザ空間/カーネル空間の動作時間/ページフォルト数等が設定されます。ac_majfltはメモリマップまたはスワップに起因(VM_FAULT_MAJOR)するもので、ac_minfltはその他のページフォルト数です。
void acct_collect(long exitcode, int group_dead) { struct pacct_struct *pacct = ¤t->signal->pacct; unsigned long vsize = 0; if (group_dead && current->mm) { struct vm_area_struct *vma; down_read(¤t->mm->mmap_sem); vma = current->mm->mmap; while (vma) { vsize += vma->vm_end - vma->vm_start; vma = vma->vm_next; } up_read(¤t->mm->mmap_sem); } spin_lock_irq(¤t->sighand->siglock); if (group_dead) pacct->ac_mem = vsize / 1024; if (thread_group_leader(current)) { pacct->ac_exitcode = exitcode; if (current->flags & PF_FORKNOEXEC) pacct->ac_flag |= AFORK; } if (current->flags & PF_SUPERPRIV) pacct->ac_flag |= ASU; if (current->flags & PF_DUMPCORE) pacct->ac_flag |= ACORE; if (current->flags & PF_SIGNALED) pacct->ac_flag |= AXSIG; pacct->ac_utime += current->utime; pacct->ac_stime += current->stime; pacct->ac_minflt += current->min_flt; pacct->ac_majflt += current->maj_flt; spin_unlock_irq(¤t->sighand->siglock); }acct_process()はdo_exit()で、プロセスがグループリーダの時のみコールされます。
static void do_acct_process(struct bsd_acct_struct *acct, struct pid_namespace *ns, struct file *file) { struct pacct_struct *pacct = ¤t->signal->pacct; acct_t ac; mm_segment_t fs; unsigned long flim; u64 elapsed; u64 run_time; struct timespec uptime; struct tty_struct *tty; const struct cred *orig_cred; orig_cred = override_creds(file->f_cred); if (!check_free_space(acct, file)) goto out; memset(&ac, 0, sizeof(acct_t)); ac.ac_version = ACCT_VERSION | ACCT_BYTEORDER; strlcpy(ac.ac_comm, current->comm, sizeof(ac.ac_comm)); do_posix_clock_monotonic_gettime(&uptime); run_time = (u64)uptime.tv_sec*NSEC_PER_SEC + uptime.tv_nsec; run_time -= (u64)current->group_leader->start_time.tv_sec * NSEC_PER_SEC + current->group_leader->start_time.tv_nsec; elapsed = nsec_to_AHZ(run_time); #if ACCT_VERSION==3 ac.ac_etime = encode_float(elapsed); #else ac.ac_etime = encode_comp_t(elapsed < (unsigned long) -1l ? (unsigned long) elapsed : (unsigned long) -1l); #endif #if ACCT_VERSION==1 || ACCT_VERSION==2 { /* new enlarged etime field */ comp2_t etime = encode_comp2_t(elapsed); ac.ac_etime_hi = etime >> 16; ac.ac_etime_lo = (u16) etime; } #endif do_div(elapsed, AHZ); ac.ac_btime = get_seconds() - elapsed; /* we really need to bite the bullet and change layout */ ac.ac_uid = orig_cred->uid; ac.ac_gid = orig_cred->gid; #if ACCT_VERSION==2 ac.ac_ahz = AHZ; #endif #if ACCT_VERSION==1 || ACCT_VERSION==2 /* backward-compatible 16 bit fields */ ac.ac_uid16 = ac.ac_uid; ac.ac_gid16 = ac.ac_gid; #endif #if ACCT_VERSION==3 ac.ac_pid = task_tgid_nr_ns(current, ns); rcu_read_lock(); ac.ac_ppid = task_tgid_nr_ns(rcu_dereference(current->real_parent), ns); rcu_read_unlock(); #endif spin_lock_irq(¤t->sighand->siglock); tty = current->signal->tty; /* Safe as we hold the siglock */ ac.ac_tty = tty ? old_encode_dev(tty_devnum(tty)) : 0; ac.ac_utime = encode_comp_t(jiffies_to_AHZ(cputime_to_jiffies(pacct->ac_utime))); ac.ac_stime = encode_comp_t(jiffies_to_AHZ(cputime_to_jiffies(pacct->ac_stime))); ac.ac_flag = pacct->ac_flag; ac.ac_mem = encode_comp_t(pacct->ac_mem); ac.ac_minflt = encode_comp_t(pacct->ac_minflt); ac.ac_majflt = encode_comp_t(pacct->ac_majflt); ac.ac_exitcode = pacct->ac_exitcode; spin_unlock_irq(¤t->sighand->siglock); ac.ac_io = encode_comp_t(0 /* current->io_usage */); /* %% */ ac.ac_rw = encode_comp_t(ac.ac_io / 1024); ac.ac_swaps = encode_comp_t(0); fs = get_fs(); set_fs(KERNEL_DS); flim = current->signal->rlim[RLIMIT_FSIZE].rlim_cur; current->signal->rlim[RLIMIT_FSIZE].rlim_cur = RLIM_INFINITY; file->f_op->write(file, (char *)&ac, sizeof(acct_t), &file->f_pos); current->signal->rlim[RLIMIT_FSIZE].rlim_cur = flim; set_fs(fs); out: revert_creds(orig_cred); }acct_update_integrals()は、CPU時間等、動的に変化する内容を更新するために、プログラムスタート時の
do_execve()および、tick毎に定期的にコールされています。
tsk->acct_rss_mem1/tsk->acct_vm_mem1は、delta(前回の処理した時間からの経過時間)で重みをつけることで、メモリサイズというのでなくメモリの占有度合いというところです。
acct_rss_mem1は実際に使用したメモリで、acct_vm_memは仮想メモリとなります。
void acct_update_integrals(struct task_struct *tsk) { if (likely(tsk->mm)) { cputime_t time, dtime; struct timeval value; unsigned long flags; u64 delta; local_irq_save(flags); time = tsk->stime + tsk->utime; dtime = time - tsk->acct_timexpd; jiffies_to_timeval(cputime_to_jiffies(dtime), &value); delta = value.tv_sec; delta = delta * USEC_PER_SEC + value.tv_usec; if (delta == 0) goto out; tsk->acct_timexpd = time; tsk->acct_rss_mem1 += delta * get_mm_rss(tsk->mm); tsk->acct_vm_mem1 += delta * tsk->mm->total_vm; out: local_irq_restore(flags); } }だらだらとなってしまいましたが、要は以下のフォーマットを追加更新されたファイルにすぎないということです。acctのログファイルには、コンパイルオプションでVERSION1,2,3とあり、struct acct_v3ないしstruct acctのフォーマットがあります。なお、動作してるacctのバージョンは、acct_v3/act->ac_versionで表示されます。
#ifdef CONFIG_BSD_PROCESS_ACCT_V3 #define ACCT_VERSION 3 #define AHZ 100 typedef struct acct_v3 acct_t; #else #ifdef CONFIG_M68K #define ACCT_VERSION 1 #else #define ACCT_VERSION 2 #endif #define AHZ (USER_HZ) typedef struct acct acct_t; #endif #else #define ACCT_VERSION 2 #define AHZ (HZ) #endif /* __KERNEL */ struct acct { char ac_flag; /* Flags */ char ac_version; /* Always set to ACCT_VERSION */ /* for binary compatibility back until 2.0 */ __u16 ac_uid16; /* LSB of Real User ID */ __u16 ac_gid16; /* LSB of Real Group ID */ __u16 ac_tty; /* Control Terminal */ __u32 ac_btime; /* Process Creation Time */ comp_t ac_utime; /* User Time */ comp_t ac_stime; /* System Time */ comp_t ac_etime; /* Elapsed Time */ comp_t ac_mem; /* Average Memory Usage */ comp_t ac_io; /* Chars Transferred */ comp_t ac_rw; /* Blocks Read or Written */ comp_t ac_minflt; /* Minor Pagefaults */ comp_t ac_majflt; /* Major Pagefaults */ comp_t ac_swaps; /* Number of Swaps */ /* m68k had no padding here. */ #if !defined(CONFIG_M68K) || !defined(__KERNEL__) __u16 ac_ahz; /* AHZ */ #endif __u32 ac_exitcode; /* Exitcode */ char ac_comm[ACCT_COMM + 1]; /* Command Name */ __u8 ac_etime_hi; /* Elapsed Time MSB */ __u16 ac_etime_lo; /* Elapsed Time LSB */ __u32 ac_uid; /* Real User ID */ __u32 ac_gid; /* Real Group ID */ }; struct acct_v3 { char ac_flag; /* Flags */ char ac_version; /* Always set to ACCT_VERSION */ __u16 ac_tty; /* Control Terminal */ __u32 ac_exitcode; /* Exitcode */ __u32 ac_uid; /* Real User ID */ __u32 ac_gid; /* Real Group ID */ __u32 ac_pid; /* Process ID */ __u32 ac_ppid; /* Parent Process ID */ __u32 ac_btime; /* Process Creation Time */ #ifdef __KERNEL__ __u32 ac_etime; /* Elapsed Time */ #else float ac_etime; /* Elapsed Time */ #endif comp_t ac_utime; /* User Time */ comp_t ac_stime; /* System Time */ comp_t ac_mem; /* Average Memory Usage */ comp_t ac_io; /* Chars Transferred */ comp_t ac_rw; /* Blocks Read or Written */ comp_t ac_minflt; /* Minor Pagefaults */ comp_t ac_majflt; /* Major Pagefaults */ comp_t ac_swaps; /* Number of Swaps */ char ac_comm[ACCT_COMM]; /* Command Name */ };プロセスアカウンティングでコマンド履歴を残す - いますぐ実践! Linux ...‎