《Unix-Linux编程实践教程》读书笔记(七)

时间:2021-03-31 04:41:39

1.      游戏同操作系统的相似之处:游戏涉及屏幕管理、时间、信号、同时做几件事情,这些情景在操作系统中十分常见。

2.      屏幕编程:curses库

         1)  hello1.c、hello2.c程序中curses库函数的使用

         2)  curses同时维护着两份屏幕拷贝,一份真实屏幕拷贝,一份工作拷贝。Curses函数操作工作拷贝,调用refresh函数仅仅将工作拷贝的更改同步到真实屏幕拷贝,使用增量的方式,减少数据量(视频的帧也使用的相同的思想)

3.      时钟编程:sleep

         Hello3.c、hello4.c使用sleep函数实现移动效果。

1)   时钟编程1:alarm

a)        Sleep的实现:使用alarm

b)        alarm的使用:设置SIGALRM处理函数à调用alarmà调用pause

c)        alarm和pause函数的语法细节。Pause挂起进程知道一个信号到来,如果该进程被信号终止,pause不返回。否则,运行完信号处理函数后pause返回,且errno置为EINTR

d)        计时器是的在未来某个时间点触发某个动作的同时,使得主进程能够腾出手来进行其他操作,如计算PI。

2)   时钟编程2:间隔计时器

a)        每个进程有三个时钟:真实时钟(现实运行时间)、虚拟时钟(用户模式下运行时间)、实用时钟(用户模式和内核模式运行时间总和,真实时钟还包括睡眠时间)

b)        三个时钟的名字和功能(记录时间的方式和上抛消息的不同

c)        两种时间间隔:“过1小时吃一粒药丸,以后每隔4小时吃一粒”中包含的初始时间和重复间隔

d)        Ticker_demo.c中对间隔计时器的使用,其中设置定时器时比alarm稍微复杂,需要设置两种时间间隔

e)        getitimer和setitimer系统调用详细说明,两个接口都可以指定使用进程三个时钟中的任意一个

3)   系统中只有一个时钟:硬件时钟的脉冲(晶振),系统将多少个脉冲信号对应成现实世界的时间单位,维护软件进程中的各个时钟

4.      时钟小结:两种时钟实现方法(alarm和setitimer)都支持在特定时间长度后向进程发送相应的信号,其中setitimer更加精确和灵活

5.      信号处理1:使用signal

         1)  在第六章play_again.c中处理用户组合按键ctrl+c时介绍了早起的原始信号处理机制,信号到来时有三种处理方式:默认操作、忽略信号、绑定自定义消息处理函数

         2)  在采取默认操作、忽略信号的处理方式时,原始信号处理机制能够胜任多信号的情境,但是若要使用绑定的自定义函数对信号进行处理,原始信号处理机制会出现捕鼠器问题,可能会在逮住一直老鼠(信号)到重新安置好捕鼠器(重新绑定信号处理函数)的过程中放掉其他老鼠(未能捕获其他的信号)

         3)  现实生活和进程所面临的多信号环境相似,电话、火警、敲门等恰似进程所面对的多个信号。不同的系统对这种多信号环境的处理方式各异,sigdemo3.c给出了一些情形

                   a)      不可靠的捕鼠器

                   b)      不同的信号在前一个信号的处理过程中到达

                   c)      相同的信号在前一个该信号的处理过程中到达

         4)  原始信号机制的其他弱点:不知道信号被发送的原因,处理函数执行过程中不能安全的阻塞其他信号

6.      信号处理2:sigaction

         1)  处理多个信号:sigaction的使用方法

                   a)      定制信号处理:设置sigaction函数使用的参数structsigaction(其中保持了对原始signal的兼容)

                   b)      使用sigaction:sigactdemo.c(如何设置structsigaction中的flag成员以定制是否阻塞后续信号等)

7.      信号小结:一个进程可能被各种来源的信号中断。信号可能下任何时候以任何顺序到达。Signal提供了一种简单但不完整的信号处理机制。Sigaction提供复杂的、明确的方法来控制进程如何对各种信号组合做出反应。

8.      防止数据损毁

         1)数据损毁的例子:各种信号中断造成写数据动作的原子性遭到破坏,从而破坏数据正确性,造成数据损毁等

         2)  临界区:临界区不是只存储数据的一盘磁盘区域而是指程序中的一段代码区域在执行这一段程序代码的过程中如果被打断将会造成数据的不完整或损毁。简而言之,临界区是一段代码。保护临界区的方法:阻塞或者忽略那些其处理函数会修改临界区代码在操作的数据的 信号。因为中断当前代码去立即响应这些信号 会像“插一腿”一样的 破坏临界区代码正在操作的数据的正确性。

         3)  阻塞信号:sigprocmask和sigsetops

                  信号的阻塞可以在处理函数中或者在进程中进行。以下说明在信号处理函数中进行信号的阻塞。

a)        在struct sigaction结构中设置sa_mask成员来进行信号的阻塞,sa_mask是一个信号集,被称为信号挡板

