用户输入Ctrl-C后发生的事件:
(1)用户输入Ctrcl-C
(2)驱动程序收到字符
(3)匹配VINTR和ISIG的字符被开启(VINTR(003, ETX, Ctrl-C, or also 0177, DEL, rubout)中断字符。发出 SIGINT 信号。当设置ISIG时可被识别,不再作为输入传递。ISIG:当接受到字符 INTR, QUIT, SUSP, 或 DSUSP时,产生相应的信号。)
(4)驱动程序调用信号系统
(5)信号系统发生SIGINT到进程
(6)进程收到SIGINT
(7)进程消亡
信号的来源:信号来自内核,生成信号的 请求来自三个地方;
(1)用户:用户能够通过输入^C,^\,或是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生的信号。
(2)内核:当进程执行出错时,内核给进程发生一个信号。
(3)进程:一个进程可以通过系统调用kill给另一个进程发送信号。一个进程可以和进程通过信号通信。由进程的某一个操作产生的信号称为同步信号。由像用户键这样的进程外事件引起的信号被称为异步信号。
当进程接收到信号时,不一定必须得按信号信息执行,进程可以通过系统调用signal告诉内核,他要如何处理信号。
(1)接受默认处理(按手册上每个信号的默认处理)进程也可以通过signal(SIGINT,SIG_DFL);来恢复默认处理。
(2)忽略信号:程序可以通过signal(SIGINF,SIG_IGN);
(3)调用一个函数:程序能够告诉内核,当信号来时应该调用哪个函数。当信号到来时被调用的函数称为信号处理函数,为安装信号处理函数,程序调用:signal(signum,function);
signal返回前一个处理函数,值是指向函数的指针。
其中SIGKILL、SIGSTOP是不可被忽略和捕获的。
alarm设置本进程的计时器到seconds秒后激发信号。当设定的时间过去之后,内核发送SIGALRM到这个进程,如果计时器已经被设置,alarm返回剩余秒数(调用alarm(0)意味着关掉闹钟)。
pause挂起调用进程直到一个信号到达。如果调用进程被这个信号终止,pause没有返回,如果调用进程用一个处理函数捕获。在控制从处理函数处返回后pause返回,这种情况下errno被设置为EINTR。
内核提供的三种计时器:
(1)ITIMER_REAL真实
这个计时器计量真实时间,如同手表记录时间。当这个计时器用尽,发送SIGALRM消息。
(2)ITIMER_VIRTUAL虚拟
这个计时器是只有进程在用户态运行时才计时,虚拟计时器的30S要比实际计时器 的30S要长,当虚拟计时器用尽,发送SIGVTALRM消息。
(3)ITIME_PROF实用
这个计时器在进程运行于用户态或由该进程调用而陷入核心态时计时,当这个计时器用尽,发送SIGPROF消息。
显示系统怎样处理信号的组合:
#include<stdio.h>
#include<signal.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#define INPUTLEN 100
int main(int ac,char * av)
{
void inthandler(int);
void quithandler(int);
char input[INPUTLEN];
int nchars;
signal(SIGINT,inthandler);
signal(SIGQUIT,quithandler);
do
{
printf("\nType a message\n");
nchars = (int)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,"quit",4) !=0 );
return 0;
}
void inthandler(int s)
{
printf("Received signal %d..waiting\n",s);
sleep(2);
printf("Leaving inthandler\n");
}
void quithandler(int s)
{
printf("Received signal %d.. waiting\n",s);
sleep(3);
printf("Leaving quithandler\n");
}
(1)不可靠的信号
如果两个SIGINTS信号杀死了进程,那么意味着你的系统是不可靠的信号,处理函数必须每次都重置。如果多个SIGINTS信号没有杀死进程,意味着处理函数再被调用后还起作用。现在信号处理机制允许你在两者之间做出选择。
(2)SIGY打断SIGX的处理函数
当按下^C和^\会看到程序先跳到inthandler,在跳到quithandler,处理完quithandler再回到inthandler,最后回到main里
(3)SIGX打断SIGX的处理函数
a.递归,调用同一个处理函数
b.忽略第二个信号,这和没有呼叫等待功能的电话机类似
c.阻塞第二个信号直到第一个处理完毕(我的电脑只能处理两个)
(4)被中断的系统调用
程序经常在等待输入的时候接收到信号,在上面的测试中,主循环阻塞以等待键盘的输入(read调用)。当输入中断的时(^C)或退出(^\)键,程序跳转到信号处理函数。当处理函数处理完成后,程序回到主循环,应该回到跳离的位置。(并不是一定的)根据系统不同而定。
信号机制的弱点:(早期)
1、不知道信号被发送的原因
信号处理函数是一个在信号到达的时候被调用的函数,内核传到处理函数一个信号数字编号,将信号编号作为参数传给处理函数使一个函数可以处理多个信号,因此可以使用一个信号处理函数处理多个信号,当信号传入处理函数时,可以根据信号的编号进行响应的处理。早期的模型只告诉处理函数它被调用是由什么类型的信号而引起的,但是没有告之为什么会发送信号。
2、处理函数中不能安全地阻塞其他消息
假设想让程序在响应SIGINT时忽略SIGQUIT。可以这样处理:
void inthandle(int s)
{
int rv;
void(*prev_quhandle)();
prev_quhandle = signal(SIGQUIT,SIG_IGN);
...
signal(SIGQUIT,prev_quhandle);
}
但是假如在第一次调用inthandle和signal的中间SIGQUIT信号传进来,就不能不能忽视SIGQUIT信号,再就是,这个函数并不是永久忽略SIGQUIT信号,当处理完SIGINT了,那么就会回来继续处理SIGOUT。
新的信号处理方式:
第一个参数signum指明想要处理的消息,第二个参数action指向描述如何响应信号的结构体,第三个参数prevaction如果不是NULL的话,就是指向描述被替换的处理的设置的结构体,如果新的操作设置成功则返回0,否则返回-1。
1、定制信号处理:struct sigaction
相对于原来的几种处理方式上增加了,结构体sigaction定义了如何处理一个信号,以下是这个结构体的完整定义:
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; //信号被处理时被阻塞
int sa_flags; //启动各种行为
void (*sa_restorer)(void);
};
a.sa_handler和sa_sigaction的选择
首先,早期的处理方式就够了,那么就用sa_handle。如果选择了sa_sigaction为一个处理函数,那么那个处理函数被调用的时候,不但可以得到信号编号而且可以获悉被调用的原因以及产生问题的上下文的相关信息,
//旧的处理机制: //新的处理机制:
struct sigaction action; action.sa_handler = handler_old;
struct sigaction action; action.sa_handler = handler_new;
如果要使用新的信号处理方式,只需要设置sa_flags的SA_SIGINFO位
b.sa_flags
sa_flags使用一些位来控制处理函数如何应对上述4个问题。
c.sa_mask
当决定处理一个信号时是否要阻塞其他信号,sa_mask中的位指定哪些信号要被阻塞。sa_mask的值包括要被阻塞的信号集,阻塞信号是防止数据损坏的重要技术。
相关函数:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset和_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
sigemptyset() 将由set设置为空的信号集合初始化,所有信号都不包含在集合中。
sigfillset()初始化设置为满,包括所有信号。
sigaddset() 和 sigdelset() 分别从set中添加和删除信号符号.
sigismember() 测试signum是否是set的成员。
临界区:
一段修改一个数据结构的代码如果在运行的时候被打断将导致数据的不完整或损毁则称这段代码为临界区。保护临界区的最简单的方法就是阻塞或忽略那些处理函数将要使用或修改特定的数据的信号。
a.阻塞信号:sigprocmask和sigsetops
可以在信号处理者一级或进程一级阻塞信号
I.在信号处理者一级阻塞信号
为了在处理一个信号的时候阻塞另一个信号,要设置struct sigaction 结构体中的sa_mask成员位,它在设置处理函数时被传递给sigaction。sa_mask是sigset_t类型,它定义了一个信号集。
II.一个进程的阻塞信号
在任何时候一个进程都有一些信号被阻塞(不是忽略)。这个信号集就称为信号挡板,通过sigprocmask可以修改这个被阻塞的信号集,sigprocmask作为一个原子操作根据所给的信号集来修改当前被阻塞的信号集。
sigprocmask修改当前的信号挡板设置,当how的值分别为SIG_BLOCK、SIG_UNBLOCK或SIG_SET时候,*sigs所指定的信号将被添加 、删除或替换。如果prev不是null,那么之前的信号挡板设置将被复制到*prev中
III.用sigsetop构造信号集
一个sigset_t是一个抽象的信号集,可以通过这些函数来增加或删除信号。
sigemptyset()sigfillset()sigaddset() sigdelset()
IV.暂时地阻塞用户信号
sigset_t sigs,prevsigs;
sigemptyset(&sigs);
sigaddset(&sigs,SIGINT);
sigaddset(&sigs,SIGQUIT);
sigprocmask(SIG_BLOCK,&sigs,&prevsigs);
...
sigprocmask(SIG_SET,*prevsigs,NULL);
暂时阻塞SIGINT和SIGQUIT信号,如果需要,在设置信号挡板的时候,使用prev提前保存之前的设置,在操作完成后还可以设置how为SIG_SET恢复之前的设置,
b.重入代码:递归调用的危险(处理信号)
一个信号处理者或者一个函数,如果在激活状态下能被调用而不引起任何问题就称为之可重入(当程序进行到信号处理函数时,还可以继续调用此函数)。可以通过设置SA_NODEFER位来允许处理函数的递归调用。反之可以通过清除此位来阻塞信号。如果处理者或者处理函数不可重入,必须阻塞信号,但是如果阻塞信号就可能丢失信号。
kill:从另一个进程处理信号
信号来自间隔计时器、终端驱动、内核或进程。一个进程可以通过kill系统调用向另一个进程发送信号:
kill向一个进程发送一个信号。发送信号的进程的用户ID必须和目标进程的用户ID相同,或者发送信号的进程的拥有者必须是超级用户,一个进程可以向自己发送信号,一个进程可以向其他进程发送任何信息,包括一般来自键盘,间隔计时器或者内核的信号
a.进程间通信的含义
接受信号的进程几乎可以设置任何信号的处理者。
输入信号:异步I/O
UNIX有两个异步输入系统。一种方法是当输入就绪时发送信号, 另一个系统当输入被读入时发送信号,UCB中通过设置文件描述块的O_ASYNC位来实现第一种方法,
第二种方法是POSIX标准,它调用aio_read。
1、使用O_ASYNC
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
如果使用fcntl()的F_SETFL命令在文件描述符上设置O_ASYNC状态标志,则只要在该文件描述符上输入或输出成为可能,就会发送一
个SIGIO(I/O有效信号)信号。可以使用F_SETSIG来获得SIGIO以外的信号的传送。如果这个权限检查失败,那么这个信号就会被丢弃。
2、使用aio_read
优点:更灵活 缺点:更复杂
#include <aio.h>
int aio_read(struct aiocb *aiocbp);
int aio_error(const struct aiocb *aiocbp);
ssize_t aio_return(struct aiocb *aiocbp);
函数将由aiocbp指向的缓冲区描述的I / O请求排队。这个功能是读取的异步模拟。
通过aio_error()检查错误信息。
aio_return()函数返回aiocbp所指向的控制块的异步I / O请求的最终返回状态。
调用read(fd,buf,count)的参数与aiocbp指向的结构的字段aio_fildes,aio_buf和aio_nbytes相对应。无论当前文件是否设置,数据都从绝对文件偏移aiocbp-> aio_offset开始读取。
调用之后,当前文件偏移量的值是未指定的。“异步”意味着这个调用一旦请求被排队就返回;当呼叫返回时,读取可能已经完成,也可能没有完成
struct aiocb
{
int aio_fildes; /*文件描述符*/
int aio_lio_opcode; /* 要执行的操作*/
int aio_reqprio; /* 请求优先抵消 */
volatile void *aio_buf; /* 缓冲区的位置。 */
size_t aio_nbytes; /* 发送的长度。*/
struct sigevent aio_sigevent; /*信号号码和值。 */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /*文件偏移量*/
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /*文件偏移量*/
#endif
char __glibc_reserved[32];
};
和调用一般的read不同,air_read不会阻塞进程,相反air_read会在完成时候发送信号,
信号的生命周期:
信号产生->信号注册->信号在进程中注销->信号处理函数执行完毕
信号的产生是触发信号的事件的发生,来源:程序错误,如非法访问内存外部信号,^C^|等通过kill或者sigqueue向另一个进程发送信号,
信号的处理:
处理:采用系统默认的SIG_DFL,并执行缺省操作捕捉信号处理,用户自定义的信号处理函数处理忽略信号SIG_IGN,但有两种信号不能被忽略SIGKILL,SIGSTOP屏蔽字