Linux信号

信号的基本概念:

信号是操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生,当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断,如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
注:在Linux系统中,可通过kill -l 查看系统定义的信号列表。34号以上的信号为实时信号,在此不做讨论,在此只讨论34号以下信号。在man 7 signal中详细解释了每个信号。

信号的产生方式:

前台进程与后台进程的切换:
在Linux终端运行程序时,可以在程序的后面加上&,就可以将程序放在后台运行,如下:
test.c:

#include<stdio.h>

int main(){
  while(1){
    ;   
  }
  return 0;
}

直接在后台运行:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test&
[1] 5071
[DELL@MiWiFi-R1CL-srv lesson12]$ jobs -l
[1]+  5071 运行中               ./test &
[DELL@MiWiFi-R1CL-srv lesson12]$
jobs -l:用于查看所有运行的程序。 

还可以通过键盘按Ctrl+z给前台运行的进程发送SIGTSTP信号,使前台进程停止,再通过bg %[number]命令把这个程序放到后台运行。如下:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test 
^Z
[1]+  已停止               ./test
[DELL@MiWiFi-R1CL-srv lesson12]$ bg %1
[1]+ ./test &
[DELL@MiWiFi-R1CL-srv lesson12]$ jobs -l
[1]+  5366 运行中               ./test &
[DELL@MiWiFi-R1CL-srv lesson12]$ 

将后台进程调到前台执行可通过fg %[number]命令,如下:

[DELL@MiWiFi-R1CL-srv lesson12]$ jobs -l
[1]+  5366 运行中               ./test &
[DELL@MiWiFi-R1CL-srv lesson12]$ fg %1
./test

也可以通过kill %[number]直接终止后台进程,如下:

[DELL@MiWiFi-R1CL-srv lesson12]$ jobs -l
[1]+  5549 运行中               ./test &
[DELL@MiWiFi-R1CL-srv lesson12]$ kill %1
[1]+  已终止               ./test
[DELL@MiWiFi-R1CL-srv lesson12]$ 
信号的产生:

通过键盘产生:
用户在终端按下某些按键时,终端驱动程序会发送相应信号给前台进程。例如:
Ctrl+c产生SIGINT信号,缺省动作是结束进程。
Ctrl+z产生SIGTSTP信号,缺省动作是停止进程。
Ctrl+\产生SIGQUIT信号,缺省动作是结束进程,并且核心转储(产生Core Dump文件)
注:此种信号只能发送给前台进程。

Core Dump:
当一个进程要异常终止时,可以选择把进程的用户地址空间的内存数据全部保存到磁盘上,文件名通常是core.xxx,此过程叫做Core Dump。事后可以通过调试器检查core.xxx文件以确定错误原因,这叫做事后调试(Post-mortem Debug)。
一个进程允许产生多大的core文件取决于进程的Resource Limit(资源限制,在PCB中),默认是不允许产生core文件的,因为core文件中可能包含用户密码等信息,造成安全问题。在开发调试阶段可以通过ulimit命令改变此限制,使之可以产生core文件。

ulimit [选项] [大小]
  • 说明:ulimit命令用于设置由shell启动进程所占用的资源。
  • 选项:
    • -a:显示当前所有的资源限制。
    • -c:设置core文件的最大值,单位为blocks。
  • 大小:设置项目的大小。

示例:

/* [DELL@MiWiFi-R1CL-srv lesson12]$ ulimit -a */
/* core file size          (blocks, -c) 0 */
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 14418
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
/* [DELL@MiWiFi-R1CL-srv lesson12]$ ulimit -c 1024 */
/* [DELL@MiWiFi-R1CL-srv lesson12]$ ulimit -a */
/* core file size          (blocks, -c) 1024 */
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 14418
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[DELL@MiWiFi-R1CL-srv lesson12]$ 

注:当终端重新打开时,上一次设置便会失效,想要永久生效便要设置相应的配置文件。

使用gdb查看core文件:

无限循环的test.c程序:

[DELL@MiWiFi-R1CL-srv lesson12]$ cat test.c
#include<stdio.h>

int main(){
  while(1){
    ;
  }
  return 0;
}
[DELL@MiWiFi-R1CL-srv lesson12]$ 

