事件驱动编程——《Unix/Linux编程实践教程》读书笔记(第7章)

1、curses库

/* 基本curses函数 */
initscr();            // 初始化curses库和tty
endwin();             // 关闭curses并重置tty
refresh();            // 使屏幕按照你的意图显示
move(r, c);           // 移动光标到屏幕的(r, c)位置
addstr(s);            // 在当前位置画字符串s
addch(c);             // 在当前位置画字符c
clear();              // 清屏
standout();           // 启动standout模式(一般使屏幕反色)
standend();           // 关闭standout模式
2、
man 3 sleep
#include <unistd.h>
unsigned int sleep(unsigned int n);
/*
 * sleep() makes the calling thread sleep until n seconds
 * have elapsed or a signal arrives which is not ignored.
 * 
 * Zero if the requested time has elapsed, or the number
 * of seconds left to sleep, if the call was interrupted
 * by a signal handler.
 */
sleep()的工作原理

系统中的每个进程都有一个私有的闹钟(alarm clock)。这个闹钟很像一个计时器,可以设置在一定秒数后闹铃。时间一到,时钟就发送一个信号SIGALRM到进程。除非进程为SIGALRM设置了处理函数(handler),否则信号将杀死这个进程。sleep函数由3个步骤组成:

/* sleep()的工作原理 */
signal(SIGALRM, handler);
alarm(num_seconds);
pause();

一下是对alarm()和pause()的描述。

man 2 alarm
#include <unistd.h>
unsigned int alarm(unsigned int n_seconds);
/*
 * alarm() arranges for a SIGALRM signal to be delivered to
 * calling process in n seconds.
 * If n_seconds is zero, any pending alarm is canceled.
 * In any envet any previously set alarm() is canceled.
 *
 * alarm() returns the number of seconds remaining until
 * any previously scheduled alarm was due to be delivered,
 * or zero if there was no previously scheduled alarm.
 */
man 2 pause
#include <unistd.h>
int pause(void);
/*
 * pause() causes the calling process (or thread) to sleep until
 * a signal is delivered that either terminates the process or
 * causes the invocation of a signal-catching function.
 *
 * pause() returns only when a signal was caught and the
 * signal-catching function returned. In this case pause()
 * returns -1, and errno is set to EINTR.
 */
精度更高的时延:
#include <unistd.h>
int usleep(useconds_t usec);
3、三种计时器:真实、进程和实用

(1)ITIMER_REAL:这个计时器计量真实时间。当这个计时器用尽,发送SIGALRM消息。

(2)ITIMER_VIRTUAL:虚拟计时器(virtual timer)只有进程在用户态运行时才计时。当虚拟计时器用尽,发送SIGVTALRM消息。

(3)ITIMER_PROF:这个计时器在进程运行于用户态或由该进程调用而陷入核心态时计时。当这个计时器用尽,发送SIGPROF消息。

4、set_ticker.c

/* 
 * set_ticker.c
 * set_ticker(number_of_milliseconds)
 * arranges for interval timer to issue SIGALRMs at regular intervals
 * return 01 on error, 0 for ok
 * arg in milliseconds, converted into whole seconds and microseconds
 * note: set_ticker(0) turns off ticker
 */

#include <stdio.h>
#include <sys/time.h>

int set_ticker(int n_msecs)
{
    struct itimerval new_timeset;
    long n_sec, n_usecs;

    n_sec = n_msecs / 1000;
    n_usecs = (n_msecs % 1000) * 1000L;

    new_timeset.it_interval.tv_sec = n_sec;
    new_timeset.it_interval.tv_usec = n_usecs;

    new_timeset.it_value.tv_sec = n_sec;
    new_timeset.it_value.tv_usec = n_usecs;

    return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
结构体itimerval、timeval的描述:
struct itimerval
{
    struct timeval it_valuse;      /* time to next timer expiration */
    struct timeval it_interval;    /* reload it_value with this */
}

struct timeval
{
    time_t      tv_sec;        /* seconds */
    suseconds   tv_usec;       /* and microseconds */
}
函数getitimer、setitimer的描述:
man 2 getitimer
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
              struct itimerval *old_value);
5、早期的信号处理机制

各种事件促使内核向进程发送信号。这些事件包括用户的击键、进程的非法操作和计时器到时。一个进程调用signal在以下3种处理信号的方法之中选择:

(1)默认操作(一般是终止进程),比如,signal(SIGALRM, SIG_DFL)

