Linux下的信号(一)----信号的基本概念与产生

一,信号的基本概念

1,什么是信号?
日常生活中,当我们走到马路上时,看到的绿灯是一种信号,它能提示我们怎样安全的过马路。又比如,新学期开始学校给每个班发的课表也是一种信号,它能提示同学们在适当的时间地点去上相应的课程而不是虚度光阴……生活中其实我们忽略了很多信号,正是由于这些信号的存在,才使得我们的生活方便而有序。
总结一下你会发现信号是什么,信号就是当你看到它是知道它是什么,并且知道看到信号之后应该做什么,至于你遵不遵守就是你自己的事了, 计算机中的信号也不例外。

2,计算机中的信号
同日常生活中的信号一样,计算机在收到信号之后,并不一定会立即处理它,它会将收到的信号记录在其相应进程的PCB中的信号部分,等待合适的时间再去处理它。换句话说,一个进程是否收到信号,需要查看其进程PCB中的信号信息,给进程发信号实则是向进程PCB中写入信号信息。同时,我们的操作系统是很智能的,当任何一个进程接收到任何一个信号时,操作系统会自动地知道各信号应作何处理。
查看系统定义的信号列表:kill -l
这里写图片描述
注意列表中不是64个信号而是62个信号。
1-31:普通信号
34-64:实时信号
———-以下内容只针对普通信号进行讲解———-
每个信号都有一个宏名称和一个信号编号,这些宏定义可以在signal.h中找到,这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:
查看命令:man 7 signal
这里写图片描述
各种信号产生的原因:
[http://blog.sina.com.cn/s/blog_7ee076050101bz5v.html]

3,普通信号的存储:
试想一下,如果你是操作系统的设计人员,你会用什么来存储这31个信号呢?当然,设计人员都很聪明,他们采用的是位图存储,每一个bit位存储一个相应信号,31个信号只需4个字节(仅一个整形的大小)即可存储,bit位的位置表示信号的编号,bit位的值则用来表示是否收到该信号(0—-未收到该信号,1—-收到该信号)。
信号量的本质就是修改PCB中管理信号变量中的某个bit位。

4,信号产生的主要条件
背景知识:
前台进程:基本不用和用户交互,优先级稍微低一些(运行前台进程: ./test.c)
后台进程:需要和用户进行交互,需要较高的相应速度,优先级别高(运行后台进程: ./test.c &)

1〉用户在终端按下某些组合键时,终端驱动程序会发送信号给前台进程。例如:
Ctrl-C产生SIGINT信号(2号信号,终止前台进程),
Ctrl-\产生SIGQUIT信号(3号信号,捕捉信号),
Ctrl-Z产生SIGTSTP信号(20号信号,可使前台进程停止)。
2〉硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号
例如:当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU(内存管理单元)会产生异常,内核将这个异常解释SIGSEGV信号发送给进程。
3〉一个进程调用kill(2)函数可以发送信号给另一个进程
可以用kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。 如果不想按默认动作处理信号,用户程序可以调用sigaction(2)函数告诉内核如何处理某种信号。

5,处理信号的方法
1〉忽略此信号(大多数信号都可以使用该方法处理)。
特例:SIGKILL , SIGSTOP
原因:它们向超级用户提供一种使进程终止或停止的方法,同时,如果忽略某些由硬件异常产生的信号,则进程的行为是未定义的。
2〉执行该信浩的默认处理动作(与信号的种类有关,大多数信号会直接终止该进程)。
3〉用信号捕捉函数为该信号指定自定义动作。
特例:不能捕捉SIGKILL , SIGSTOP信号
原因:为了防止非法用户的恶意入侵使得进程永远杀不掉。
信号捕捉函数:修改信号的默认动作,有些信号是不能被捕捉的,如9号信号SIGKILL等。

#include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

参数:signum:信号编号 handler:指向怎样捕捉该信号的函数
返回值:signal函数的返回值是一个函数指针,成功返回返回以前的处理 配置,失败返回错误码对应的错误提示。
makefile:

 signal:signal.c
  2     gcc -o signal signal.c
  3 .PHONY:clean
  4 clean:
  5     rm -f signal

signal.c:

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

void myhandler(int signo)
{
    printf("got SIGINT\n");
}
int main()
{
   signal(2,myhandler);//捕捉2号信号SIGINT:Ctrl C
   while(1)
   {
       sleep(1);
   }
   return 0;
}

运行结果:
捕捉2号信号:
这里写图片描述

杀死进程(此时ctrl C不再能够结束进程):
这里写图片描述

进程结束:
这里写图片描述

改进:signal.c

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

typedef void (*sighandler_t)(int);//函数指针
sighandler_t old_handler = NULL;

void myhandler(int signo)
{
      printf("got SIGINT\n");//第一次按ctrlC捕捉2号信号SIGINT
      signal(2, old_handler);//恢复默认的处理,再按ctrl C的话,就会终止程序
}
int main()
{
   old_handler = signal(2,myhandler);//捕捉2号信号SIGINT:Ctrl C
   while(1)
   {
       sleep(1);
   }
   return 0;
}

运行结果:
这里写图片描述

二,产生信号

1,通过终端按键产生信号
上面说过,SIGINT(ctrl C)的默认处理动作是终止进程,SIGQUIT(ctrl )的默认处理动作是终止进程并且Core Dump,那么什么是Core Dump呢?
Core Dump:核心转储。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。
进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。
代码验证:
1>首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:ulimit -c 1024
这里写图片描述
2>然后写一个死循环程序:
makefile:

   .PHONY: all
   all:signal sig
   signal:signal.c
       gcc -o $@ $^
   sig:sig.c
       gcc -o $@ $^
   .PHONY:clean
   clean:
       rm -f signal sig

sig.c:

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

 int main()
 {
     printf("pid is %d\n",getpid());
     while(1);
     return 0;
 }

运行结果:
这里写图片描述
ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制而来,所以也具 有和Shell进程相同的Resource Limit值,这样就可以产生Core Dump了。

2,软硬件异常产生信号
软硬件异常信号 其他信号 SIGCHLD or SIGCLD 子进程结束时, 父进程会收到这个信号。 如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

3,调用系统函数想进程发信号
1〉首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号。
这里写图片描述
3603是sig进程的id。之所以要再次回车才显示“段错误”是因为在3603程终止掉 之前已经回到了Shell提示符等待用户输入下一条令,Shell不希望“段错误”信息和用户的输入交错在一起,所以等用户输入命令之后才显示。
指定某种信号的kill命令可以有多种写 法,上面的命令还可以写成:
kill -SIGSEGV 3603或kill -11 3603, 11是信号SIGSEGV的编号。
以往遇 到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include <signal.h>
int kill(pid_t pid, int signo);//给任意进程发信号
int raise(int signo);//自己给自己发任意信号

这两个函数都是成功返回0,错误返回-1。

kill函数实例:
makefile:

  1 .PHONY: all
  2 all: signal sig kill
  3 signal:signal.c
  4     gcc -o $@ $^
  5 sig:sig.c
  6     gcc -o $@ $^
  7 kill:kill.c
  8     gcc -o $@ $^
  9 .PHONY:clean
 10 clean:
 11     rm -f signal sig kill

kill.c:

  7 #include<stdio.h>
  8 #include<signal.h>
  9 #include<stdlib.h>
 10 
 11 static void usage(const char* proc)//.kill用法
 12 {
 13     printf("usage:%s sig pid\n",proc);
 14 }
 15 int main(int argc,char* argv[])
 16 {
 17     if(argc != 3)
 18     {
 19         usage(argv[0]);
 20         return 1;
 21     }
 22     int pid = atoi(argv[2]);//进程号
 23     int sig = atoi(argv[1]);//信号编号
 24     kill(pid,sig);//用当前进程给pid号进程发送sig号信号
 25     return 0;
 26 }

1〉首先在前台运行上面的死循环程序
这里写图片描述

2〉使用kill函数向死循环进程发送2号信号终止该进程
这里写图片描述

这里写图片描述

raise函数的实例:
makefile:

  1 .PHONY: all
  2 all: signal sig kill raise
  3 signal:signal.c
  4     gcc -o $@ $^
  5 sig:sig.c
  6     gcc -o $@ $^
  7 kill:kill.c
  8     gcc -o $@ $^
  9 raise:raise.c
 10     gcc -o $@ $^
 11 .PHONY:clean
 12 clean:
 13     rm -f signal sig kill raise

raise.c:

  7 #include<stdio.h>
  8 #include<signal.h>
  9 #include<stdlib.h>
 10 #include<unistd.h>
 11 
 12 void myhandler()
 13 {
 14     printf("this is a raise test:pid is %d\n",getpid());
 15 }
 16 int main()
 17 {
 18     signal(2,myhandler);
 19     while(1)
 20     {
 21         raise(2);//给自己发2号信号
 22         sleep(1);
 23     }
 24     return 0;
 25 }

运行中用kill命令杀死该进程:kill -9 4009
这里写图片描述

abort函数使当前进程接收到信号而异常终止。

#include <stdlib.h>
void abort(void);//自己给自己发终止信号

就像exit函数一样,abort函数总是会成功的,所以没有返回值。
makefile:

  1 .PHONY: all
  2 all: signal sig kill raise abort
  3 signal:signal.c
  4     gcc -o $@ $^
  5 sig:sig.c
  6     gcc -o $@ $^
  7 kill:kill.c
  8     gcc -o $@ $^
  9 raise:raise.c
 10     gcc -o $@ $^
 11 abort:abort.c
 12     gcc -o $@ $^
 13 .PHONY:clean
 14 clean:
 15     rm -f signal sig kill raise abort

abort.c:

  7 #include<stdio.h>
  8 #include<unistd.h>
  9 #include<stdlib.h>
 10 #include<signal.h>
 11 
 12 int count = 0;
 13 void myhandler(int sig)
 14 {
 15     printf("count is %d,sig is:%d\n",count++,sig);
 16 }
 17 int main()
 18 {
 19     for(int i = 1; i <= 31; ++i)
 20     {
 21         signal(i,myhandler);
 22     }
 23     while(1)
 24     {
 25         sleep(1);
 26         abort();//使当前进程接收到信号而异常止
 27     }
 28     return 0;
 29 }

这里写图片描述

4,由软件条件产生信号
由软件条件产生的信号:SIGPIPE,SIGALRM(主要介绍)
alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

返回值:是0或者是以前设定的闹钟时间还余下 的秒数
如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
makefile:

  1 .PHONY: all
  2 all: signal sig kill raise abort alarm
  3 signal:signal.c
  4     gcc -o $@ $^
  5 sig:sig.c
  6     gcc -o $@ $^
  7 kill:kill.c
  8     gcc -o $@ $^
  9 raise:raise.c
 10     gcc -o $@ $^
 11 abort:abort.c
 12     gcc -o $@ $^
 13 alarm:alarm.c
 14     gcc -o $@ $^
 15 .PHONY:clean
 16 clean:
 17     rm -f signal sig kill raise abort alarm

alarm.c:

  7 #include<stdio.h>
  8 #include<signal.h>
  9 #include<stdlib.h>
 10 #include<unistd.h>
 11 
 12 int count = 0;
 13 void myhandler()
 14 {
 15     printf("count is %d\n",count);
 16     exit(1);
 17 }
 18 int main()
 19 {
 20     signal(SIGALRM,myhandler);
 21     alarm(1);//设置闹钟
 22     while(1)
 23     {
 24         count++;
 25     }
 26     return 0;
 27 }

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。
这里写图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值