2007-10-07

manpages索引更新

从debian sarge 转到 etch后,就发现man东西少了很多,基本的函数,系统调用都没有相应的man手册页。猜想可能是某些和开发相关的包没有装。后来在Synaptic中搜索documentaion,一项一项找,最后发现是manpages-dev没有装。

apt-get install manpages-dev

基本的系统调用、库函数等就都有了。但man -k 和man -f却依然找不到这些函数。猜想可能是存在某些索引之类的东东。可是在网上没有找到在哪里更新索引的指导。man自身的手册页实在太长,看不下去。后来也就将就了。

ubuntu和debian的情况一样,同样默认不安装manpages-dev,无奈,只能忍受。

以前小时候找不到东西经常会很生气,而且越气急败坏越找不到。往往一个小东西搞得一天心情都很差。后来妈妈劝导我,找不到就放一放,过一段时间它会自己出来的。且不论这个理论是否正确,对于调节情绪来说还是相当有用的,况且事实证明最终那些找不到的小东西真会在日后的某一天中被偶然发现。所谓”众里寻她千百度,蓦然回首,她在灯火阑珊处”是也。

而且,我想,那一刻,任何人都会怀有一份惊喜与开心的。

前些日子偶然看到一博客,顺着他的文章一篇篇看下去 — 忽然,ho ho,the answer is Here!

http://blog.verycd.com/yoyopub/showentry=34994

原来manpage的索引由mandb命令管理,在安装了新的manpage文件后,只需

mandb -c
[/basj]

更新一下索引即可。

again,thanks for sharing。

–update–

日前发现manpages-dev包中没有posix相关的内容,包括pthread等的说明,找了一下,这些东西在manpages-posix和manpages-posix-dev包中,装上再重新更新一下索引就可以查阅了。

–EOF–

2007-10-03

Linux下瞬时cpu使用率的统计

top是linux下查看cpu使用率最常见的命令,功能很强大,使用也很方便,是系统管理的好帮手。

在bash脚本中集成top命令获取cpu使用率很简单,但是如果我们想要在C代码中获取当前cpu占用率,又该如何处理呢?

首先要了解一下top的工作原理

1、linux系统通过/proc/stat伪文件系统来输出当前的cpu使用情况,而top正是通过读取此文件中的数据来实现cpu使用情况的统计。
2、cpu占用率并不是一个瞬时概念,而是一个短时间内的统计值,因此无法通过瞬时计算得出,而必须通过一定时间差内的统计来实现。

stat的文件结构

stat文件的前几行为CPU使用信息描述。
具体有多少行取决于系统有多少块CPU,但行中的字段都是相同的。第一行以字符串’CPU’开头,为系统总的CPU使用情况。其余行以’CPU’+CPUID开头,用以分别描述单个CPU。

第一行内容如下:

cpu 15718 197 2574 136003 3881 690 531 0

数字的单位为USER_HZ(1/100ths of a second on most architectures)。
第一个字符串为CPU标识,其后四个数字依次为在用户态消耗的时间片,在低优先级的用户态消耗的时间片,内核态消耗的时间片以及空闲时间片。

2.4内核中,以上即为CPU使用信息的全部内容。但在2.6内核中,这一行还多出了三个额外的数据。分别为 iowait,irq,softirq等操作所耗费的时间片。

了解了以上信息后,我们就可以开始准备编码了~ 不过,最好还是有个参考~

参照busybox的top源代码,我们实现自己的cpu统计程序如下:

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>

#define SMLBUFSIZ 256

typedef unsigned long long ul_t;

typedef struct cpu_t {
    ul_t u, n, s, i, w, x, y, z; // as represented in /proc/stat
    ul_t u_sav, s_sav, n_sav, i_sav, w_sav, x_sav, y_sav, z_sav; // in the order of our display
} cpu_t;

cpu_t *cpus_refresh (cpu_t *cpus)
{
    static FILE *fp = NULL;
    int i;
    int num;
    char buf[SMLBUFSIZ];

    if (!fp) {
        if (!(fp = fopen("/proc/stat", "r")))
            fprintf(stderr,"Failed /proc/stat open: %s", strerror(errno));
    }
    rewind(fp);
    fflush(fp);

    // first value the last slot with the cpu summary line
    if (!fgets(buf, sizeof(buf), fp)) fprintf(stderr,"failed /proc/stat read");

    cpus->x = 0;
    cpus->y = 0;
    cpus->z = 0;
    num = sscanf(buf, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu",
            &cpus->u,
            &cpus->n,
            &cpus->s,
            &cpus->i,
            &cpus->w,
            &cpus->x,
            &cpus->y,
            &cpus->z
            );
    if (num < 4)
        fprintf(stderr,"failed /proc/stat read");

    return cpus;
}

