微型Top源码剖析

Posted tab_tab_tab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微型Top源码剖析相关的知识,希望对你有一定的参考价值。

简单来说就是通过读取/proc下面的文件来进行,然后进行操作的。
感觉也就那回事 分别读取了 /proc/stat /proc/loadavg /proc/meminfo
还有就是/proc/进程ID/stat这几个文件 全部都是纯文本操作
其中
/proc/loadavg可以读取5、10、15分钟内CPU的平均均衡、运行的进程数、进程总数、最后一次运行的进程ID
/proc/stat 可以获取用户态(不含ROOT身份)运行时间、用户态(ROOT身份)运行时间、核心态运行时间、磁盘IO和非磁盘IO等待的时间还有什么软硬中断时候运行的时间。。。据说最新版本的还有在虚拟机中系统运行的时间和虚拟机中CPU运行时间这两个东西
/proc/meminfo 可以读取内存上限、剩余内存、给驱动开发用的Buffer大小、交换区大小、RAM中替换出去在磁盘上数据的大小等等
/proc/进程ID/stat这个文件更变态,基本上所有进程的状态信息都在里面。进程ID,名字,状态,父进程ID,线程组号,会话组号ID 还有什么终端设备号、缺页次数….虚拟空间大小….驻留的虚拟中间大小…还有什么SP指针 PC指针 运行在哪个CPU上 结束时候给父进程发送什么信号 被交换的页数 优先级 甚至调度策略 信号位图之类的 都完完全全在上面了.
感觉也就那回事,,,有以下想不到的或者是收获
1.用statfs这个函数来做预处理,看文件系统有没有装载,没有装载就直接退出。
2.else 后面跟while循环 老外不是else while 而是 else while 写在同一行看起来很漂亮
3.代码命名
4.老外会在代码中不自觉使用并且把代码写进去,好处一可以限制变量的生命周期和作用域(及时释放内存)
就这么多了….

主循环里面执行


int main()

    struct statics stat;
    machine_init(&stat);//stat已经指向全局静态数组
    struct system_info info;//
    get_system_info(&info);//读取loadavg文件 查看最近CPU平均负载
    struct top_proc proc;


    for(;;)
    
        printf("Used CPU:%.1f%%\\n",(float)info.cpustates[0]/10);
        printf("Nice CPU:%.1f%%\\n",(float)info.cpustates[1]/10);
        printf("System CPU:%.1f%%\\n",(float)info.cpustates[2]/10);
        printf("Idle CPU:%.1f%%\\n",(float)info.cpustates[3]/10);

        printf("total memroy:%d\\n", info.memory[0]);
        printf("free memroy:%d\\n", info.memory[1]);
        printf("buffers:%d\\n", info.memory[2]);
        printf("cached:%d\\n", info.memory[3]);
        printf("total swap:%d\\n", info.memory[4]);
        printf("free swap:%d\\n", info.memory[5]);

        sleep(2);
        printf("..................................\\n");
        get_system_info(&info);

        read_one_proc_stat( (pid_t)7443, &proc);//获取进程状态

        struct top_proc *p = &proc;

        printf("%s\\n",format_next_process(p));
    
    return 0;

其中的三个结构体statics system_info 为:

struct statics

    char **procstate_names;
    char **cpustate_names;
    char **memory_names;
#ifdef ORDER
    char **order_names;
#endif
;
struct system_info

    int    last_pid;
    double load_avg[3];
    int    p_total;
    int    p_active;     /* number of procs considered "active" */
    int    *procstates;
    int    *cpustates;
    int    *memory;
;

struct top_proc

    pid_t pid;
    uid_t uid;
    char name[64];
    int pri, nice;
    unsigned long size, rss;    /* in k */
    int state;
    unsigned long time;
    double pcpu, wcpu;
;

然后回到main中,看到第一个函数machine_init,其主要功能是对statics 进行初始化,让其指向一些全局的静态的东西。

