从API开始理解QNX -- 系统脉冲

系统脉冲 

前面提到过,除了用户自己定义的脉冲外,系统(内核)也会向用户进程发送脉冲,用来通报某些状态的发生。在这些系统脉冲里,比较常见的应该是这几个: 

_PULSE_CODE_UNBLOCK 
_PULSE_CODE_DISCONNECT 
_PULSE_CODE_THREADDEATH 

_PULSE_CODE_COIDDEATH


_PULSE_CODE_UNBLOCK 

这个脉冲平时不一定用到。不过,这个UNBLOCK的概念还是很重要的。我们知道,在消息传递中,为了保证可靠性,当客户端向服务器发送消息时,客户端是被“阻塞”(BLOCK)住的。这种状态,要一直到服务器应答了以后才会被开放。这就造成了一个问题,有时候,如果服务器还没有应答,而客户端又希望从阻塞中恢复,那又怎么办呢? 

最常见的,比如用户CTRL-C了客户端进程,按POSIX标准,一个SIGINT应该被发送到客户端进程,而客户端进程如果没有特别处理的话,应该被SIGINT终止。那么,如果这时,客户端正好在阻塞状态中,会怎么样呢? 

如果客户端进程,就这样突然凭空消失的话,服务器端会混乱,因为服务器端的程序,是建立在客户端被阻塞的情形下的。试想一下,如果客户端突然消失了,而服务器端又要MsgRead(),或是MsgWrite()会发生什么事呢? 

为了防止服务器端发生这种混乱,QNX规定如果一个进程,正REPLY BLOCK在别的进程上的时候,它不能自动脱离BLOCK状态;相反地,如果进程有什么理由要退出BLOCK状态,系统会先给服务器端发送一个 “UNBLOCK”脉冲,好象是说“REPLY BLOCK中的xx线程希望脱离阻塞状态”;而由服务器自己判断应该如何处理。服务器端可以在自己内部数据结构做出整理后,选择MsgError(rcvid, EINTR);以解除客户端的阻塞状态;也可以什么也不做,不理睬这个脉冲,这样客户端就不能退出BLOCK状态。有时候你会在QNX遇到Ctrl-c但进程却没有退出的情况,这就是因为进程正被阻塞在一个服务器上,而服务器没有正确处理 UNBLOCK PAULSE的结果。 


/*
 * Simple server
 */

#include
#include
#include
#include

int main()
{
    int chid, rcvid, status;
    struct _pulse pulse;
        
    /* Make sure the channel will handle unblock pulse */
    if ((chid = ChannelCreate(_NTO_CHF_UNBLOCK)) == -1) {
            perror("ChannelCreate");
            return -1;
    }
    printf("Server is ready, pid = %d, chid = %d\n", getpid(), chid);
    for (;;) {
            if ((rcvid = MsgReceive(chid, &pulse, sizeof(pulse), NULL)) == -1) {
                    perror("MsgReceive");
                    return -1;
            }

            if (rcvid > 0) {
                    printf("Server: Received a message, rcvid is %d, do not reply\n", rcvid);
                    continue;
            }
                
            printf("Server: Received a Pulse, pulse code is %d\n", pulse.code);
            if (pulse.code != _PULSE_CODE_UNBLOCK) {
                    continue;
            }

            printf("Server: client with rcvid %d want to unblock, let go\n",
                           pulse.value.sival_int);
                
            MsgError(pulse.value.sival_int, EINTR);
    }
    ChannelDestroy(chid);
    return 0;
}

最后,如果服务器端不希望接收UNBLOCK脉冲(非常不推荐),服务器在创建频道的时候,可以不设_NTO_CHF_UNBLOCK标志。在这种情况下,如果处于REPLY BLOCK状态下的客户端希望取消阻塞的话,内核将不再通知服务器,直接让客户端恢复为READY状态。


_PULSE_CODE_DISCONNECT 

在很多时候,服务器也希望知道与自己有连接的客户端结束了连接。这样,服务器端可以进行一些特定的处理。这就是 _PULSE_CODE_DISCONNECT的作用。当客户端 ConnectDetach()一个连接时,服务器端会收到一个脉冲。与UNBLOCK不一样的是,客户端不会在ConnectDetach()里阻塞,而是继续其运行。要注意的是,如果客户端多次 ConnectAttach() 到同一个服务器时,只有最后一个ConnectDetach()才会触发这个脉冲。 

与 UNBLOCK一样,服务器在创建频道的时候,必面设 _NTO_CHF_DISCONNECT这个标志位。否则,服务器不会收到这个脉冲。同时要注意的是,如果服务器设了这个脉冲,那么在收到脉冲后要做一个 ConnectDetach(pulse.value.sival_int);



这个系统脉冲与别的有点不同,它对任何进程都有用(不一定要在一个服务器、客户端环境中)。在一个多线程进程里,有时你希望知道一个线程退出了。POSIX的标准是 pthread_join(),但这个函数的问题是1)这个函数必须给出一个指定的线程号,如果你有多个线程,且不知道哪个先退出时会有一定困难。2)这个函数会阻塞直到被指定的线程退出。虽然有pthread_timedjoin()可以指定等待时间,但总的来说,与我们希望的总是不一样。 