利用Ctrl+\产生SIGQUIT信号,结束进程并产生core文件。

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test 
^\退出(吐核)
[DELL@MiWiFi-R1CL-srv lesson12]$ ll
总用量 164
-rw-------. 1 DELL DELL 245760 53 19:40 core.9885
-rwxrwxr-x. 1 DELL DELL   8464 53 16:08 test
-rw-rw-r--. 1 DELL DELL     68 53 19:36 test.c
[DELL@MiWiFi-R1CL-srv lesson12]$ 

注:core文件后缀的数字为当前进程的pid。

使用gdb查看core文件:

[DELL@MiWiFi-R1CL-srv lesson12]$ ll
总用量 164
/* -rw-------. 1 DELL DELL 245760 5月   3 19:40 core.9885 */
-rwxrwxr-x. 1 DELL DELL   8464 53 16:08 test
-rw-rw-r--. 1 DELL DELL     68 5月   3 19:36 test.c
[DELL@MiWiFi-R1CL-srv lesson12]$ gdb
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7_4.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
/* (gdb) core-file core.9885 */
[New LWP 9885]
Missing separate debuginfo for the main executable file
Try: yum --enablerepo='*debug*' install /usr/lib/debug/.build-id/1b/72b45fac48a0c75d2e386cb56992dd746ffb9a
/* Core was generated by `./test'. */ /* 由./test生成 */
/* Program terminated with signal 3, Quit. */ /* 程序由3号信号终止 */
#0  0x00000000004004f1 in ?? ()
(gdb) 

硬件异常产生:
由硬件监测到异常发生并通知内核,然后由内核向当前进程发送相应的信号。例如:
1、当前的进程执行了除以0指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程,缺省动作为结束进程,并核心转储。
2、当进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程,缺省动作是结束进程,并核心转储。

调用系统调用函数向进程发送信号 :
一个进程可以通过调用系统调用函数kill()向另外一个进程发送信号,也可以通过终端命令kill发送信号给某个进程,kill命令也是通过调用kill()函数实现的,若不指定信号,则发送SIGTERM信号,默认动作是结束进程。

kill()系统调用:

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
  • 说明:此函数用于向另外一个进程发送指定的信号。
  • 参数:
    • pid:接收信号进程的标识符。
    • sig:所要发送的信号。
  • 返回值:成功返回0,失败返回-1,并设置error。

代码:

send.c:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>

int main(int argc,char **argv){
  if(argc != 3){ 
    printf("./send pid signalnumber\n");
    return -1; 
  }
  pid_t pid = atoi(argv[1]);
  int signal = atoi(argv[2]);
  int ret = kill(pid,signal);
  if(ret == -1){
    perror("kill");
    return -2; 
  }
  return 0;
}

receive.c:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main(){
  pid_t id = getpid();
  while(1){
    usleep(10);
    printf("pid:%d\n",id);
  }
  return 0;
}

结果:

SB

kill命令:

test.c:

#include<stdio.h>

int main(){
  while(1){
    ;   
  }
  return 0;
}

结果:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test &
[1] 16915
[DELL@MiWiFi-R1CL-srv lesson12]$ kill -SIGSEGV 16915
[DELL@MiWiFi-R1CL-srv lesson12]$ ll
总用量 188
-rw-------. 1 DELL DELL 245760 5月   3 22:36 core.16915
-rwxrwxr-x. 1 DELL DELL   8624 53 20:53 receive
-rw-rw-r--. 1 DELL DELL    168 5月   3 20:37 receive.c
-rwxrwxr-x. 1 DELL DELL   8664 53 21:06 send
-rw-rw-r--. 1 DELL DELL    366 5月   3 21:06 send.c
-rwxrwxr-x. 1 DELL DELL   8464 53 22:29 test
-rw-rw-r--. 1 DELL DELL     67 5月   3 22:29 test.c
[1]+  段错误               (吐核)./test
[DELL@MiWiFi-R1CL-srv lesson12]$ gdb
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7_4.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
(gdb) core-file core.16915
[New LWP 16915]
Missing separate debuginfo for the main executable file
Try: yum --enablerepo='*debug*' install /usr/lib/debug/.build-id/1b/72b45fac48a0c75d2e386cb56992dd746ffb9a
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004004f1 in ?? ()
(gdb) 

