分享

Iperf 源代码分析(七)

 MikeDoc 2012-06-13
转自: http://blog.csdn.net/willon_tom/article/details/4470225
下面以程序的执行为主线,简要分析一下Iperf源代码的实现。
 
main函数
 
main函数在文件main.cpp中定义,它是程序的入口点。
/* -------------------------------------------------------------------
 * global variables
 * ------------------------------------------------------------------- */
#define GLOBAL()
Condition gQuit_cond;
/* -------------------------------------------------------------------
 * sets up signal handlers
 * parses settings from environment and command line
 * starts up server or client thread
 * waits for all threads to complete
 * ------------------------------------------------------------------- */
int main( int argc, char **argv ) {
    Listener *theListener = NULL;
    Speaker  *theSpeaker  = NULL;
    // signal handlers quietly exit on ^C and kill
    // these are usually remapped later by the client or server
    my_signal( SIGTERM, sig_exit );
    my_signal( SIGINT,  sig_exit );
    signal(SIGPIPE,SIG_IGN);
    // perform any cleanup when quitting Iperf
    atexit( cleanup );
    ext_Settings* ext_gSettings = new ext_Settings;
    Settings* gSettings = NULL;
    // read settings from environment variables and command-line interface
    gSettings = new Settings( ext_gSettings );
    gSettings->ParseEnvironment();
    gSettings->ParseCommandLine( argc, argv );
    // start up client or server (listener)
    if ( gSettings->GetServerMode() == kMode_Server ) {
        // start up a listener
        theListener = new Listener( ext_gSettings );
        theListener->DeleteSelfAfterRun();
        // Start the server as a daemon
        if ( gSettings->GetDaemonMode() == true ) {
            theListener->runAsDaemon(argv[0],LOG_DAEMON);
        }
        theListener->Start();
        if ( ext_gSettings->mThreads == 0 ) {
            theListener->SetDaemon();
            // the listener keeps going; we terminate on user input only
            waitUntilQuit();
            if ( Thread::NumUserThreads() > 0 ) {
                printf( wait_server_threads );
                fflush( 0 );
            }
        }
    } else if ( gSettings->GetServerMode() == kMode_Client ) {
        theSpeaker = new Speaker(ext_gSettings);
        theSpeaker->OwnSettings();
        theSpeaker->DeleteSelfAfterRun();
        theSpeaker->Start();
    } else {
        // neither server nor client mode was specified
        // print usage and exit
        printf( usage_short, argv[0], argv[0] );
    }
    // wait for other (client, server) threads to complete
    Thread::Joinall();
    DELETE_PTR( gSettings );  // modified by qfeng
    // all done!
    return 0;
} // end main
 
程序首先通过my_signal和signal函数为信号SIGTERM、SIGINT及SIGPIPE安装信号处理程序,这里的 my_signal函数在文件lib/signal.c中定义,它完成的功能与signal是一样的,但提高了兼容性。atexit函数可以使程序退出时 自动执行cleanup函数完成一些扫尾工作(主要是将服务器维护的客户信息链表释放)。ext_Settings是一个保存设定的参数的结构体,这些测 量时使用的参数或为用户通过命令行指定,或使用默认值。将信息组织为一个结构体有利于信息在各对象间共享和传递。Settings类对 ext_Settings结构和在其上进行的操作进行了封装。关于ext_Settings和Settings可以参见src/Settings.hpp 和src/Settings.cpp。在生成实例gSettings之后,main分表调用gSettings对象的ParseEnvironment方 法和ParseCommandLine方法从环境变量和命令行读取参数填充到结构ext_gSettings中。由调用的顺序可见,命令行参数的设定将覆 盖环境变量的设定。
 
如果是服务器模式,main生成Listener实例,DeleteSelfAfterRun方法是基类Thread实现的方法,它将成员变量 mDeleteSelf设为true,在线程的主过程(Run函数)结束之后,系统会自动释放内存空间(见对Thread类的讨论)。若用户指定 Iperf作为守护进程运行,则main调用runAsDaemon方法使自己在后台运行。Start方法启动Listener线程开始监听,在 Start方法中会调用pthread_create系统调用创建新线程,此后程序就存在两个执行线程了(见前文对Thread类的讨论)。新线程执行 Listerner类的Run函数。原线程从Start函数中返回。ext_gSettings的mThreads在客户端表示并行执行的线程数,在服务 器端表示服务器总共接收的请示数,为0表示服务器永远运行下去。若为后一种情况,main函数调用Thread基类的SetDaemon函数使自身不再收 线程接收同步机制的限制(所有的Thread类的派生类线程的执行结束可以通过Thread类的static成员函数Joinall进行同步, SetDaemon函数使调用线程不在Joinall函数的“join”范围之内,参见前文对Thread类的讨论),之后调用 waitUntilQuit函数。main函数将在waitUntilQuit函数中阻塞,直到所有的线程结束。
 