static void format_output (cpu_t *cpu, const char *pfx)
{
#define TRIMz(x)  ((tz = (long long)(x)) < 0 ? 0 : tz)
    long long u_frme, s_frme, n_frme, i_frme, w_frme, x_frme, y_frme, z_frme, tot_frme, tz;
    float scale;

    u_frme = cpu->u - cpu->u_sav;
    s_frme = cpu->s - cpu->s_sav;
    n_frme = cpu->n - cpu->n_sav;
    i_frme = TRIMz(cpu->i - cpu->i_sav);
    w_frme = cpu->w - cpu->w_sav;
    x_frme = cpu->x - cpu->x_sav;
    y_frme = cpu->y - cpu->y_sav;
    z_frme = cpu->z - cpu->z_sav;
    tot_frme = u_frme + s_frme + n_frme + i_frme + w_frme + x_frme + y_frme + z_frme;
    if (tot_frme < 1) tot_frme = 1;
    scale = 100.0 / (float)tot_frme;

    fprintf(stderr,"%s: %.2f%%us,   %.2f%%sy,   %.2f%%ni,   %.2f%%id,   %.2f%%wa,   %.2f%%hi,   %.2f%%si,   %.2f%%st\n",
                pfx,
                (float)u_frme * scale,
                (float)s_frme * scale,
                (float)n_frme * scale,
                (float)i_frme * scale,
                (float)w_frme * scale,
                (float)x_frme * scale,
                (float)y_frme * scale,
                (float)z_frme * scale);
    
    cpu->u_sav = cpu->u;
    cpu->s_sav = cpu->s;
    cpu->n_sav = cpu->n;
    cpu->i_sav = cpu->i;
    cpu->w_sav = cpu->w;
    cpu->x_sav = cpu->x;
    cpu->y_sav = cpu->y;
    cpu->z_sav = cpu->z;

#undef TRIMz
}

int main(){

    cpu_t * cpu;
    cpu =(cpu_t *)malloc(sizeof(cpu_t));
    memset(cpu,0,sizeof(cpu_t));


    while(1){
        cpus_refresh(cpu);
        format_output(cpu,"Cpu usage:  ");
        sleep(1);
    }

}

需要注意的是,无论用什么方法,我们都无法获取到系统瞬时的CPU使用率,而只能通过近期的使用情况来推测。

在这个程序中,第一次调用cpus_refresh时返回的并不是当前的CPU使用率,而是从系统启动以来至今为止的CPU使用率,这并不能反映当前的CPU使用情况。第二次调用计算差值后得到的是这一秒内CPU的使用率,用它来度量当前瞬时的使用率还是合理的。

–EOF-

2007-09-27

nanosleep精度问题分析

最近有个同学在做流量模拟,发包时需要精确控制发包间隔,即要用到高精度的延时。

首先想到的便是nanosleep系统调用,毕竟从接口上看,它可以达到纳秒级的精度(1*10^-9)。

然而实际上nanosleep的解析度远没有达到预期,把时间间隔设地再小,精度也始终只有一微秒左右。

WHY?

READ THE FUCKING MANUAM

原来,2.6内核中nanosleep实现基于内核中的普通定时器机制,最大解析度也只有1/HZs,所以nanpsleep暂停的最小单位即是1ms,考虑到进程的切换与函数调用消耗的时间,实际的最小单位可能会在10ms左右甚至更长。

如果是这样,那为何内核还要提供这样一个名不副实的函数呢?
原来在2.4内核中,当把进程调度策略设置成实时级别的,诸如SCHED_FIFO或SCHED_RR,则对于较小的延迟时间,内核将采用忙等的方式进行计时处理。由此提供的解析精度可以达到2ms以下。

尝试了一下,实际解析度的确升高了,效果不错。

#include   <stdio.h>
#include   <time.h>
#include   <sys/time.h>
#include   <sys/types.h>
#include   <unistd.h>
#include   <sched.h>

#define BILLION  1000000L

void do_sleep(int looptimes,int sleeptimes){
    
    int i,j;
    struct timespec tv={0,1};
    struct timeval start, stop;
    long accum;

    for(i=0;i<looptimes;i++){
        gettimeofday(&start,NULL);

        for(j=0; j<sleeptimes; j++){
            nanosleep(&tv,NULL);
        }

        gettimeofday(&stop,NULL);
        accum = ( stop.tv_sec - start.tv_sec )*BILLION + ( stop.tv_usec - start.tv_usec );

        fprintf(stderr,"%ld\t", accum/sleeptimes );
    }
    
    fprintf(stderr,"\n");
}

int main( int argc, char** argv )
{
    struct sched_param sp;
        
    sp.sched_priority=0;
    sched_setscheduler(0,SCHED_FIFO,&sp);

    fprintf(stderr,"Schedule priority: %d\n",sp.sched_priority);
        
    do_sleep(10,100);

    sp.sched_priority=99;
    sched_setscheduler(0,SCHED_FIFO,&sp);
    
    fprintf(stderr,"Schedule priority: %d\n",sp.sched_priority);
    
    do_sleep(10,100);
    
    return 0;
}