_PULSE_CODE_THREADDEATH就是系统(内核)用来通知一个进程,它的某个线程退出了的脉冲。 

首先这是一个脉冲,所以希望收到这个脉冲的进程必须首先建立一个频道(对,即使你是一个“客户端”进程,通常只连接别人的频道,为了接收脉冲,也要自建频道)。但是一个进程可以建好多频道的,当线程退出内核需要发送脉冲时,内核应该发送给哪一个频道呢?所以,建立这个频道时,有个特殊的频道标志位,_NTO_CHF_THREAD_DEATH;这样就可以告诉内核,所有的“线程死亡”脉冲,都发到这个频道来! 

最后,内核当然不希望一个进程建好多好多_THREAD_DEATH频道,这样在线程死亡时,要发好多个脉冲。所以规定一个进程里只可以有一个 _NTO_CHF_THREAD_DEATH频道。 

具体的代码可以参考示例程序。最后想指出的是,虽然示例程序里的这个线程只是用来接收脉冲,但这个频道与普通的接受消息的频没有什么两样。如果有需要,你也可以直接在这同一频道上接收来自别的进程的消息。

$ cat thread_pulse.c
#include
#include
#include

// the thread will wait some second, and exit
void *do_something(void *arg)
{
    int sleeptime;
    
    sleeptime = rand() % 10;
    sleep(sleeptime);
    return 0;
}

int main(int argc, char **argv)
{
    int nthread = 10, i;
    int chid;
    struct _pulse pulse;
    
    if (argc > 1) {
        nthread = atoi(argv[1]);
    }

    srand(time(NULL));
    
    // create the channle for THREADDERATH pulse
    if ((chid = ChannelCreate(_NTO_CHF_THREAD_DEATH)) == -1) {
        perror("ChannelCreate");
        return -1;
    }
    
    // one process can only have one of these channel
    if (ChannelCreate(_NTO_CHF_THREAD_DEATH) != -1) {
        printf("Create another channel succesed?\n");
        return -1;
    }
    printf("Create Second Channel returns: %s\n", strerror(errno));
    
    printf("Create threads from 2 to %d ... \n", nthread + 1);
    for (i = 0; i < nthread; i++) {
        if ((errno = pthread_create(0, 0, do_something, 0)) != EOK) {
            perror("pthread_create");
            return -1;
        }
    }
    
    for (i = 0; i < nthread; i++) {
        if (MsgReceive(chid, &pulse, sizeof(pulse), 0) == -1) {
            perror("MsgReceive");
            return -1;
        }
        printf("Thread %d dead\n", pulse.value);
    }
    
    ChannelDestroy(chid); 
    return 0;
}

$ qcc -o thread_pulse thread_pulse.c
$ ./thread_pulse 5
Create Second Channel returns: Resource busy
Create threads from 2 to 6 ...
Thread 6 dead
Thread 2 dead
Thread 4 dead
Thread 5 dead
Thread 3 dead



这个系统脉冲恐怕是这四个当中最有趣的一个了。大家可能注意到了。QNX的消息传递很注意保护服务器端,客户端的阻塞,UNBLOCK脉冲,DISCONNECT脉冲等;但是,如果万一服务器端出了问题,客户端也需要知道,这样客户端才能进行适当的处理,进而自我修复系统。这个_PULSE_CODE_COIDDEATH脉冲就是派这个用处的。 

当一个客户端与一个服务器端建立起连接以后,如果服务器端突然退出了,内核会向客户端发送这个_PULSE_CODE_COIDDEATH脉冲,以通知客户端它所连接的服务器出了问题。当然,与上一个线程死亡的脉冲相似,客户端也必须准备好一个特殊的频道(在建立频道时设_NTO_CHF_COID_DISCONNECT标志位),同时这样的频道也在一个进程中也只能有一个。 

那么这个脉冲可以派什么用呢?上次有人在论坛问,如何监控一个会不时crash的io-net,其实靠的就是这个脉冲。那个帖子里还有监控程序源码,就是与io-net建一个连接(怎么建?io-net是TCPIP子系统,随便开个socket就行了),然后等这个脉冲。平时,监控程序阻赛在 MsgReceive()上,丝毫不会占CPU;如果io-net突然crash了,那么内核会向监控程序发送 _PULSE_CODE_COIDDEATH脉冲;也就是说,如果我们收到了这个脉冲,就是io-net已经crash了,然后只要想办法重新启动io- net了。(那个监控程序是执行一个外部脚本)。 

这个系统脉冲在QNET上也有效,所以,你甚至可以监控在另一个机器上的服务器。你也可以 open("/net/remote/dev/null". O_RDWR); 这样就同 "remote" 这台机器上的 procnto 建立了一个连接。当然,procnto是不会crash的,如果你收到了_PULSE_CODE_COIDDEATH,那就表示你同remote这台机器间的连接断了;比如网线拔掉了,或是中间的路由出了问题。当然,QNET会做一些重传,所以判断会有些延迟。 

QNX上有个叫“高可用性”(High available Manager)的框架,虽然提供了不少别的功能,其基础也是用到了这个脉冲。


你可能感兴趣的:(【QNX】)