(2)忽略信号,比如,signal(SIGALRM, SIG_IGN)

(3)调用一个函数,比如,signal(SIGALRM, handler)

6、POSIX中替代signal()的函数sigaction()

(1)

man 2 sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);

/* 其中 */
struct sigaction {
    void (*sa_handler)(int);  /* SIG_DFL, SIG_IGN, or function */
    void (*sa_sigaction)(int, siginfo_t *, void *);  /* new handler */
    sigset_t sa_mask;  /* signals to block while handling */
    int sa_flags;  /* enable various behaviors */
    void (*sa_restorer)(void);/* POSIX does not specify this element */
};

(2)sa_flags是用一些位来控制处理函数的。下面是部分位标记的含义,更多的细节可以在手册上查到。

SA_RESETHAND:当处理函数被调用时重置;

SA_NODEFER:在处理信号时关闭信号自动阻塞。这样就允许递归调用信号处理函数;

SA_RESTART:当系统调用是针对一些慢速的设备或类似的系统调用,重新开始,而不是返回。这样是采用BSD模式;

SA_SIGINFO:指明使用sa_sigaction的处理函数的值。如果这个位没有被设置,那么就使用sa_handler指向的处理函数的值。如果sa_sigaction被使用,传给处理函数将不只是信号编号,还包括指向描述信号产生的原因和条件的结构

(3)sa_mask中的位指定哪些信号要被阻塞。sa_mask的值包括要被阻塞的信号集。阻塞信号是防止数据损毁的重要技术。

sigaction()的概述比较麻烦,用一个例程来了解一下吧:
/*
 * sigactdemo.c
 * purpose: shows use of sigaction()
 * feature: blocks ^\ while handling ^C
 *          does not reset ^C handler, so two kill
 */

#include <stdio.h>
#include <signal.h>

#define INPUTLEN (100)

int main(void)
{
    struct sigaction    newhandler;         /* new settings */
    sigset_t            blocked;            /* set of blocked sigs */
    void                inthandler(int);
    char                x[INPUTLEN];

    /* load these two members first */
    newhandler.sa_handler = inthandler;     /* handler function */
    newhandler.sa_flags = SA_RESETHAND | SA_RESTART;
                                            /* options */

    /* then build the list of blocked signals */
    sigemptyset(&blocked);                  /* clear all bits */
    sigaddset(&blocked, SIGQUIT);           /* add SIGQUIT to list */
    newhandler.sa_mask = blocked;           /* store blockmask */

    if (sigaction(SIGINT, &newhandler, NULL) == -1)
        perror("sigaction");
    else
        while (1)
        {
            fgets(x, INPUTLEN, stdin);
            printf("input: %s\n", x);
        }
    return 0;
}

void inthandler(int s)
{
    printf("Called with signal %d\n", s);
    sleep(s);
    printf("done handling signal %d\n", s);
}

7、防止数据损毁(Data Corruption)

一段修改一个数据结构的代码如果在运行时被打断导致数据的不完整或损毁,则称这段代码为临界区。临界区不一定就在信号处理函数中,很多出现在常规的程序流中。保护临界区的最简单的办法是阻塞或忽略那些处理函数将要使用或修改特定数据的信号。

a、在信号处理函数一级阻塞信号:设置struct sigaction结构中的sa_mask成员位,它在设置处理函数时被传递给sigaction;

b、在进程一级的阻塞信号:sigprocmask()作为原子操作根据所给的信号集来修改当前被阻塞的信号集

man 2 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
当how的值分别为SIG_BLOCK、SIG_UNBLOCK或SIG_SET时,set所指定的信号将被添加、删除或替换。如果oldset不是null,那么之前的信号掩模设置将被复制到oldset中。

可以用sigsetops构造信号集,一个sigset_t是一个抽象的信号集,可以通过一些函数来添加或删除信号。基本的函数如下:

sigemptyset(sigset_t *setp);            // 清除由setp指向的列表中的所有信号
sigfillset(sigset_t *setp);             // 添加所有的信号到setp指向的列表
sigaddset(sigset_t *setp, int signum);  // 添加signum到setp指向的列表
sigdelset(sigset_t *setp, int signum);  // 从setp指向的列表中删除signum
c、例子:暂时地阻塞用户信号
sigset_t sigs, presvigs;
sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGQUIT);
sigprocmask(SIG_BLOCK, &sigs, &prevsigs);
// ...modify data structure here.
sigprocmask(SIG_SET, &prevsigs, NULL);
8、kill:从另一个进程发送的信号