raise()函数:

#include <signal.h>

int raise(int sig);
  • 说明:此函数用于向调用进程发送信号。
  • 参数:
    • sig:所发送的信号。
  • 返回值:成功返回0,失败返回非0。

示例:
test.c:

#include<stdio.h>
#include<signal.h>

int main(){
  int ret = raise(11);
  if(ret != 0){ 
    printf("failure\n");
    return -1; 
  }
  printf("success\n");
  return 0;
}

结果:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test 
段错误(吐核)
[DELL@MiWiFi-R1CL-srv lesson12]$ ll
总用量 192
/* -rw-------. 1 DELL DELL 245760 5月   4 17:13 core.6508 */
-rwxrwxr-x. 1 DELL DELL   8624 53 20:53 receive
-rw-rw-r--. 1 DELL DELL    168 5月   3 20:37 receive.c
-rwxrwxr-x. 1 DELL DELL   8664 53 21:06 send
-rw-rw-r--. 1 DELL DELL    366 5月   3 21:06 send.c
-rwxrwxr-x. 1 DELL DELL   8568 54 17:01 test
-rw-rw-r--. 1 DELL DELL    171 5月   4 17:01 test.c
[DELL@MiWiFi-R1CL-srv lesson12]$ gdb
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7_4.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
/* (gdb) core-file core.6508 */
[New LWP 6508]
Missing separate debuginfo for the main executable file
Try: yum --enablerepo='*debug*' install /usr/lib/debug/.build-id/c9/ec773d9eaa4bac396e976fb0de382753a1d05a
Core was generated by `./test'.
/* Program terminated with signal 11, Segmentation fault. */
#0  0x00007fc87d9031f7 in ?? ()
(gdb) 

abort()函数:

#include <stdlib.h>

void abort(void);
  • 说明:此函数使调用进程接收SIGABRT信号,而异常终止。

示例:
test.c:

#include<stdio.h>
#include<stdlib.h>

int main(){
  printf("pid:%d\n",getpid());
  abort();
  return 0;
}

结果:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test 
pid:7135
已放弃(吐核)
[DELL@MiWiFi-R1CL-srv lesson12]$ ll
总用量 196
/* -rw-------. 1 DELL DELL 249856 5月   4 17:25 core.7135 */
-rwxrwxr-x. 1 DELL DELL   8624 53 20:53 receive
-rw-rw-r--. 1 DELL DELL    168 5月   3 20:37 receive.c
-rwxrwxr-x. 1 DELL DELL   8664 53 21:06 send
-rw-rw-r--. 1 DELL DELL    366 5月   3 21:06 send.c
-rwxrwxr-x. 1 DELL DELL   8616 54 17:18 test
-rw-rw-r--. 1 DELL DELL    310 5月   4 17:18 test.c
[DELL@MiWiFi-R1CL-srv lesson12]$ gdb
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7_4.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
/* (gdb) core-file core.7135 */
[New LWP 7135]
Missing separate debuginfo for the main executable file
Try: yum --enablerepo='*debug*' install /usr/lib/debug/.build-id/df/89641cf10e63a75b7c1e449c5db37e980a1754
Core was generated by `./test'.
/* Program terminated with signal 6, Aborted. */
#0  0x00007fac0cf601f7 in ?? ()
(gdb) 

软件条件产生:
当向读端关闭的管道写数据时产生SIGPIPE信号,默认动作为结束进程。
通过系统调用unsigned int alarm(unsigned int seconds)函数设置一个闹钟,告诉内核在seconds秒后给当前进程发送SIGALRM信号,默认动作为结束进程。

alarm()函数:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • 说明:此函数用于设置一个闹钟,告诉内核在seconds秒后给调用进程发送SIGALRM信号,默认动作为结束进程。
  • 参数:
    • seconds:设置闹钟的时间,单位为秒,若为0,表示取消以前设置的闹钟。
  • 返回值:若前面设置过闹钟,则返回离闹钟到达的时间。否则,返回0。

示例:

#include<stdio.h>
#include<unistd.h>


