前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux信号种类与函数

Linux信号种类与函数

作者头像
xxpcb
发布2020-08-04 15:40:07
2.9K0
发布2020-08-04 15:40:07
举报

主要介绍:

  • Linux中的信号种类
  • 信号操作的相关函数

Linux中的信号种类

信号是一种进程间通信的方法,应用于异步事件的处理。信号的实质是一种软中断。

使用kill -l可以查看Linux系统中的所有信号,如下:

代码语言:javascript
复制
deeplearning@deeplearning:~$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

对其中一些信号进行介绍:

  • SIGHUP:本信号在用户终端连接结束时(正常或非正常)发出。
  • SIGINT:程序终止(或中断,interrupt)信号,通常是Ctrl+cDelete键(INTR字符)时发出。
  • SIGQUIT:与SIGINT类似,但由Ctrl+\(QUIT字符)控制,进程收到该信号时会产生core文件,类似于一个程序错误信号。
  • SIGLL:执行了非法指令,通常是可执行文件本身错误。
  • SIGKILL:用来立即结束程序的运行,该信号不能被阻塞、处理或忽略。
  • SIGTERM:程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞或处理,shell命令的kill默认产生该信号。
  • SIGSTOP:停止(stopped)进程的执行,注意和terminate及interrupt的区别,该进程还未结束,只是暂停执行,该信号与SIGKILL一样不能被阻塞、处理或忽略。
  • SIGWINCH:窗口大小改变时发出的信号。

信号操作的相关函数

信号的处理

signal函数

要对一个信号进行处理(除了无法捕捉的SIGKILL和SIGSTOP),需要为其注册相应的处理函数,通过调用signal()函数可以进行注册。

  • #include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>void SignHandler(int iSignNum){ printf("Capture signal number:%d\n",iSignNum); exit(1);}int main(void){ signal(SIGINT,SignHandler); while(1) sleep(1); return 0;}使用gcc编译,并执行,通过Ctrl+c查看效果:
    1. 捕捉SIGINT信号,catch_sigint.c:
  • #include <signal.h> #include <stdio.h> #include <unistd.h> int main(void) { signal(SIGINT,SIG_IGN); while(1) sleep(1); return 0; } 该程序将“Ctrl+C"产生的SIGINT信号忽略掉了,不能结束进行,不过可以通过”Ctrl+"发送SIGQUIT信号。 编译运行: $ ./ignore_sigint ^C^\Quit (core dumped) (键入“Ctrl+c”,再键入“Ctrl+\”) 可以看到,“Ctrl+c”无效,因为被程序忽略了,自能通过“Ctrl+\”结束程序。
    1. 忽略SIGINT信号,ignore_sigint.c:
  • #include <signal.h>#include <stdio.h>#include <unistd.h>int main(void){ signal(SIGINT,SIG_DFL); while(1) sleep(1); return 0;}运行1:
    1. SIGINT信号的默认处理,default_sigint.c:
  • #include <signal.h>#include <stdio.h>#include <sys/types.h>#include <unistd.h>void sigroutine(int dunno){ switch(dunno) { case 1:printf("Capture SIGHUP signal,the signal number is %d\n",dunno);break; case 2:printf("Capture SIGINT signal,the signal number is %d\n",dunno);break; case 3:printf("Capture SIGQUIT signal,the signal number is %d\n",dunno);break; } return;}int main(void){ printf("process ID is %d\n", getpid()); if(signal(SIGHUP,sigroutine)==SIG_ERR) printf("Couldn't register signal handler for SIGHUP!\n"); if(signal(SIGINT,sigroutine)==SIG_ERR) printf("Couldn't register signal handler for SIGINT!\n"); if(signal(SIGQUIT,sigroutine)==SIG_ERR) printf("Couldn't register signal handler for SIGQUIT!\n"); while(1) sleep(1); return 0;}运行:
    1. 定义多个信号处理函数,signals.c:

sigaction函数

Linux还提供另外一种功能更加强大的信号处理机制:sigaction系统调用。sigaction函数的功能是检查或修改与指定信号相关联的处理动作,该函数可完全替代signal函数,并且还提供更加详细的信息,确切了解进程接收到信号时所发生的具体细节。

  • sigaction函数使用举例,sigaction.c:#include <stdio.h>#include <string.h>#include <unistd.h>#include <signal.h>int g_iSeq=0;void SignHandlerNew(int iSignNo, siginfo_t *pInfo, void *pReserved){ int iSeq = g_iSeq++; printf("%d Enter SignHandlerNew, signo:%d.\n", iSeq, iSignNo); sleep(3); printf("%d Leave SignHandlerNew, signo:%d.\n", iSeq, iSignNo);}int main(void){ char szBuf[20]; int iRet; struct sigaction act; act.sa_sigaction = SignHandlerNew; act.sa_flags = SA_SIGINFO; sigemptyset(&act.sa_mask); sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL); do{ iRet=read(STDIN_FILENO, szBuf, sizeof(szBuf)-1); if(iRet<0) { perror("read fail."); break; } szBuf[iRet]=0; printf("Get: %s", szBuf); }while(strcmp(szBuf, "quit\n")!=0); return 0;}执行:

