资源描述
信号与系统调用的关系:
当一个进程正在执行一个系统调用时,如果向该进程发送一个信号,那么对于大多数
系统调用来说,这个信号在系统调用完成之前将不起作用,因为这些系统调用不能被信号
打断。但是有少数几个系统调用能被信号打断,例如: wait(),pause()以及对慢速设备 (终端、
打印机等)的 read()、 write()、 open()等。如果一个系统调用被打断,它就返回-1,并将 errno
设为 EINTR。可以用下列代码来处理这种情况:
if (wirte(tfd,buf,SIZE)<0) {
if (errno==EINTR) {
warn(“ Write interrupted.” );
…
…
}
}
信号的复位:
在 Linux 中,当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信
号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用
相应的处理函数。下面的程序演示了这一点:
#include <stdio.h>
#include <signal.h>
int catch(int sig); //信号绑定函数,必须在main函数前声明。
int main(void)
{
printf("the program is starting.\n");
signal(SIGINT, catch);//接受到SIGINT信号,执行绑定的catch函数
//signal(SIGINT, SIG_DFL);//SIG_DFL 恢复成系统的缺省动作
//signal(SIGINT, SIG_IGN);//告诉进程将 SIGINT 信号忽略,CTRL+C
sleep(10);
printf("the end.\n");
return 0;
}
int catch(int sig)
{
printf("Interrupt called.\n");
sleep(4);
printf(“Interrupt Func end.\n”);
}
执行它,结果如下:
the program is starting.
<ctrl+c>
Interrupt called
<ctrl+c>
Interrupt Func end.
Interrupt called
Interrupt Func end.
the end
只能保存一个CTRL+C。
如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中
断:the program is starting.
<ctrl+c>
Interrupt called
<ctrl+\>
Quit called
Quit ended.
Interrupt Func end。
the ended.
在进程间发送信号:
个进程通过对 signal()的调用来处理其它进程发送来的信号。一个进程也可以向其它的进程发送信号。这一操作是由系统调用 kill()来完成的。 kill()在 linux 系统库 signal.h中的函数声明:int kill(pid_t pid, int sig);
参数 pid 指定了信号发送的对象进程:它可以是某个进程的进程标识符(pid),也可以是以下的值:
如果 pid 为零,则信号被发送到当前进程所在的进程组的所有进程;
如果 pid 为-1 ,则信号按进程标识符从高到低的顺序发送给全部的进程(这个过程受
到当前进程本身权限的限制);
如果 pid 小于-1,则信号被发送给标识符为 pid 绝对值的进程组里的所有进程。
需要说明的是,一个进程并不是向任何进程均能发送信号的,这里有一个限制,就是
普通用户的进程只能向具有与其相同的用户标识符的进程发送信号。也就是说,一个用户
的进程不能向另一个用户的进程发送信号。只有 root 用户的进程能够给任何线程发送信号。
参数 sig 指定发送的信号类型。它可以是任何有效的信号。
由于调用 kill()的进程需要直到信号发往的进程的标识符,所以这种信号的发送通常只
在关系密切的进程之间进行,比如父子进程之间。
下面是一个使用 kill()调用发送信号的例子。这个程序建立两个进程,并通过向对方发
送信号 SIGUSR1 来实现它们之间的同步。这两个进程都处于一个死循环中,在接收对方发
送的信号之前,都处于暂停等待中。这是通过系统调用 pause()来实现的,它能够使一个程
序暂停,直至一个信号到达,然后进程输出信息,并用 kill 发送一个信号给对方。当用户
按了中断键,这两个进程都将终止。
#include <signal.h>
int ntimes=0;
main()
{
int pid,ppid;
int p_action(), c_action();
/* 设定父进程的 SIGUSR1 */
signal(SIGUSR1,p_action);
switch(pid=fork()) {
case -1: /*fork 失败*/
perror("synchro");
exit(1);
case 0: /*子进程模块*/
/* 设定子进程的 SIGUSR1 */
signal(SIGUSR1,c_action);
/* 获得父进程的标识符*/
ppid=getppid();
for(;;) {
sleep(1);
kill(ppid,SIGUSR1);
pause();
}
- 58- Linux 网络编程
/*死循环*/
break;
default: /*父进程模块*/
for (;;) {
pause();
sleep(1);
kill(pid,SIGUSR1);
}
/*死循环*/
}
}
p_action()
{
printf("Patent caught signal #%d\n",++ntimes);
}
c_action()
{
printf("Child caught signal #%d\n",++ntimes);
}
程序运行结果如下:
Patent caught signal #1
Child caught signal #1
Patent caught signal #2
Child caught signal #2
Patent caught signal #3
Child caught signal #3
Patent caught signal #4
Child caught signal #4
<ctrl+c>
kill 命令用于向一个运行进程发送信号,它发送的信号默认为 SIGTERM,但是也可以指定为其它信号。我们可以直接用信号的号码来指定 kill 命令所发送信号之类型,也可以用符号名指定。比如可以用下面的命令来完成向进程标识符为 1234 的进程发送 SIGINT 信号:
kill – s SIGINT 1234
系统调用 alarm() 和 pause():
alarm()它可以建立一个进程的报警时钟,在时钟定时器到时的时候,用信号向程序报告。alarm()系统调用在 Linux 系统函数库 unistd.h 中的函数声明如下:
unsigned int alarm(unsigned int seconds);
函数唯一的参数是 seconds ,其以秒为单位给出了定时器的时间。当时间到达的时候,
就向系统发送一个 SIGARLM 信号。例如:
alarm(60);
这一调用实现在 60 秒后发一个 SIGALRM 信号。alarm 不会象 sleep 那样暂停调用进
程的执行,它能立即返回,并使进程继续执行,直至指定的延迟时间到达发出 SIGALRM
信号。事实上,一个由 alarm()调用设置好的报警时钟,在通过 exec() 调用后,仍将继续有
效。但是,它在 fork() 调用后中,在子进程中失效。
如果要使设置的报警时钟失效,只需要调用参数为零的 alarm():
alarm(0)
alarm()调用也不能积累。如果调用 alarm 两次,则第二次调用就取代第一次调用。但
是,后一个alarm会把前一个alarm的剩余时间作为返回值,然后自己重新从0开始计数,前一个alarm就此成为历史。
当需要对某项工作设置时间限制时,可以使用 alarm()调用来实现。其基本方法为:先
调用 alarm()按时间限制值设置报警时钟,然后进程作某一工作。如果进程在规定时间以内
完成这一工作,就再调用 alarm(0)使报警时钟失效。如果在规定时间内未能完成这一工作,
进程就会被报警时钟的 SIGALRM 信号中断,然后对它进行校正。
下面这个程序使用上述方法来强制用户作出回答。在其中包括一个 quickreply()函数,
它有一个参数 prompt,它是一个指向提示字符串的指针。quickreply 的返回值也是一个指
针。它指向含有输入行信息的字符串。这个例行程序在试作五次之后,如果仍未得到输入
信息,就返回一个 null 指针。每当 quickreply 要提醒用户时,它就向终端发送 ASCII 码 007,这会使终端响铃。quickreply 调用了标准 I/O 库中的例行程序 gets()。gets()把标准输入上的下一行信息存入一个字符型数组,它返回一个指向该数组的指针。当到达文件末或出错时,gets 则返回一个 null 指针。函数 catch 是信号 SIGALRM 的关联函数,它完成对此信号的处理。catch设置了一个 timed_out 标志,在 quickreply 中对这个标志进行检查,看它是否超过了规定的时限。
#include <stdio.h>
#include <signal.h>
#define TIMEOUT 5
#define MAXTRIES 5
#define LINESIZE 100
#define BELL '\007'
#define TRUE 1
#define FALSE 0
/* 判断超时是否已经发生的标志*/
static int time_out;
static char inputline[LINESIZE];
char* quickreply (char* prompt);
- 60- Linux 网络编程
main()
{
printf("%s\n",quickreply("Input"));
}
char* quickreply (char* prompt)
{
int (*was)(),catch(),ntries;
char* answer;
/* 设定捕捉 SIGALRM 的的关联并保存原有关联*/
was=signal(SIGALRM,catch);
for (ntries=0;ntries<MAXTRIES;ntries++)
{
time_out=FALSE;
printf("\n%s>",prompt);
/* 设定定时器*/
alarm(TIMEOUT);
/* 获取输入*/
answer=gets(inputline);
/* 关闭定时器*/
alarm(0);
if (!time_out)
break;
}
/* 恢复原有的 SIGALRM 关联*/
signal(SIGALRM,was);
return (time_out?((char*) 0):answer);
}
/* SIGALRM 信号处理函数*/
catch()
{
/* 设定超时标志*/
time_out=TRUE;
/* 响铃警告*/
putchar(BELL);
}
2.系统调用 pause()
系统调用 pause()能使调用进程暂停执行,直至接收到某种信号为止。pause()在 Linux
系统函数库 unistd.h 中的函数声明如下:
int pause(void);
该调用没有任何的参数。它的返回始终是 -1 ,此时 errno 被设置为ERESTARTNOHAND 。
下面这个程序为了在规定时间显示一个消息,使用了 alarm 和 pause。对它的调用方法
如下:
$tml minutes message-text &
第一个参数为时间数,第二个参数为显示的消息。
#include <stdio.h>
#include <signal.h>
#define TRUE 1
#define FALSE 0
#define BELLS "\007\007\007"
int alarm_flag=FALSE;
/* SIGALRM 处理函数*/
setflag()
{
alarm_flag=TRUE;
}
main(int argc,char* argv[])
{
int nsecs;
int i;
if (argc<2)
{
fprintf(stderr,"Usage:tml #minutes message\n");
exit(1);
}
if ((nsecs=atoi(argv[1])*60)<=0)
{
fprintf(stderr,"Invalid time\n");
exit(2);
}
/* 设定 SIGALRM 的关联动作*/
signal(SIGALRM,setflag);
- 62- Linux 网络编程
/* 设定定时器*/
alarm(nsecs);
/*使用 pause()调用等待信号*/
pause();
if (alarm_flag)
{
printf(BELLS);
for (i=2;i<argc;i++)
{
printf("%s\n",argv[i]);
}
}
exit(0);
}
展开阅读全文