事件驅動編程——《Unix/Linux編程實踐教程》讀書筆記(第7章)

一、curses庫 shell

/* 基本curses函數 */
initscr();            // 初始化curses庫和tty
endwin();             // 關閉curses並重置tty
refresh();            // 使屏幕按照你的意圖顯示
move(r, c);           // 移動光標到屏幕的(r, c)位置
addstr(s);            // 在當前位置畫字符串s
addch(c);             // 在當前位置畫字符c
clear();              // 清屏
standout();           // 啓動standout模式(通常使屏幕反色)
standend();           // 關閉standout模式
二、
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()的描述。 async

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);
三、三種計時器:真實、進程和實用

(1)ITIMER_REAL:這個計時器計量真實時間。當這個計時器用盡,發送SIGALRM消息。 函數

(2)ITIMER_VIRTUAL:虛擬計時器(virtual timer)只有進程在用戶態運行時才計時。當虛擬計時器用盡,發送SIGVTALRM消息。 ui

(3)ITIMER_PROF:這個計時器在進程運行於用戶態或由該進程調用而陷入核心態時計時。當這個計時器用盡,發送SIGPROF消息。 this

四、set_ticker.c rest

/* 
 * 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);
五、早期的信號處理機制

各類事件促使內核向進程發送信號。這些事件包括用戶的擊鍵、進程的非法操做和計時器到時。一個進程調用signal在如下3種處理信號的方法之中選擇: code

(1)默認操做(通常是終止進程),好比,signal(SIGALRM, SIG_DFL) 遞歸

(2)忽略信號,好比,signal(SIGALRM, SIG_IGN) 進程

(3)調用一個函數,好比,signal(SIGALRM, handler)

六、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);
}

七、防止數據損毀(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);
八、kill:從另外一個進程發送的信號

一個進程能夠經過kill系統調用向另外一個進程發送信號

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

九、輸入信號:一部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 */
}
相關文章
相關標籤/搜索