信号集

在实际应用中,一个用户进程常常需要对多个信号进行处理,在LInux中引入信号集(signal set)概念,用于表示由多个信号所组成集合的数据类型,其定义为sigset_t类型的变量。

信号的发送

发送信号的函数有:kill,raise,sigqueue,alarm,setitimer,abort。

kill函数

kill函数用于向某一进程或进程组发送信号。

  • 父进程使用kill函数向子进程传递一个SIGABRT信号,使子进程非正常结束,kill.c: #include<unistd.h> #include<signal.h> #include<sys/types.h> #include<sys/wait.h> #include<stdio.h> int main(void) { pid_t pid; int status; if(!(pid=fork())) { printf("Hi I am child process!\n"); sleep(10); printf("Hi I am child process, again!\n"); return 1; } else { printf("send signal to child process (%d)\n", pid); sleep(1); if(kill(pid, SIGABRT)==1) printf("kill failed!\n"); wait(&status); if(WIFSIGNALED(status)) printf("child process receive signal %d\n", WTERMSIG(status)); } return 0; } 运行: $ ./kill send signal to child process (2689) Hi I am child process! child process receive signal 6 从结果可以看出,当父进程将SIGABRT发送给子进程(ID 2689)后,子进程非正常结束,第2句输出语句没有执行。

raise函数

raise函数用于向进程本身发送信号。

  • 使用raise函数向自身进程发送一个SIGABRT信号,使自己非正常结束,raise.c: #include<sys/types.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> int main(void) { printf("Hello, I like Linux C Progrms!\n"); if(raise(SIGABRT)==-1) { printf("raise failed!"); exit(1); } printf("Hello, I like Linux C Progrms, again!\n"); return 0; } 运行: $ ./raise Hello, I like Linux C Progrms! Aborted (core dumped) 可以看到程序非正常结束。

sigqueue函数

sigqueue是比较新的发送信号系统调用,主要针对实时信号提出的,支持信号带有参数,通常与sigaction函数配合使用。

  • 使用sigqueue函数向进程自身发送信号SIGUSR1信号,并附加一个字符串信息,sigqueue.c: #include<stdio.h> #include<signal.h> #include<unistd.h> #include<stdlib.h> void SigHandler(int signo, siginfo_t *info, void *context) { char *pMsg=(char*)info->si_value.sival_ptr; printf("Receive signalunmber:%d\n",signo); printf("Receive Meaasage:%s\n",pMsg); } int main(void) { struct sigaction sigAct; sigAct.sa_flags=SA_SIGINFO; sigAct.sa_sigaction=SigHandler; if(sigaction(SIGUSR1, &sigAct, NULL) == -1) { printf("sigaction filed!\n"); exit(1); } sigval_t val; char pMsg[] = "I like Linux C programs!"; val.sival_ptr = pMsg; if(sigqueue(getpid(), SIGUSR1, val) == -1) { printf("sigqueue failed!\n"); exit(1); } sleep(3); return 0; } 运行: $ ./sigqueue Receive signalunmber:10 Receive Meaasage:I like Linux C programs! 可以看出,进程成功接收到了自身发送的信号10(SIGUSR1)以及信号携带的字符串参数。

alarm函数

alarm函数专门为SIGALRM信号而设,使系统在一定时间之后发送信号。

使用alarm函数产生SIGALRM信号,alarm时间参数设置为5分钟,alarm.c:

代码语言:javascript
复制
#include<unistd.h>
#include<signal.h>
#include<stdio.h>

void handler()
{
    printf("Hello, I like linux C programs!\n");
}

int main(void)
{
    int i;
    signal(SIGALRM, handler);
    alarm(5);
    for(i=1;i<7;i++)
    {
        printf("sleep %d ...\n", i);
        sleep(1);
    }

    return 0;
}

执行:

代码语言:javascript
复制
$ ./alarm
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
sleep 5 ...
Hello, I like linux C programs!
sleep 6 ...

在for循环运行了5次,即大约5秒后,产生了SIGALRM信号,此时由signal注册信号的处理函数handler,输出字符串。信号处理完毕后又返回先前程序的中断点,继续执行for循环。

setitimer函数

setitimer函数与alarm函数一样,也可以用于使系统在某一时刻发出信号,但它可以更加精确地控制程序。

  • 使用setitimer函数产生SIGALRM信号,setitimer.c: #include<signal.h> #include<time.h> #include<sys/time.h> #include<unistd.h> #include<stdio.h> #include<stdlib.h> static void ElsfTimer(int signo) { struct timeval tp; struct tm *tm; gettimeofday(&tp, NULL); tm = localtime(&tp.tv_sec); printf("sec = %ld\t", tp.tv_sec); printf("usec = %ld\n", tp.tv_usec); printf("%d-%d-%d%d:%d:%d\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } static void InitTime(int tv_sec, int tv_usec) { struct itimerval value; signal(SIGALRM, ElsfTimer); value.it_value.tv_sec = tv_sec; value.it_value.tv_usec = tv_usec; value.it_interval.tv_sec = tv_sec; value.it_interval.tv_usec = tv_usec; setitimer(ITIMER_REAL, &value, NULL); } int main(void) { InitTime(5, 0); while(1){} exit(0); } 执行: ./setitimer sec = 1574475716 usec = 295047 2019-11-2310:21:56 sec = 1574475721 usec = 295042 2019-11-2310:22:1 sec = 1574475726 usec = 295041 2019-11-2310:22:6 sec = 1574475731 usec = 295041 2019-11-2310:22:11 sec = 1574475736 usec = 295041 2019-11-2310:22:16 sec = 1574475741 usec = 295041 2019-11-2310:22:21 ^\Quit (core dumped) (键入“Ctrl+\”退出) 可以看出,程序每隔5秒便会调用信号处理函数ElsfTimer,打印当前系统的时间和日期。