int machine_init(struct statics * statics)//进入目录 设置好全局的变量

    //确定文件系统已经安装
    
        struct statfs sb;
        if (statfs(PROCFS, &sb) < 0 || sb.f_type != PROC_SUPER_MAGIC)//PROC_SUPER_MAGIC用于认知文件系统,宏的具体数字为0x9fa0
        
            fprintf(stderr, " proc filesystem not mounted on " PROCFS "\\n");
            return -1;
        
    

    /* chdir to the proc filesystem to make things easier */
    chdir(PROCFS);//进入 proc

    /* fill in the statics information */
    //指向全局的静态数组信息
    statics->procstate_names = procstatenames;
    statics->cpustate_names = cpustatenames;
    statics->memory_names = memorynames;

    /* all done! */
    return 0;

get_system_info(&info);//读取loadavg文件 查看最近CPU平均负载

void get_system_info(struct system_info *info)

    char buffer[4096+1];
    int fd, len;
    char *p;
    int i;
/*
/proc # cat loadavg
1.0   1.00 0.93 2/19 301
lavg_1 (1.0) 1-分钟平均负载
lavg_5 (1.00) 5-分钟平均负载
lavg_15(0.93) 15-分钟平均负载
nr_running (2) 在采样时刻,运行队列的任务的数目,与/proc/stat的procs_running表示相同意思
nr_threads (19) 在采样时刻,系统中活跃的任务的个数(不包括运行已经结束的任务)
last_pid(301) 最大的pid值,包括轻量级进程,即线程。
*/
    /* get load averages */
    
        fd = open("loadavg", O_RDONLY);
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
        buffer[len] = '\\0';
        //strtod将字符串转换成浮点数   
        info->load_avg[0] = strtod(buffer, &p);//5分钟内的平均负载
        info->load_avg[1] = strtod(p, &p);//10分钟内的平均负载
        info->load_avg[2] = strtod(p, &p);//15分钟内的平均负载
        p = skip_token(p);          /* skip 是正在运行的进程数  分母是进程总数*/   
        p = skip_ws(p);             //最后一个是最近运行的进程ID号
        if (*p)
            info->last_pid = atoi(p);
        else
            info->last_pid = -1;
    

    /* get the cpu time info */
    
        fd = open("stat", O_RDONLY);
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
        buffer[len] = '\\0';
        /*
        cpu  5484 160 6158 212335 16934 126 190 0 0
        cpu0 5484 160 6158 212335 16934 126 190 0 0
        intr 233270 194 357 0 2 1108 0 0 0 1 0 0 0 1303 0 0 0 2090 18850 75 266 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1228 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
        ctxt 342349
        btime 1459309158
        processes 6089
        procs_running 1
        procs_blocked 0
        softirq 338409 0 151659 572 208 19816 0 948 0 536 164670
        */
        p = skip_token(buffer);         /* "cpu" */  //跳过CPU
        cp_time[0] = strtoul(p, &p, 0);//nptr字符串根据参数base来转换成无符号的长整型数。
        //user (5484) 从系统启动开始累计到当前时刻,用户态的CPU时间(单位:jiffies) ,不包含 nice值为负进程。1jiffies=0.01秒
        cp_time[1] = strtoul(p, &p, 0);
        //nice (160) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间(单位:jiffies)
        cp_time[2] = strtoul(p, &p, 0);
        //system (6158) 从系统启动开始累计到当前时刻,核心时间(单位:jiffies)
        cp_time[3] = strtoul(p, &p, 0);
        //idle (212335) 从系统启动开始累计到当前时刻,除硬盘IO等待时间以外其它等待时间(单位:jiffies)

        //iowait (16934) 从系统启动开始累计到当前时刻,硬盘IO等待时间(单位:jiffies) ,
        //irq (126) 从系统启动开始累计到当前时刻,硬中断时间(单位:jiffies)
        //softirq (190) 从系统启动开始累计到当前时刻,软中断时间(单位:jiffies)

        /*
        stealstolen(0):       
        虚拟系统中的运行时间(since 2.6.11)
        guest(0) :
        //虚拟CPU  分配给虚拟机的CPU
        which is the time spent running a virtual  CPU  for  guest operating systems under the control of the Linux kernel(since 2.6.24)
        */
        /* convert cp_time counts to percentages */
        percentages(4, cpu_states, cp_time, cp_old, cp_diff);
    

    /* get system wide memory usage */
    
        char *p;

        fd = open("meminfo", O_RDONLY);
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
        buffer[len] = '\\0';

        p = buffer;
        p = skip_token(p);//跳过
        memory_stats[0] = strtoul(p, &p, 10); /* 内存上限 */ ///10为进制数 

        p = strchr(p, '\\n');//extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置。
        p = skip_token(p);
        memory_stats[1] = strtoul(p, &p, 10); /* 剩余内存 */


        p = strchr(p, '\\n');
        p = skip_token(p);
        memory_stats[2] = strtoul(p, &p, 10);
        //用来给块设备做的缓冲大小(只记录文件系统的metadata以及 tracking in-flight pages,就是说 buffers是用来存储,
        //目录里面有什么内容,权限等等。)

        p = strchr(p, '\\n');
        p = skip_token(p);
        memory_stats[3] = strtoul(p, &p, 10); /* cached memory */
