Posts p7重写linux命令-事件驱动编程
Post
Cancel

p7重写linux命令-事件驱动编程

1. 编写一个弹球游戏

1.1 cueses库简介

一些基本的使用:

1
2
3
4
5
6
7
8
9
int main(){
    initscr();
    clear();
    move(10,20);
    addstr("hello word");
    move(LINES - 1,0);
    getch();
    endwin();
}

demo2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
    int i ;
    initscr();  // 初始化
    clear() ;  // 清屏
    for( i = 0;i < LINES;++i){
        move(i,i+1);
        if (i %2 == 1)
            standout();  // 反色
        addstr("hello word");
        if (i % 2 == 1)
            standend();  // 恢复
    }
    refresh();  // 让屏幕按照你的意图显示  立即刷新缓存的意思
    getchar();  // 等待用户输入
    endwin();   // 恢复终端状态
}

1.2 时钟编程(sleep)

懒得写了 自己读代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
nt main()
{
    char	message[] = "Hello";
    char	blank[]   = "     ";
    int	dir = +1;
    int	pos = LEFTEDGE ;

    initscr();
    clear();
    while(1){
        move(ROW,pos);
        addstr( message );		/* draw string		*/
        move(LINES-1,COLS-1);		/* park the cursor	*/   // 将光标隐藏起来
        refresh();			/* show string		*/
        sleep(1);
        move(ROW,pos);			/* erase string		*/
        addstr( blank );
        pos += dir;			/* advance position	*/
        if ( pos >= RIGHTEDGE )		/* check for bounce	*/
            dir = -1;
        if ( pos <= LEFTEDGE )
            dir = +1;
    }
}

1.3 sleep() 的实现

注释,注释,注释,一定要看一下注释

1
2
3
4
5
6
7
8
9
10
11
12
void wakeup(int);
int main(){
    printf("about to sleep for 4 seconds\n");
    signal(SIGALRM,wakeup);  // 信号处理的三种模式 还记得吗
    alarm(4);                // 当时间到了以后,内核发送SIGALRM到该进程
    pause(); // 挂起,等待信号。 不仅仅是SIGALRM时钟信号,别的信号也可以唤醒,或者死亡
    printf(" has end");
}

void wakeup(int signum){
    printf(("Alarm received from kernel\n"));
}

1.3 时钟编程2 间隔计时器

usleep(): 将当前进程挂起n微妙,知道有一个不能被忽略的信号达到。

三种计时器: 1. 真实 2. 进程(又叫虚拟) (用户态时间) 3. 实用 (用户态加核心态)

p

  • ITIMER_REAL 真实时间,发送SIGALRM消息

  • ITIME_VIRTUAL 用户态时间,发送SIGVTALRM消息

  • ITIMER_PROF 用户态+和心态时间,发送SIGPROF消息

1.4 间隔时间器

本质: 初始时间 + 重复间隔

一个简单的例子: 医生让你一小时后吃药,每隔4小时吃一颗药,同上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int main(){
    void countdown(int);

    // SIGALRM 真实时间
    signal(SIGALRM,countdown);
    if (set_ticker(500) == -1 ){
        perror("set_ticker");
    } else{
        while (1){
            pause();
        }
    }
    return 0;
}

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;        /* set reload  */
    new_timeset.it_interval.tv_usec = n_usecs;      /* new ticker value */
    // 初始时间
    new_timeset.it_value.tv_sec     = n_sec  ;      /* store this   */
    new_timeset.it_value.tv_usec    = n_usecs ;     /* and this     */

    return setitimer(ITIMER_REAL, &new_timeset, NULL);

}

void countdown(int signum){
    static int num = 10;
    printf("%d ..",num --);
    fflush(stdout);
    if(num < 0){
        printf("DONE ! \n");
        exit(0);
    }
}

2. 信号处理

2.1 信号处理1 signal

  1. 早期信号处理的三种方式
  • 默认操作

  • 忽略信号

  • 调用函数

  1. 处理多个信号带来的问题
  • 捕鼠器问题

    捕捉信号后,信号处理函数就失效了。(针对调用函数)

  • 处理多个信号

    进程如何处理多个进程? 比如用户按下Ctrl - C 产生的一个SIGINT 信号, 或者Ctrl - \ 产生的SIGQUIT信号,再或者计时器产生的一个SIGQLRM信号 。 Unix 一个进程何如处理相应多个信号?