int main(){
  unsigned int ret = alarm(100);
  printf("The first alarm clock return:%u\n",ret);
  usleep(1000*1000*10);
  ret = alarm(50);
  printf("The second alarm clock return:%u\n",ret);
  return 0;
}

结果:

[DELL@MiWiFi-R1CL-srv lesson12]$ ./test 
The first alarm clock return:0
The second alarm clock return:90
[DELL@MiWiFi-R1CL-srv lesson12]$ 

信号处理方式:

了解信号的处理方式之前,我们先来了解几个关于信号的其他概念。
信号递达(Delivery):实际执行的信号处理动作 。
信号未决(Pending):信号从产生到递达之间的状态。

信号在内核中的表示模型:
这里写图片描述
每个信号都有两个标志位分别表示阻塞(block)和未决(pending)状态,这两个标志位共同决定信号处于三个阶段中的那个阶段(未产生、未决、正递达),当标志位为1时,表示有效,即block表相应位为1表示阻塞该信号,pending表相应位为1表示信号处于产生但未递达的未决状态;标志位为0时,表示无效,即block表相应位为0表示不阻塞该信号,pending表相应位为0表示该信号未处于未决状态。注意:信号产生时内核会置位pending表,信号递达时会自动清除未决标志,还有一个函数指针表示处理动作,依据取值的不同对信号的处理方式有以下几种情况:
1、当函数指针取值为常数SIG_IGN时,表示忽略该信号。
2、当函数指针的取值为常数SIG_DFL时,表示执行系统默认的处理动作。
3、当函数指针取值为函数地址时,表示用自定义的信号处理函数捕捉该信号,此行为称为信号捕捉。
注:阻塞信号和忽略信号截然不同,信号阻塞后便不会达到递达状态,而忽略信号是信号递达之后的一种处理方式。此外,进程对某信号进行阻塞,在解除阻塞之前若该信号产生多次,在Linux系统中,对于常规信号只按产生一次计算,而实时信号会将产生的多次依次放在i一个队列中。
在Linux内核中,每个信号只需block表和pending表中的一个bit位来表示其状态,所以内核以相同的类似于位图的数据结构sigset_t类型变量来存储block表和pending表。sigset_t称为信号集,阻塞信号集也叫信号屏蔽字。

下面介绍关于操作信号集的函数:

#include <signal.h>

int sigemptyset(sigset_t *set);
//此函数初始化set指向的信号集,使其中所有信号对应的bit位置为0.
int sigfillset(sigset_t *set);
//此函数初始化set指向的信号集,使其中所有信号对应的bit位置为1.

//在使用下面两个函数改变信号集中相应信号对应的bit位前,应调用上面两个函数初始化信号集,使其处于确定的状态。
int sigaddset(sigset_t *set, int signum);
//在set指向的信号集中添加signum对应的信号。
int sigdelset(sigset_t *set, int signum);
//在set指向的信号集中删除signum对应的信号。
int sigismember(const sigset_t *set, int signum);
//此函数用于判断set指向的信号集上,signum对应的信号位是否为1,若为1,函数返回1,若为0,函数返回0,函数出错返回-1.

上面四个函数均是调用成功返回0,出错返回-1.

sigprocmask()函数:

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 说明:此函数用于读取或更改当前进程的信号屏蔽字。
  • 参数:
    • how:依据此参数和非空的set指针指向的阻塞信号集决定如何处理当前进程的信号屏蔽字,取值有如下三种可能:
      SIG_BLOCK:将set指向的阻塞信号集添加进进程的信号屏蔽字,即:block |= *set;
      SIG_UNBLOCK:将set指向的阻塞信号集从进程的信号屏蔽字中去除,即:block &= ~(*set);
      SIG_SETMASK:用set指向的阻塞信号集替换当前进程的信号屏蔽字。
    • set:若不未空,指向阻塞信号集,此阻塞信号集用于修改进程的信号屏蔽字。
    • oldset:输出型参数,若不为空,用于保存当前进程调用此函数前的信号屏蔽字。
  • 返回值:成功返回0,失败返回-1.
    注:当调用setprocmask()函数对多个未决的信号解除屏蔽,在函数返回前至少有一个信号递达。

sigpending()函数:

#include <signal.h>