//Cached: 用来给文件做缓冲大小(直接用来记忆我们打开的文件). 它不包括SwapCached
        for(i = 0; i< 8 ;i++) 
            p++;
            p = strchr(p, '\\n');///跳过8个
        

        p = skip_token(p);
        memory_stats[4] = strtoul(p, &p, 10); /* total swap */
        // SwapTotal: 交换空间的总和
        p = strchr(p, '\\n');
        p = skip_token(p);
        memory_stats[5] = strtoul(p, &p, 10); /* free swap */
        //SwapFree: 从RAM中被替换出暂时存在磁盘上的空间大小
    
/*
  MemTotal: 所有可用RAM大小 (即物理内存减去一些预留位和内核的二进制代码大小)

    MemFree: LowFree与HighFree的总和

    Buffers: 用来给块设备做的缓冲大小(只记录文件系统的metadata以及 tracking in-flight pages,就是说 buffers是用来存储,目录里面有什么内容,权限等等。)

    Cached: 用来给文件做缓冲大小(直接用来记忆我们打开的文件). 它不包括SwapCached

    SwapCached: 已经被交换出来的内存,但仍然被存放在swapfile中。用来在需要的时候很快的被替换而不需要再次打开I/O端口。

    Active: 最近经常被使用的内存,除非非常必要否则不会被移作他用.

    Inactive: 最近不经常被使用的内存,非常用可能被用于其他途径.

    HighTotal:
    HighFree: 高位内存是指所有在860MB以上的内存空间,该区域主要用于用户空间的程序或者是缓存页面。内核必须使用不同的手法使用该段内存,因此它比低位内存要慢一些。

    LowTotal:
    LowFree: 低位可以达到高位内存一样的作用,而且它还能够被内核用来记录一些自己的数据结构。
                   Among many other things, it is where everything from the Slab is
                   allocated.  Bad things happen when you're out of lowmem.

    SwapTotal: 交换空间的总和

    SwapFree: 从RAM中被替换出暂时存在磁盘上的空间大小

    Dirty: 等待被写回到磁盘的内存大小。

    Writeback: 正在被写回到磁盘的内存大小。

    Mapped: 影射文件的大小。

    Slab: 内核数据结构缓存

    VmallocTotal: vmalloc内存大小

    VmallocUsed: 已经被使用的虚拟内存大小。

    VmallocChunk: largest contigious block of vmalloc area which is free

    CommitLimit:
    Committed_AS:

*/
    /* set arrays and strings */
    info->cpustates = cpu_states;
    info->memory = memory_stats;