Iperf的服务器端结束过程比较复杂。在一般情况下,Iperf服务器将一直运行,直到用户按下中断键(Ctrl-C等)时程序退出。由于存 在多个线程,有的操作系统会把传向进程的信号传递给进程里的每一个线程,因此在主程序退出时必须考虑所有线程的同步问题,这是通过信号处理程序实现的。
 
在main函数的开始,为SIGTERM和SIGINT信号安装的信号处理函数是sig_exit,其代码如下(在文件lib/signal.c中)。
 
/* -------------------------------------------------------------------
 * sig_exit
 *
 * Quietly exits. This protects some against being called multiple
 * times. (TODO: should use a mutex to ensure (num++ == 0) is atomic.)
 * ------------------------------------------------------------------- */
void sig_exit( int inSigno ) {
    static int num = 0;
    if ( num++ == 0 ) {
        fflush( 0 );
        exit( 0 );
    }
} /* end sig_exit */
 
这里的num声明为静态变量,初始化为0,因为所有线程均使用同一个处理函数(同一份copy),因此当按下Ctrl-C时,至少有一个线程会 执行sig_exit函数,此时num值为0,程序调用exit退出。当任一线程调用exit函数时,整个进程(包括它的所有线程)退出。如果在程序退出 前有其他的线程也执行了sig_exit函数(在有多个线程收到同一信号的多个副本的情况下),num不为0,将不再调用exit函数。在这里对num是 否为0的检验是为了保证在有多个线程收到信号的情况下,exit函数仅被调用一次。
 
下面来看waitUntilQuit函数,该函数是在main函数中的服务器代码中被调用的,其代码如下(在src/main.cpp中)
 
/* -------------------------------------------------------------------
 * Blocks the thread until a quit thread signal is sent
 * ------------------------------------------------------------------- */
void waitUntilQuit( void ) {
#ifdef HAVE_THREAD
    // signal handlers send quit signal on ^C and kill
    gQuit_cond.Lock();
    my_signal( SIGTERM, sig_quit );
    my_signal( SIGINT,  sig_quit );
#ifdef HAVE_USLEEP
    // this sleep is a hack to get around an apparent bug? in IRIX
    // where pthread_cancel doesn't work unless the thread
    // starts up before the gQuit_cand.Wait() call below.
    // A better solution is to just use sigwait here, but
    // then I have to emulate that for Windows...
    usleep( 10 );
#endif
    // wait for quit signal
    gQuit_cond.Wait();
    gQuit_cond.Unlock();
#endif
} // end waitUntilQuit
 
这里的gQuit_cond是一个条件变量类的实例,条件变量类的定义和实现在lib/Condition.hpp中(所有的方法都实现为inline函数,提高效率)。它封装了POSIX线程间同步的条件变量机制。gQuit_cond的作用就是同步所有线程的结束。
 
在waitUntilQuit中,首先对gQuit_cond加锁,之后将SIGTERM和SIGINT的信号处理函数改为sig_quit, 之后调用条件变量类的Wait方法在gQuit_cond上等待,直到有线程调用gQuit_cond的Signal方法,之后 waitUntilQuit退出,主线程回到main函数。sig_quit的代码如下(在main.cpp中)
 
/* -------------------------------------------------------------------
 * Sends a quit thread signal to let the main thread quit nicely.
 * ------------------------------------------------------------------- */
void sig_quit( int inSigno ) {
#ifdef HAVE_THREAD
    // if we get a second signal after 1/10 second, exit
    // some implementations send the signal to all threads, so the 1/10 sec
    // allows us to ignore multiple receipts of the same signal
    static Timestamp* first = NULL;
    if ( first != NULL ) {
        Timestamp now;
        if ( now.subSec( *first ) > 0.1 ) {
            sig_exit( inSigno );
        }
    } else {
        first = new Timestamp();
    }
    // with threads, send a quit signal
    gQuit_cond.Signal();
#else
    // without threads, just exit quietly, same as sig_exit()
    sig_exit( inSigno );
#endif
} // end sig_quit
 
sig_quit完成的主要功能是:当某个线程收到第一个中断信号时,调用gQuit_cond的Signal方法唤醒在此条江变量上等待的主 线程(此时主线程正在waitUntilQuit函数中阻塞)。若在很短时间(0.1s)内有其他线程执行了sig_quit函数,是同一个信号的副本, 因此不进行任何操作;若相隔了一段时间,则说明用户在此按下了Ctrl-C键,要求程序强行退出,此时直接通过sig_exit函数调用exit函数退 出。
 
回到main函数,到主线程从waitUntilQuit后,如果发现此时线程组内还有正在执行的线程,则打印如下语句:
 
 Waiting for server threads to complete. Interrupt again to force quit.
 
如果此时用户在此按下Ctrl-C,则发生上面所述的第二种情况,程序立即结束运行。否则,主线程调用Thread类的Joinall方法(Joinall为Thread类的静态函数成员)等待所有线程的结束,在释放了内存后退出。
 
在客户端,main函数的工作比较简单。它生成Speaker类(说者监控线程)的实例,由后者负责生成其他的Client类(说者线程)事例向服务器端发送数据,之后main函数退出。但用户按下Ctrl-C时,线程组立即结束运行。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多