int sigpending(sigset_t *set);
  • 说明:此函数通过输出型参数set,返回进程的未决信号集。
  • 返回值:成功返回0,失败返回-1.

示例:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

//打印未决信号集
void print(sigset_t *pending){
  int i = 1;
  for(;i <= 34;i++){
    if(sigismember(pending,i)){
      putchar('1');
    }else{
      putchar('0');
    }   
  }
  putchar('\n');
}

int main(){
  sigset_t block,pending;
  //初始化信号集
  sigemptyset(&block);
  //将SIGINT信号加到block信号集中。
  sigaddset(&block,SIGINT);
  //阻塞SIGINT信号(Ctrl+c)
  sigprocmask(SIG_BLOCK,&block,NULL);
  while(1){
    //获取未决信号集。
    sigpending(&pending);
    //打印未决信号集
    print(&pending);
    sleep(1);
  }
  return 0;
}

结果:

[DELL@localhost lesson12]$ ./signal 
0000000000000000000000000000000000
0000000000000000000000000000000000
^C0100000000000000000000000000000000//2号信号被阻塞,按下Ctrl+c不退出,处于未决状态
0100000000000000000000000000000000
0100000000000000000000000000000000
0100000000000000000000000000000000
^\退出(吐核)//Ctrl+\任然可以退出
[DELL@localhost lesson12]$ 

捕捉信号:
这里写图片描述
sigaction()函数:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
  • 说明:此函数用来查询和设置信号处理方式,不同于signal()函数的是,此函数设置的信号处理方式在进程运行期间均有效,而signal()函数设置的处理方式只生效一次。
  • 参数:
    • isgnum:所要查询或设置的信号。
    • act:指向struct sigaction{}结构体,act指向的结构体用于设置信号处理方式,此结构体的定义如下:
struct sigaction {
void     (*sa_handler)(int);//函数指针,指向信号处理函数。
void     (*sa_sigaction)(int, siginfo_t *, void *);//函数指针,指向信号处理函数,一般用于实时信号。
sigset_t   sa_mask;//用于设置在处理该信号的时候,将sa_mask指定的信号集搁置
int        sa_flags;//下面说明
void     (*sa_restorer)(void);//此参数没有使用。
};
sa_flags的作用:此参数可能有四种取值,分别说明如下。
SA_SIGINFO:若为此值,则使用第二个函数指针所指向的信号处理函数,此时可以向处理函数发送附加信息(一般用于实时信号),若不为此值,则使用第一个函数指针所指向的信号处理函数,此时只能向处理函数发送信号的数值。
SA_RESETHAND:当调用信号处理函数时,将信号处理函数重置为默认值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统重起该系统调用。
SA_NODEFER:一般情况下,当信号处理函数运行时,内核会阻塞该信号,若设置了此值,则在信号处理函数运行时不会阻塞该信号。
    • oldact:此参数用于保存旧的信号处理方式。
  • 返回值:成功返回0,失败返回-1.

忽略信号:
将sigaction()函数的第二个参数指向结构的sa_handler字段置为SIG_IGN,则信号的处理方式设置为忽略。
默认处理信号:
将sigaction()函数的第二个参数指向结构的sa_handler字段置为SIG_DFL或将第四个字段置为SA_RESETHAND,则信号的处理动作为默认的动作。

pause()函数:

#include <unistd.h>

int pause(void);
  • 说明:此函数用于将调用进程挂起直到有信号到达。
  • 返回值:若到达的信号处理动作是终止进程,则进程终止pause函数没有返回值,若处理动作为忽略,则进程继续挂起pause函数不返回,若处理动作为捕捉,则调用信号处理函数后,pause函数返回-1,并设置errno为EINTR

sleep()函数的模拟实现mysleep()函数:
1、mysleep()函数调用sigaction()函数注册SIGALRM信号的信号处理函数sigalarn()。
2、调用alarm()函数设置闹钟。
3、调用pause()函数挂起进程等待信号的到来。
4、SIGALRM递达,执行信号的处理函数。
5、pause()函数返回-1.mysleep()函数返回。

代码:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

//信号处理函数,虽然信号处理函数什么都没干,
//但必须将此SIGALRM信号捕捉,因为pause()函数
//只有在等到的信号的处理方式为捕捉时才返回。
void sigalarm(int signum){
 ;
}

//成功调用返回0,被信号中断返回剩余秒数
unsigned int mysleep(unsigned int seconds){
  struct sigaction act,oldact;
  unsigned int restsleep;
  act.sa_flags = 0;
  act.sa_handler = sigalarm;
  sigemptyset(&act.sa_mask);
  //注册SIGALRM信号的处理函数
  sigaction(SIGALRM,&act,&oldact);
  //设置闹钟
  alarm(seconds);
  pause();
  //取消闹钟,pause()若被除SIGALRM外的信号打断
  //则restsleep为剩余闹钟时间,若被SIGALRM信号打断则为0
  restsleep = alarm(0);
  //恢复SIGALRM信号的处理方式,若不恢复下次调用
  //任为本次设置的处理方式
  sigaction(SIGALRM,&oldact,NULL);
  return restsleep;
}

int main(){
  mysleep(10);
  return 0;
}

可重入函数:

这里写图片描述
注:情况1的结果为36,情况2的结果为24。
如上图所示,error()函数被不同的执行流调用,有可能在第一次调用还没有结束的时候,就再次进入该函数,此行为称为重入。error()函数访问一个全局变量,有可能因为重入造成全局变量数值的错乱,这种函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量和参数,则称为可重入函数。这是因为函数的局部变量和参数保存在函数的作用域空间中,当函数被多次调用时会创建多个副本,每个函数操作自己的数据函数间互不干扰。

竟态条件:

再来看看我们前面写的mysleep()函数。
假如程序运行到alarm()函数和pause()函数之间时CPU切换到其它程序运行时,且运行时间大于seconds秒,当CPU再次切回mysleep()程序时,内核发现SIGALRM信号递达,调用信号处理函数处理SIGALRM信号,处理完成回到内核后没有信号递达,再次回到mysleep()执行流继续执行pause()函数,此时SIGALRM信号已经处理完了,pause()函数等不到SIGALRM信号,所以导致程序错误,这种由于时序问题导致的错误称为竞态条件。
那么如何解决呢?在alarm()之前屏蔽SIGALRM信号在pause()函数之前再解除屏蔽这样可以吗?答案是否定的,因为再解除屏蔽和pause()之间也可能信号递达,那么到底该怎么办呢?要是能够将解除屏蔽操作和pause()函数合成一步操作就好了啊,由是就有了sigsuspend()函数,此函数说明如下:
sigsuspend()函数:

#include <signal.h>

int sigsuspend(const sigset_t *mask);
  • 说明:此函数的作用是将信号屏蔽字暂时设置为参数mask,并挂起等待信号到达两步合成一个原子操作,函数调用完成恢复调用之前的信号屏蔽字。其余类似pause()函数。
  • 参数:mask为暂时设置的信号屏蔽字。
  • 返回值:同pause()函数。

排除竟态条件后的mysleep()函数

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

//SIGALRM信号处理函数
void sigalarm(int signum){
  ;
}

unsigned int mysleep(unsigned int seconds){
  struct sigaction act,oldact;
  sigset_t newmask,oldmask,susmask;
  unsigned int restsleep;

  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);
  act.sa_handler = sigalarm;

  //注册SIGALRM信号的处理函数
  sigaction(SIGALRM,&act,&oldact);

  sigemptyset(&newmask);
  sigaddset(&newmask,SIGALRM);
  //阻塞SIGALRM信号
  sigprocmask(SIG_BLOCK,&newmask,&oldmask);
  alarm(seconds);
  susmask = oldmask;
  sigdelset(&susmask,SIGALRM);
  //原子操作,解除SIGALRM信号的屏蔽,并挂起等待
  sigsuspend(&susmask);
  //若为其余信号或提前的SIGALRM信号中断,
  //则返回闹钟剩余秒数,否则返回0.
  restsleep = alarm(0);
  //恢复信号屏蔽字
  sigprocmask(SIG_SETMASK,&oldmask,NULL);
  //恢复SIGALRM信号的默认处理方式。
  sigaction(SIGALRM,&oldact,NULL);
  return restsleep;
}
int main(){
  mysleep(10);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值