/*
/proc/1 # cat stat

3348 (xdg-screensaver) S 1 2790 2790 0 -1 4202560 30551 330530 0 0 19 77 100 391 20 0 1 0 53358 5296128 171 4294967295 
134508544 135361360 3214570048 3214565048 10228772 0 65536 6 65536 0 0 0 17 0 0 0 0 0 0

pid=3348 进程(包括轻量级进程,即线程)号

comm= xdg-screensaver 应用程序或命令的名字

task_state=S 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead

ppid=1 父进程ID

pgid=2790 线程组号/

sid=2790 c该任务所在的会话组ID

tty_nr=0 (pts/3) 该任务的tty终端的设备号,INT(0/256)=主设备号,(0-主设备号)=次设备号

tty_pgrp=-1 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。

task->flags=4202560进程标志位,查看该任务的特性///

min_flt=30551该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数@@@@@@@@@@@@@@

cmin_flt=330530 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目

maj_flt=0 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数

cmaj_flt=0 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目

utime=19 该任务在用户态运行的时间,单位为jiffies

stime=77 该任务在核心态运行的时间,单位为jiffies

cutime=100 累计的该任务的所有的waited-for进程曾经在用户态运行的时间,单位为jiffies

cstime=391 累2计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies

priority=20 任务的动态优先级

nice=0 任务的静态优先级

num_threads=1 该任务所在的线程组里线程的个数


it_real_value=0 由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位.

start_time=53358 该任务启动的时间,单位为jiffies

vsize=5296128(bytes) 该任务的虚拟地址空间大小

rss=171(page) 该任务当前驻留物理地址空间的大小

Number of pages the process has in real memory,minu 3 for administrative purpose.

这些页可能用于代码,数据和栈。
//4294967295
rlim=4294967295=0xFFFFFFFF(bytes) 该任务能驻留物理地址空间的最大值

start_code=134508544=0x8000  该任务在虚拟地址空间的代码段的起始地址(由连接器决定)

end_code=135361360该任务在虚拟地址空间的代码段的结束地址

start_stack=3214570048=0Xbeb0ff30该任务在虚拟地址空间的栈的开始地址

kstkesp=3214565048  sp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致.

kstkeip=10228772 =0X10FD58 指向将要执行的指令的指针, PC(32 位指令指针)的当前值.

pendingsig=0 待处理信号的位图,记录发送给进程的普通信号

block_sig=65536 阻塞信号的位图

sigign=6 忽略的信号的位图

sigcatch=65536被俘获的信号的位图

wchan=0  如果该进程是睡眠状态,该值给出调度的调用点

nswap=0 被swapped的页数

cnswap=0 所有子进程被swapped的页数的和

exit_signal=17 该进程结束时,向父进程所发送的信号

task_cpu(task)=0 运行在哪个CPU上

task_rt_priority=0 实时进程的相对优先级别

task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程 
*/

然后是一堆的打印。。。忽略掉。。。再看一个函数是读取进程信息的函数