多信号处理demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define INPUTLEN 100
int main(){
    void inthandler(int);
    void quithandler(int);
    char input[INPUTLEN];
    int nchars;

    signal(SIGINT,inthandler);
    signal(SIGQUIT,quithandler);

    do{
        printf("\nType a massage\n");
        nchars = read(0,input,(INPUTLEN - 1));
        if (nchars == -1){
            perror(" read returned an error");
        } else{
            input[nchars] = '\0';
            printf("You typed: %s",input);
        }
    } while (strncmp(input,"quie",4) != 0);
    return 0;
}

void inthandler(int s){
    printf(" Received signal %d .. witing\n",s);
    sleep(2);;
    printf("leaving inthandler \n");
}

void quithandler(int s){
    printf(" Received signal %d .. witing\n",s);
    sleep(2);;
    printf("leaving inthandler \n");
}

实验结果:

  1. 不可靠信号(捕鼠器)

    两个SIGINT信号,并没有杀死进程。 处理函数在被调用后还是能起作用。不过要有一定的时间恢复,在处理函数处理时,同样的SIGINT信号并不能起到作用。

  2. 两个不同信号(接电话的时候有人敲门)

    两个不同的信号,SIGQUITSIGINT信号。 有一个执行顺序。先处理SIGQUIT信号,再处理SIGINT信号。

  3. 两个相同信号(两次敲门)

    同1,两个SIGINT信号,有3种处理方式。 本次实验是忽略第二个人敲门,直到第一个处理完毕。

  4. 程序处理中被信号打断

    程序正在处理,突发SIGINT信号,怎么处理? 首先处理中断信号,如果信号没有杀死进程,信号处理完成后,该程序继续处理。

该信号机制的弱点

  1. 没有告诉为什么会产生该信号,处理函数并不知道为什么会生成该信号。

  2. 处理函数不能安全的阻塞其他消息。 这个跟上面说的有点相悖论。

    两个不同的信号,SIGQUITSIGINT信号。 有一个执行顺序。先处理SIGQUIT信号,再处理SIGINT信号。

2.2 信号处理2 sigaction

这里只丢代码 详细的后章会有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define INPUTLEN 100
int main(){
    struct sigaction newhandler;
    sigset_t blocked;
    void inthandler();
    char x[INPUTLEN];

    newhandler.sa_handler = inthandler;     // 老模式
    newhandler.sa_flags = SA_RESETHAND;   // 函数调用时重置
    
    // 阻塞设置 这个在下章中详解
    sigemptyset(&blocked);
    sigaddset(&blocked,SIGQUIT);
    newhandler.sa_mask = blocked;

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

    return 0;
}

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

3. 防止数据损毁

一些情况下,一个操作不应该被其他的操作打断,在对一个数据结构的改动结束之前,其他函数不能够更改或者读写这个数据结构

3.1 临界区

临界区的代码具有原子性,当程序处理信号时,必须决定拿一段代码为临界区。保护临界区最简单的办法就是阻塞或者忽略那些处理函数将要修改临界区的特定信号

3.2 阻塞信号的两种方法

  1. 在信号处理一级阻塞信号

    设置struct sigaction 结构中的 sa_mask 位。

  2. 进程的阻塞信号

    任何时候一个进程都有一些信号被阻塞,通过sigprocmask可以修改被阻塞的信号集。 根据原子操作可以修改当前的被阻塞的信号集

3.3 信号挡板,详解

函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how: SIG_BLOCK,SIG_UNBLOCK,SIG_SET(替换)

看代码吧,代码比较好理解一点

1
2
3
4
5
6
7
8
9
int main() {
    sigset_t sigs, prevsigs;
    sigemptyset(&sigs);   // 设为空
    sigaddset(&sigs, SIGINT);    // ctr-v信号加入其中
    sigaddset(&sigs, SIGQUIT);   // ctrl-\信号加入其中
    sigprocmask(SIG_BLOCK, &sigs, &prevsigs);    //将信号列表的中的信号设为阻塞状态
    sigprocmask(SIG__SET, *prevsigs, NULL);  //  恢复为之前状态
}

3.4 进程间的通信 kill

` int kill (pid_t pid,int sig);`

  • 进程号
  • 信号量

3.5 异步I/O

4. 总结

  1. 进程通过设置计时器来安排事件,么个进程有3个独立的计时器。计时器通过发送信号来通知进程。每个进程可以被设置成只发送一个信号,或者按固定的间隙发送信号。

  2. 处理一个信号比较简单(忽略,恢复,处理函数),处理多个信号,由进程决定是忽略还是阻塞。
  3. 函数执行的一些复杂任务是不能够被打断的,需要设置临界区代码。
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags