Linux信号机制

信号分类 

不可靠信号 VS. 可靠信号

Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:

1.进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要 在信号处理函数结尾再一次调用signal(),重新安装该信号。

2.早期UNIX下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。 

Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后, 不必重新调用该信号的安装函数 (信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。

可靠信号

随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种UNIX版本分别在这方面进行了研究,力图实现"可靠信号"。由 于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号 的发送和安装也出现了新版本: 信号发送函数sigqueue()及信号安装函数sigaction()。

sigaction和signal函数都是调用内核服务do_signal函数;[内核服务函数,应用系统无法调用的函数]

实时信号(可靠信号)和非实时信号(不可靠信号)

早期UNIX系统只定义了32种信号,Linux 3.x支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进 一步增加,这需要得到内核的支持。 前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,对 该信号的默认反应就是进程终止。后32个信号表示 实时信号 ,等同于可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

信号发送

kill

int kill(pid_t pid, int signo);

kill既可以向自身发送信号,也可以向其他进程发送信号

signo参数组合情况解释:

pid>0 将信号sig发给pid进程

pid=0 将信号sig发给 同组进程

pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)

pid<-1 将信号sig发送给 进程组 是 pid (绝对值)的每一个进程

实验:

void onSignalAction(int signalNumber)
{
  switch(signalNumber)
  {
  case SIGUSR1:
    cout << "SIGUSR1 = " << signalNumber << endl;
    break;
  default:
    cout << "Other Signal ..." << endl;
    break;
  }
}

int main()
{
  if (signal(SIGUSR1,onSignalAction)== SIG_ERR)
  {
    perror("signal error");
    return -1;
  }

  pid_t pid = fork();
  if (pid == -1)
  {
    perror("fork error");
    return -1;
  }
  else if (pid == 0)
  {
    /**向父进程发送信号
    pid_t ppid = getppid();
    kill(ppid,SIGUSR1);
    */

    /**向同组所有进程发送信号,子进程也会受到该信号
    kill(0,SIGUSR1);
    */

    //向本组所有进程发送信号,作用同上
  //getpgrp()函数获取进程组pid
    pid_t pgid = getpgrp();
    killpg(pgid,SIGUSR1);
    exit(0);
  }

  int sleepTime = 3;
  while (sleepTime > 0)
  {
    write(STDOUT_FILENO,"Parent start Sleep...\n",
        sizeof("Parent start Sleep...\n"));
    sleepTime = sleep(sleepTime);
    write(STDOUT_FILENO,"Parent return from Sleep...\n",
        sizeof("Parent return from Sleep...\n"));
  }

  return 0;
}

注意: 如果在fork之前安装信号,则子进程可以继承信号 。

Sleep遇上signal,子进程向父进程发送信号,sleep函数的几点说明

1)sleep函数作用,让进程睡眠。

2) 能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码

3)sleep函数的返回值,是剩余的秒数

Man手册显示:

RETURN VALUE

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遇上signal
void onSignalAction(int signalNumber)
{
  switch(signalNumber)
  {
  case SIGINT:
    cout << "SIGINT = " << signalNumber << endl;
    break;
  default:
    cout << "Other Signal ..." << endl;
    break;
  }
}

int main()
{
  if (signal(SIGINT,onSignalAction)== SIG_ERR)
  {
    perror("signal error");
    return -1;
  }
  cout << "Main Start Sleeping..." << endl;
  int returnValue = sleep(100); //可中断睡眠
  cout << "Main End Sleeping... returnValue = " << returnValue << endl;

  return 0;
}
//示例:sleep加强
int main()
{
  //...同上
  cout << "Main Start Sleeping..." << endl;
  //sleep加强版^^
  int sleepTime = 20;
  do
  {
    sleepTime = sleep(sleepTime);
  cout << "continue..." << endl;
  }
  while (sleepTime > 0);
  cout << "Main End Sleeping... sleepTime = " << sleepTime << endl;

  return 0;
}

raise

int raise(int sig);

给自己发送信号。raise(sig)等价于kill(getpid(), sig);

killpg

int killpg(int pgrp, int sig);

给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);

sigqueue

int sigqueue(pid_t pid, int sig, const union sigval value);

给进程发送信号,支持排队,可以附带信息。

pause

int pause(void);

将进程置为可中断睡眠状态

。然后它调用内核函数schedule(),使Linux进程调度器找到另一个进程来运行。

pause使调用者进程挂起,直到一个信号被捕获

Alarm

unsigned int alarm(unsigned int seconds);

alarm函数,设置一个闹钟延迟发送SIGALRM信号(告诉Linux内核n秒中以后,发送SIGALRM信号);

手册描述:

DESCRIPTION

alarm() arranges for a SIGALRM signal to be delivered to the process in seconds seconds.

If seconds is zero, no new alarm() is scheduled.

In any event any previously set alarm() is cancelled.

//alarm 递归调用
void onSignalAction(int signalNumber)
{
  switch(signalNumber)
  {
  case SIGALRM:
    cout << "SIGALRM = " << signalNumber << endl;
    alarm(1);	//继续调用onSignalAction
    break;
  default:
    cout << "Other Signal ..." << endl;
    break;
  }
}

int main()
{
  if (signal(SIGALRM,onSignalAction)== SIG_ERR)
  {
    perror("signal error");
    return -1;
  }

  alarm(1);

  while(true)
  {
    pause();
    cout << "pause returned..." << endl;
  }

  return 0;
}

可重入 / 不可重入函数

可重入函数概念

为了增强程序的稳定性,在信号处理函数中 应使用可重入函数 。 

所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果 信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可 预料的后果。 不可再入函数在信号处理函数中被视为不安全函数。

满足下列条件的函数多数是 不可重入 的:

(1)使用静态数据结构,如 getlogin() , gmtime() , getgrgid() , getgrnam() , getpwuid() 以及 getpwnam() 等等;

(2)函数实现时,调用了malloc () 或者free()函数;

(3)实现时使用了标准 I/O 函数

//实验
//在X64系统可能运行正常,但是在32位系统很有可能有输出错乱的情况
void onSignalAction(int signalNumber)
{
  switch(signalNumber)
  {
  case SIGALRM:
    printGlobalTeacher();
    alarm(1);
    break;
  default:
    cout << "Other Signal ..." << endl;
    break;
  }
}

int main()
{
  if (signal(SIGALRM,onSignalAction)== SIG_ERR)
  {
    perror("signal error");
    return -1;
  }

  Teacher t1,t2;
  t1.age = 11;
  t1.number = 11;
  t2.age = 22;
  t2.number = 22;

  alarm(1);

  while(true)
  {
    g_t = t1;
    g_t = t2;
  }

  return 0;
}

附 -man 手册使用

man 7 signal 查找可重入函数和不可重入函数

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。