void read_one_proc_stat(pid_t pid, struct top_proc *proc)

    char buffer[4096], *p;

    /* grab the proc stat info in one go */
    
        int fd, len;

        sprintf(buffer, "%d/stat", pid);

        fd = open(buffer, O_RDONLY);//打开%d/stat
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);

        buffer[len] = '\\0';
    

    proc->uid = proc_owner(pid);///

    /* parse out the status */

    p = buffer;
    p = strchr(p, '(')+1;           /* skip pid */
    
        char *q = strrchr(p, ')');//跳过pid和进程名称
        int len = q-p;
        if (len >= sizeof(proc->name))///进程名字过长 比结构体中长要限制
            len = sizeof(proc->name)-1;
        memcpy(proc->name, p, len);//拷贝进程名字
        proc->name[len] = 0;
        p = q+1;
    
    //R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead
    p = skip_ws(p);
    switch (*p++)
    
    case 'R': proc->state = 1; break;//R:runnign,
    case 'S': proc->state = 2; break;//S:sleeping TASK_INTERRUPTIBLE
    case 'D': proc->state = 3; break;// D:disk sleep (TASK_UNINTERRUPTIBLE)
    case 'Z': proc->state = 4; break;//Z:zombie僵死进程
    case 'T': proc->state = 5; break;//T: stopped
    case 'W': proc->state = 6; break;//等待???Wait  NO  swap切出交换区
    

    p = skip_token(p);              /* skip ppid父进程ID */
    p = skip_token(p);              /* skip pgrp 线程组号 */
    p = skip_token(p);              /* skip session该任务所在的会话组ID */
    p = skip_token(p);              /* skip tty该任务的tty终端的设备号 */
    p = skip_token(p);              /* skip tty pgrp 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID */
    p = skip_token(p);              /* skip flags进程标志位 */
    p = skip_token(p);              /* skip min flt 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数*/
    p = skip_token(p);              /* skip cmin flt 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目*/
    p = skip_token(p);              /* skip maj flt 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数*/
    p = skip_token(p);              /* skip cmaj flt 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目*/

    proc->time = strtoul(p, &p, 10);        /* utime在用户态运行的时间, */
    proc->time += strtoul(p, &p, 10);       /* stime在核心态运行的时间, */

    p = skip_token(p);              /* skip cutime 累计的该任务的所有的waited-for进程曾经在用户态运行的时间*/
    p = skip_token(p);              /* skip cstime 累计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies*/

    proc->pri = strtol(p, &p, 10);      /* priority任务的动态优先级 */
    proc->nice = strtol(p, &p, 10);     /* nice任务的静态优先级 */
//该任务所在的线程组里线程的个数   没有!!!
    p = skip_token(p);              /* skip timeout该任务所在的线程组里线程的个数 */
    p = skip_token(p);              /* skip it_real_val  由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位*/
    p = skip_token(p);              /* skip start_time  该任务启动的时间*/

    proc->size = bytetok(strtoul(p, &p, 10));   /* vsize 该任务的虚拟地址空间大小 */
    proc->rss = pagetok(strtoul(p, &p, 10));    /* rss  该任务当前驻留物理地址空间的大小*/

#if 0
    /* for the record, here are the rest of the fields */
    p = skip_token(p);              /* skip rlim 该任务能驻留物理地址空间的最大值*/
    p = skip_token(p);              /* skip start_code 该任务在虚拟地址空间的代码段的起始地址(由连接器决定)*/
    p = skip_token(p);              /* skip end_code该任务在虚拟地址空间的代码段的结束地址 */
    p = skip_token(p);              /* skip start_stack 该任务在虚拟地址空间的栈的开始地址*/
    p = skip_token(p);              /* skip sp  sp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致.*/
    p = skip_token(p);              /* skip pc 指向将要执行的指令的指针, PC(32 位指令指针)的当前值.*/
    p = skip_token(p);              /* skip signal  待处理信号的位图,记录发送给进程的普通信号*/
    p = skip_token(p);              /* skip sigblocked  阻塞信号的位图*/
    p = skip_token(p);              /* skip sigignore 忽略的信号的位图*/
    p = skip_token(p);              /* skip sigcatch被俘获的信号的位图 */
    p = skip_token(p);              /* skip wchan 如果该进程是睡眠状态,该值给出调度的调用点*/

///新版本系统有的
    p = skip_token(p);              /*  nswap=0 被swapped的页数 */
    p = skip_token(p);              /* cnswap=0 所有子进程被swapped的页数的和 */
    p = skip_token(p);              /* exit_signal=0  该进程结束时,向父进程所发送的信号*/
    p = skip_token(p);              /* task_cpu(task)=0 运行在哪个CPU上*/
    p = skip_token(p);              /* task_rt_priority=0 实时进程的相对优先级别*/
    p = skip_token(p);              /*task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程 */
#endif

以上是关于微型Top源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

Redis源码剖析之AOF

Poll源码剖析

select源码剖析

「源码剖析」如何实现一个虚拟DOM算法

Nacos Config 动态刷新源码剖析

php源码建博客4--实现MVC结构微型框架