Hackbench程序分析报告
一、Hackbench功能概述
    Hackbench是一个测试Linux进程调度器的性能、开销和可伸缩性的基准测试程序。Hackbench测试可以运行在不同的硬件平台上和不同的Linux操作系统版本上,通过模拟一组C/S模式下的客户端进程和服务器端进程的通信来测试Linux进程调度器的性能、开销和可伸缩性。
二、Hackbench工作原理
    Hackbench测试分为若干组进行,组数num_group由命令行给出,每个组有num_fds组读写进程,num_fds可以设置初值20。在每一个测试组中,创建num_fds个客户端进程(读进程)和num_fds个服务器进程(写进程)分别负责信息的读写工作,创建num_fds个管道负责读写进程之间的通信。读进程负责从从管道中读取数据,写进程负责向管道中写数据。参数loops为每个写进程向每个读进程写入的信息条数,每条信息可设定为100个字符。每个写进程向每个管道中分别发送loops条信息,故每个写进程共发送num_fds*loops条信息,所有写进程共发
送num_fds*num_fds*loops条信息。每个读进程从与之相连的管道中读取num_fds*loops条信息(每个写进程发送loops条,共num_fds个写进程),所有读进程共读取num_fds*num_fds*loops条信息,与写进程发送的信息数相等。
    测试中用到两个管道readyfds[2]和wakefds[2]来进行主进程和所有读写子进程的同步工作。主进程在创建好所有的管道和读写进程之后,通过readyfds[2]和wakefds[2]的同步,在所有读写进程准备好之后,开始计时,用start标识此时间点。计时开始后,各个读写进程之间开始相互通信,读进程在读完数据和写进程在写完数据后,进程结束。所有读写进程都完成工作之后,计时结束,用stop标识此时间点。然后计算此段所消耗的时间diff = stop start。最后输出diff。通过在不同硬件平台和不同Linux版本上运行此程序以比较diff的差异,可以评测在不同平台下不同Linux版本的进程调度器的性能、开销和可伸缩性。
三、Hackbench.c源代码流程分析
1、程序用到的主要参数变量:
    loops:每个写进程向每个读进程发送的信息数,设定为100。
    use_pipes:是否使用管道pipe()创建管道,若命令行中有-pipe选项,则use_pipes = 1。
              否则use_pipes = 0 。全局变量。
    num_groups,num_fds:分别为组数和每组中读/写进程的个数。
    total_children:所有子进程个数,即total_children = num_groups * num_fds * 2 。
    start, stop, diff:分别记录进程调度的开始时间,结束时间,以及这期间的耗时。
    readyfds[2], wakefds[2]:两个用于所有进程同步的管道,readyfds表示已经准备好了,
                          wakefds用来通知所有子进程要开始计时了。
2、主要函数:
static void barf(const char *msg);    打印一些错误信息。
static void fdpair(int fds[2]);
根据全局变量use_pipes的值选择用pipe()还是socketpair()建立管道,文件描述符返回到fds
static void ready(int ready_out, int wakefd);
    协调各进程之间的同步信息,这个函数要和其他函数合作,下文要重点分析。
static void sender(unsigned int num_fds, int out_fd[num_fds], int ready_out,int wakefd);
    写进程用此函数向读进程发送信息。num_fds为要发送信息的进程的个数;out_fd数组
中的元素为文件描述符,指向读写进程通信管道的写端;ready_out和wakefd用来进行进程间的同步。调用ready()函数来进行同步工作。
static void receiver(unsigned int num_packets, int in_fd, int ready_out, int wakefd);
    读进程用此函数从管道中读取信息。num_packets为读取的信息个数,in_fd为管道;ready_out和wakefd用来进行进程间的同步。调用ready()函数来进行同步工作。
static unsigned int group(unsigned int num_fds, int ready_out, int wakefd);
    此函数用来对每个测试组创建读写子进程,并做好准备读写工作。num_fds为组中进程的个数。ready_out和wakefd用来进行进程间的同步。
3、程序执行流程分析:
hackbench.c的程序流程图如下:
                        图1  hackbench程序流程图
在main主函数中:
(1)、从命令行读取参数,检查输入参数的正确性。
(2)、创建两个用于进程同步的管道readyfds[2]和wakefds[2]。
(3)、根据组数num_groups调用group()函数,在每一组中分别创建num_fds个读进程和num_fds个写进程,每个读写进程发送各自的读写请求,通过readyfds[1]通知主进程准备就绪,并阻塞在wakefds[0]处,各个读写进程都处于阻塞状态。group()函数返回每组的子进程数。最后记录所有的读写进程数total_children。
(4)、主进程通过readyfds[0]得知所有读写子进程都准备就绪后,开始计时,通过
wakefds[1]接触所有子进程的阻塞。
(5)、解除各个进程的阻塞,开始计时,各进程通信开始。计时流程如下:
                      图2 hackbench计时流程
    在每一个进程组内,一共有num_fds个读进程和num_fds个写进程,num_fds个管道。每个读进程连接一个管道。每个写进程分别向每个管道的写端写入loops = 100 条信息,每个写进程从管道中读出num_fds*loops条信息。如下图:
                            图3 进程组内的通信
(7)、所有进程通信完毕,各个子进程先后退出。计时结束。
(8)、计算所用时间diff。
4、主进程和各读写子进程间的同步问题:
    主进程和各个读写子进程间的同步问题主要是通过两个管道文件readyfds[2]和wakefds[2]实现的,如下图所示:
                    图4  主进程和各子进程间的同步
    程序在开始时首先创建两个管道文件readyfds[2]和wakefds[2]。在对每个组创建读写进程时,readyfds[1]和wakefds[0]作为参数传入group()函数中。
    在group()函数中创建了读写进程之后,在读写进程发出读写请求,分别调用receiver()和sender()。注意,此时readyfds[1]和[wakefds[0]都作为参数传入这两个函数。这两个函数在读写信息之前,都要先调用ready()函数,readyfds[1]和[wakefds[0]作为参数传入ready()函数。
    在ready()函数中,各个子进程首先向readyfds[1]写入一个字符(在图2中的1处),以示已经准备好读写了,。然后把wakefds[0]作为参数填充到pollfd结构中:
    struct pollfd pollfd = { .fd = wakefd, .events = POLLIN };
    POLLIN表示要等待一个读数据事件的发生。
    然后调用进程间通信实验总结poll(&pollfd, 1, -1)来等待读事件的发生,也即在图2中4的位置读数据,此时若无数据读出,则进程一直阻塞在此。
    主进程在所有读写子进程创建完毕后,从readyfds[0]出读取一个数据,以测试各个子进程是否都已经准备好了(在图2中的2处)。由于一共有total_children个子进程,所以一共要读取total_children次。
    在确定各个子进程都准备好了之后,计时开始。主进程向wakefds[1]写一个字符,以示计时开始了(在图2中的3处)。各个阻塞在图2中的4处的进程在读到数据之后,即全部接触阻塞了。此时各个进程就可以开始相互通信了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。