abort函数

向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可以定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort后,SIGABORT仍能被进程接收。

信号的阻塞

在Linux的信号控制中,有时不希望进程在接收到信号时立刻中断进行的执行,也不希望该信号被完全忽略,而是延时一段时间再去调用相关的信号处理函数。

sigprocmask函数

sigprocmask函数可以用于检查或更改进程的信号掩码(signalmask)。信号掩码是由被阻塞的发送给当前进程的信号组成的信号集。

将sigaction.c程序进行修改,得到如下程序:

  • 阻塞屏蔽SIGINT信号,block_sigint.c函数: #include<stdio.h> #include<string.h> #include<unistd.h> #include<signal.h> int g_iSeq = 0; void SignHandlerNew(int iSignNo, siginfo_t *pInfo, void *pReserved) { int iSeq = g_iSeq++; printf("%d Enter SignHandlerNew, signo:%d.\n", iSeq, iSignNo); sleep(3); printf("%d Leave SignHandlerNew, signo:%d.\n", iSeq, iSignNo); } int main(void) { char szBuf[20]; int iRet; struct sigaction act; act.sa_sigaction = SignHandlerNew; act.sa_flags = SA_SIGINFO; sigset_t sigSet; sigemptyset(&sigSet); sigaddset(&sigSet, SIGINT); sigprocmask(SIG_BLOCK, &sigSet, NULL); sigemptyset(&act.sa_mask); sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL); do{ iRet = read(STDIN_FILENO, szBuf, sizeof(szBuf)-1); if(iRet<0) { perror("read fail."); break; } szBuf[iRet]=0; printf("Get:%s",szBuf); }while(strcmp(szBuf, "quit\n")!=0); return 0; } 运行: $ ./block_sigint hello! (键入“hello!”) Get:hello! linux! (键入“linux!”) Get:linux! ^\0 Enter SignHandlerNew, signo:3. (键入“Ctrl+\”,产生SIGQUIT信号) 0 Leave SignHandlerNew, signo:3. (SIGQUIT信号处理完毕) read fail.: Interrupted system call (读出错,进程中断,程序非正常退出) 与上面 的sigaction.c程序相比,此程序键入“Ctrl+c"不再有反应,屏蔽了SIGINT信号。

sigsuspend函数

sigsuspend函数用于使进程挂起,然后等待开放信号的唤醒。注意,此函数没有成功返回值,如果它返回到调用者,则总是返回-1。

计时器与信号

睡眠函数

Linux系统下有两个睡眠函数:sleep()usleep(),函数原型为:

代码语言:javascript
复制
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);

两个函数分别让进程睡眠seconds秒和usec微秒。

sleep函数的内部是使用信号机制进行处理,用到的函数有:

代码语言:javascript
复制
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
int pause(void);

alarm函数告知自身进程,在seconds秒后自动产生一个SIGALRM信号。而pause函数用于将自身进程挂起,直到有信号发生时才从pause返回。

  • 使用pause函数将进程挂起,模拟随眠3秒钟,pause.c: #include<signal.h> #include<stdio.h> #include<unistd.h> void SignHandler(int iSignNo) { printf("signal;%d\n", iSignNo); } int main(void) { signal(SIGALRM, SignHandler); alarm(3); printf("Before pause().\n"); pause(); printf("After pause().\n"); return 0; } 执行: $ ./pause Before pause(). signal;14 After pause(). 在输出第一句"Before pause()."后,等待约3秒钟,采输出第二句。

时钟处理

Linux系统为每个进程维护3个计时器:

  • 真实计时器计算的是程序运行的**实际时间**
  • 虚拟计时器计算的是程序运行在**用户态**时所消耗的时间(实际时间减去系统调用和程序随眠时间)
  • 实用计时器计算的是程序处于**用户态**和**内核态**所消耗的时间之和

参考:《精通Linux C编程》- 程国钢

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农爱学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux中的信号种类
  • 信号操作的相关函数
    • 信号的处理
      • signal函数
      • sigaction函数
      • 信号集
    • 信号的发送
      • kill函数
      • raise函数
      • sigqueue函数
      • alarm函数
      • setitimer函数
      • abort函数
    • 信号的阻塞
      • sigprocmask函数
      • sigsuspend函数
    • 计时器与信号
      • 睡眠函数
      • 时钟处理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档