b)        Sigprocmask函数作为一个原子操作根据传入的信号集来设置进程当前要阻塞的信号集

c)        Sigsetops函数用来构造信号集

4)   为当前信号处理而设置了信号挡板,在处理的最后需要将之恢复成之前的信号挡板

9.      重入代码:一个信号处理函数或者一个函数,在其激活状态下(当前正在执行该函数)能再次被调用(终端之前正在进行的那次执行)而不引起任何问题就被称之为可重入。

10.   kill:从另一个进程发送的信号

         1)  信号来源:用户、计时器、内核、进程。

2)  一个进程向另一个进程发送信号的系统调用kill。使用时注意:发送信号的进程的用户ID必须和接收信号的进程的用户ID相同,kill可以被用来向某个进程自身发送信号。Kill发送的信号包括来自键盘、计时器、内核的信号。

         3)  信号可以像球一样下进程之间传递,信号的到达触发某个函数的执行

         4)  IPC信号设计:SIGUSR1、SIGUSR2(这两个信号系统没有预定义语义,可供编程人员使用)

11.   使用计时器和信号:视频游戏

         1)  bounce1d.c:在一条直线上控制动画

                   a)      程序设计的重点:状态变量和事件处理。记录位置、方向、延时的变量定义了动画的状态。用户输入和计时器信号时改变这些状态的事件。

                   b)      每次计时器到达发出信号时,调用改变位置重画动画的代码;每当用户输入时就改变方向和速度变量等。

                   c)      bounce1d.c使用的是signal函数,新信号到来时是递归调用信号处理函数还是阻塞依赖于自己的系统。

         2)  bounce2d.c:二维动画

                   a)      将bounce1d.c中的字符改变成了由字母“o”来模仿的小球,其程序设计基本与前者相同,不同之处在于球的运动不再是在一条直线上,而是需要增加变量来表示在水平和垂直两个方向的运行状态。(此处的处理时借用一个时钟来模拟两个时钟的效果,因为程序支持分别在两个方向上独立的进行速度的加减)

                   b)      剩下的为小球工作的区域添加围墙和受用户方向键控制的小挡板的工作自己完成。

12.   输入信号:异步I/O

         之前的学习中已将看到,信号可以使得主监控程序腾出手来进行其他的任务,绑定好信号处理函数之后,信号的处理将变得方便。同样的在bounce1d.c和bounce2d.c两个程序中对于时钟信号的处理都采用了上述信号中断处理的异步方式,而对于用户输入则使用getch函数阻塞在哪里直到用户真正进行了输入操作。思考一下,我们也可以将用户的输入事件按信号中断处理的方式来进行,进一步解放主监控程序。

1)  unix中有两个异步输入系统。一种是在输入就绪的时候发生信号,另一个是在系统当输入被读入的时候发送信号。

2)  使用异步I/O

对于用户输入使用信号:SIGIO,对该信号编写信号处理函数,相应用户的输入操作。

a)        方法1:使用O_ASYNC

-->建立和设置在键盘上输入时被调用的信号处理函数;

-->使用fcntl的F_SETOWN命令来告诉内核发送输入通知信号给进程,此时其他进程也可能链接到键盘,但是不向这些进程发送信号;

-->使用fcntl来设置文件描述符0中的O_ASYNV位来打开输入信号;

-->在主监控程序中循环调用pause来等待来及计时器或者键盘的信号。

b)        方法2:使用aio_read

相比O_ASYNC来说,aio_read更加灵活和复杂:

-->建立和设置在键盘上输入时被调用的信号处理函数;

-->设置struct  kbcbuf中的变量赖志明等待什么类型的输入,当输入发生时产生什么信号;

-->通过将上述结构体传给aio_read来递交读入请求,aio_read函数本身也不会阻塞进程,其会在完成时发送信号

-->在主监控程序中循环调用pause来等待来及计时器或者键盘的信号。

3)  弹球程序中需要异步输入码?不,等待用户输入阻塞主监控程序,用信号中断来处理定时重画动画,已经能胜任需要。

4)  异步输入、视频游戏和操作系统:操作系统中对异步输入的使用十分常见,因为内核要运行程序而不能把时间浪费在等待用户输入上。同样对于用户而言,程序最好不要使用同步设计,这样在完成一个任务的时候用户只能等待,而异步的设计使得用户可以先进行其他操作而仅仅等待异步信号(或消息)的到来。

13.   小结

         1)有些程序流程很简单,而另外一些需要响应外部事件。一个视频游戏要响应时钟和用户的输入,操作系统也要响应时钟和外设

         2)  curses库的基本使用

         3)  一个进程可以通过设置计时器来安排事件,每个进程有三个计时器

         4)  处理一个信号较简单,有三种方法。对于多个信号就比较复杂,原始信号处理机制的不足引出了对它的改进

         5)临界区及其保护方法

         6)  可重入代码

----------------------------------------------------------------------------------------

本文链接http://blog.csdn.net/yongchurui/article/details/26508431

2014.05.21