实验环境:
kernel version: Linux version 2.4.20-8smp (bhcompile@stripples.devel.redhat.com) (gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)) #1 SMP Thu Mar 13 16:43:01 EST 2003

–EOF–

2007-03-26

当signal遇上exec

一般来说,多进程环境下的Linux程序,子进程是继承父进程的信号处理方式的。也就是说,如果在父进程中为某一个信号指定了处理函数,那么子进程在收到这个信号时同样会调用这个处理函数。

举例如下:

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>

void handler(int signum,siginfo_t *info,void *myact)
{
    fprintf(stderr,"receive signal %d\n", signum);
    fprintf(stderr,"Signal is sended by %d as reason : %d on error: %d\n"
                    , info->si_pid,info->si_code,info->si_errno);
}

int main(int argc,char**argv)
{
    struct sigaction act;

    sigemptyset(&act.sa_mask);

    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=handler;

    if(sigaction(SIGUSR1,&act,NULL) < 0)
    {
        printf("install sigal error\n");
    }

    pid_t pid=fork();
    if(pid == 0)
    {
        for(int i=0;i<100;i++)
        {
            if(i==5)
            {
                fprintf(stderr,"Send SIGUSR1 signal......\n");
                kill(getpid(),SIGUSR1);
            }
            else
            {
                fprintf(stderr,"Sleeping......\n");
                sleep(1);
            }
        }
        return 0;
    }

    fprintf(stderr,"Child Process id is : %d\n",pid);

    wait(NULL);

    return 0;
}

但很多情况下需要在fork之后在子进程中调用exec族函数来执行一个新的程序。那这种情况下,父子进程对于信号处理的继承关系又是怎样的呢。

将上面的程序改动一下。

main.cpp:

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>

void handler(int signum,siginfo_t *info,void *myact)
{
    fprintf(stderr,"receive signal %d\n", signum);
    fprintf(stderr,"Signal is sended by %d as reason : %d on error: %d\n"
                    ,info->si_pid,info->si_code,info->si_errno);
}

int main(int argc,char**argv)
{
    struct sigaction act;

    sigemptyset(&act.sa_mask);

    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=handler;

    if(sigaction(SIGUSR2,&act,NULL) < 0)
    {
        printf("install sigal error\n");
    }

    pid_t pid=fork();

    if(pid == 0)
    {
        execlp("/root/test/sigusr","./sigusr",NULL);
        return 0;
    }

    fprintf(stderr,"Child Process id is : %d\n",pid);

    wait(NULL);

    return 0;
}

将原来子进程中的语句放到一个新的文件中,并将其编译成sigusr可执行文件 :

sigusr.cpp:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

int main(){

    for(int i=0;i<100;i++)
    {
        if(i==5)
        {
            fprintf(stderr,"Send SIGUSR1 signal......\n");
            kill(getpid(),SIGUSR1);
        }
        else
        {
            fprintf(stderr,"Sleeping......\n");
            sleep(1);
        }
    }
}

编译执行main.cpp可以看到,子进程打印了五条sleeping……语句后就退出了,并没有调用父进程指定的信号处理函数。

分析:

1、fork生成的子进程中继承了父进程所有的数据,当然包括信号处理函数的入口地址等信息,所以当子进程继承父进程的信号处理方式不会出现问题。

2、fork后调用exec后发生了什么呢?

open group上的exec的文档是这么说的 :

The exec functions replace the current process image with a new process image. The new image is constructed from a regular, executable file called the new process image file. There is no return from a successful exec, because the calling process image is overlaid by the new process image.

exec 后,系统用参数指定的可执行文件的映像将原由子进程完全替换掉,只留下进程号等一小部分信息不变,这样的话在exec后子进程中已经不存在父进程中的数 据,子进程也无法得知父进程信号处理函数的入口地址。在这种情况下,继承父进程的信号处理机制也就无从谈起了,因为这是不可能的。
但有一个例外情况,那就 是如果在父进程中将对某个信号的处理方式设置为忽略(SIG_IGN),那么exec后的子进程会继承父进程的设置,在子进程中忽略这个信号。因为设置为 忽略的话就不涉及到寻找处理函数入口地址了,这是可行的。

Open Group对此的描述如下:

Signals set to the default action (SIG_DFL) in the calling process image are set to the default action in the new process image. Signals set to be ignored (SIG_IGN) by the calling process image are set to be ignored by the new process image. Signals set to be caught by the calling process image are set to the default action in the new process image. After a successful call to any of the exec functions, alternate signal stacks are not preserved and the SA_ONSTACK flag is cleared for all signals.

再引申一下,还有一种情况需要注意,如果在父进程中调用了atexit()函数,那么在exec后的子进程中同样会失效。原理和前文所述的信号设置是一样的 🙂

–EOF–

参考文档:
Open Group The Single UNIX ® Specification, Version 2,exec:
http://www.opengroup.org/onlinepubs/007908799/xsh/exec.html