一个进程可以通过kill系统调用向另一个进程发送信号

man 2 kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t dest_pid, int sig);

9、输入信号:一部I/O

Unix有两个一部输入(asynchronous input)。一种方法是当输入就绪时发送信号,另一种是系统当输入就被读入时发生信号。UCB中通过设置文件描述块(file descriptor)的O_ASYNC位来实现第一种方法。第二种方法是POSIX标准,它调用aio_read

a、使用O_ASYNC:首先建立和设置在键盘输入时被调用的处理函数。其次,使用fcntl的F_SETOEN命令告诉内核发送输入通知信号给进程。其他进程可能也连接到键盘。这里不想让这些进程发送信号。第三,通过调用fcntl来设置文件描述符0中的O_ASYNC位来打开输入信号。最后,循环使用pause等待来自计时器或键盘的信号。当有一个从键盘的字符到达,内核向进程发送SIGIO信号。

b、使用aio_read:首先,设置输入被读入时所调用的处理函数on_input。其次,设置struct kbcbuf中的变量来指明等待什么类型的输入,当输入发生时产生什么信号,例如,可以设置从文件描述符0中读入1个字符,当字符被读入时希望收到SIGIO信号。实际上能指定任何信号,甚至是SIGALRM或SIGINT。第三,通过将以上定义的结构体传给aio_read来递交读入请求。和调用一般的read不同,aio_read不会阻塞进程,想法aio_read会在完成时发送信号。

以下是aio_read的例子:

/*
 * bounce_aio.c
 * purpose: animation with user control, using aio_read() etc
 * note: set_ticker() sends SIGALRM, handler does animation
 *       keyboard sends SIGIO, main only calls pause()
 * compile: cc bounce_aio.c set_ticker.c -lrt -lcurses -o bounce_aio
 */

#include <stdio.h>
#include <curses.h>
#include <signal.h>
#include <aio.h>
#include <string.h>
#include "set_ticker.h"

/* The state of the game */
#define MESSAGE     "hello"
#define BLANK       "     "

int     row     = 10;
int     col     = 0;
int     dir     = 1;
int     delay   = 200;
int     done    = 0;

struct aiocb kbcbuf;        /* an aio control buf */

int main(void)
{
    void    on_alarm(int);
    void    on_input(int);
    void    setup_aio_buffer(void);

    initscr();
    crmode();
    noecho();
    clear();

    signal(SIGIO, on_input);
    setup_aio_buffer();
    aio_read(&kbcbuf);      /* place a read request */

    signal(SIGALRM, on_alarm);
    set_ticker(delay);

    mvaddstr(row, col, MESSAGE);

    while (!done)
        pause();
    endwin();

    return 0;
}

/*
 * handler called when aio_read() has stuff to read
 * first check for any error codes, and if ok, then get the return code
 */
void on_input(int signum)
{
    int     c;
    char    *cp;
    cp = (char *)kbcbuf.aio_buf;        /* cast to char * */

    /* check for errors */
    if (aio_error(&kbcbuf) != 0)
        perror("reading failed");
    else if (aio_return(&kbcbuf) == 1)
    {
        c = *cp;
        if (c == 'Q' || c == EOF)
            done = 1;
        else if (c == ' ')
            dir = -dir;
    }

    /* place a new request */
    aio_read(&kbcbuf);
}

void on_alarm(int signum)
{
    signal(SIGALRM, on_alarm);
    mvaddstr(row, col, BLANK);
    col += dir;
    mvaddstr(row, col, MESSAGE);
    refresh();

    /* now handle borders */
    if (dir == -1 && col <= 0)
        dir = 1;
    else if (dir == 1 && col+strlen(MESSAGE) >= COLS)
        dir = -1;
}

void setup_aio_buffer(void)
{
    static char input[1];

    /* discribe what to read */
    kbcbuf.aio_fildes   = 0;        /* standard input */
    kbcbuf.aio_buf      = input;    /* buffer */
    kbcbuf.aio_nbytes   = 1;        /* number to read */
    kbcbuf.aio_offset   = 0;        /* offset in file */

    /* describe what to do when read is ready */
    kbcbuf.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
    kbcbuf.aio_sigevent.sigev_signo  = SIGIO;           /* send sIGIO */
}



转载于:https://my.oschina.net/happynewyeah/blog/296761

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值