Skip to content

信号相关函数

signal函数

函数作用

  • 注册信号捕捉函数

函数原型

C++
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数参数

  • signum:信号编号
  • handler:信号处理函数

kill函数/命令

描述

  • 给指定进程发送指定信号
  • kill命令:kill -SIGKILL 进程PID

函数原型

C++
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

函数返回值

  • 成功:0;
  • 失败:-1,设置errno

函数参数

  • sig信号参数:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
  • pid参数:
  • pid > 0: 发送信号给指定的进程。
    • pid = 0: 发送信号给与调用kill函数进程属于同一进程组的所有进程。
    • pid < -1: 取|pid|发给对应进程组。
    • pid = -1:发送给进程有权限发送的系统中所有进程。

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。

abort函数raise函数

raise函数

函说描述

  • 给当前进程发送指定信号(自己给自己发)

函数原型

C++
#include <signal.h>
int raise(int sig);

函数返回值

  • 成功:0,失败非0值

函数拓展

  • raise(signo) == kill(getpid(), signo);

abort函数

函数描述

  • 给自己发送异常终止信号 6) SIGABRT,并产生core文件

函数原型

C++
#include <stdlib.h>
void abort(void);

函数拓展

  • abort() == kill(getpid(), SIGABRT);

alarm函数

函数原型

C++
#include <unistd.h>

unsigned int alarm(unsigned int seconds);

函数描述

  • 设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止每个进程都有且只有唯一的一个定时器。

函数返回值

  • 返回0或剩余的秒数,无失败。例如:

img

常用操作

  • 取消定时器alarm(0),返回旧闹钟余下秒数。

alarm使用的是自然定时法,与进程状态无关,就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。

练习题1:编写一个程序测试alarm函数

练习题2:编写程序,测试你的电脑1秒种能数多个数字。

  • 使用time命令查看程序执行的时间。程序运行的瓶颈在于IO,优化程序,首选优化IO。
  • 实际执行时间 = 系统时间 + 用户时间 + 损耗时间

损耗的时间主要来来自文件IO操作,IO操作会有用户区到内核区的切换,切换的次数越多越耗时。

setitimer函数

函数原型

C++
#include <sys/time.h>
int getitimer(int which, struct itimerval* curr_value);
int setitimer(int which, const struct itimerval* new_value, struct itimerval* old_value);

函数描述

  • 设置定时器(闹钟),可代替alarm函数,精度微秒us,可以实现周期定时。

函数返回值

  • 成功:0;
  • 失败:-1,设置errno值

函数参数:

  • which:指定定时方式

    • 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
    • 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
    • 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
  • new_value:struct itimerval,负责设定timeout时间。

    • itimerval.it_value: 设定第一次执行function所延迟的秒数

    • itimerval.it_interval: 设定以后每几秒执行function

      C++
      struct itimerval {
          struct timerval it_interval; // 闹钟触发周期
          struct timerval it_value; // 闹钟触发时间
      };
      struct timeval {
          long tv_sec; // 秒
          long tv_usec; // 微秒
      };
  • old_value: 存放旧的timeout值,一般指定为NULL

练习: 使用setitimer实现每隔一秒打印一次hello, world。

信号集相关函数

由于信号集属于内核的一块区域,用户不能直接操作内核空间,为此,内核提供了一些信号集相关的接口函数,使用这些函数用户就可以完成对信号集的相关操作。

信号集是一个能表示多个信号的数据类型,sigset_t set,set即一个信号集。既然是一个集合,就需要对集进行添加、删除等操作。

sigset_t类型的定义在signal.h文件中的第49行处:typedef __sigset_t sigset_t;

__sigset_t的定义在sigset.h文件中的26,27行处:

C++
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))

typedef struct{
    unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

上述变量类型的定义的查找有个小窍门: 可以执行gcc的预处理命令:

gcc -E test.c -o test.i 这样头文件就会展开,可以直接到test.i文件中看到相关变量类型的定义。

信号集相关函数

int sigemptyset(sigset_t *set);

  • 函数说明:将某个信号集清0
  • 函数返回值:成功:0;失败:-1,设置errno

int sigfillset(sigset_t *set);

  • 函数说明:将某个信号集置1
  • 函数返回值:成功:0;失败:-1,设置errno

int sigaddset(sigset_t *set, int signum);

  • 函数说明:将某个信号加入信号集合中
  • 函数返回值:成功:0;失败:-1,设置errno

int sigdelset(sigset_t *set, int signum);

  • 函数说明:将某信号从信号清出信号集
  • 函数返回值:成功:0;失败:-1,设置errno

int sigismember(const sigset_t *set, int signum);

  • 函数说明:判断某个信号是否在信号集中
  • 函数返回值:在:1;不在:0;出错:-1,设置errno

sigprocmask函数

  • 函数说明:用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程控制块中的信号屏蔽字(阻塞信号集)。
  • 特别注意,屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。
  • 函数原型
    C++
    #include <signal.h>
    
    /* Prototype for the glibc wrapper function */
    int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
    
    /* Prototype for the underlying system call */
    int rt_sigprocmask(int how, const kernel_sigset_t* set, kernel_sigset_t* oldset, size_t sigsetsize);
    
    /* Prototype for the legacy system call (deprecated) */
    int sigprocmask(int how, const old_kernel_sigset_t* set, old_kernel_sigset_t* oldset);
  • 函数返回值:成功:0;失败:-1,设置errno
  • 函数参数
    • how参数取值:假设当前的信号屏蔽字为mask
    • SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
    • SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
    • SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
    • set:传入参数,是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字。
    • oldset:传出参数,保存旧的信号屏蔽字。

sigpending函数

  • 函数原型:
    C++
    #include <signal.h>
    int sigpending(sigset_t* set);
  • 函数说明:读取当前进程的未决信号集
  • 函数参数:set传出参数
  • 函数返回值:成功:0;失败:-1,设置errno

练习:编写程序,设置阻塞信号集并把所有常规信号的未决状态打印至屏幕。

信号捕捉函数

sigaction函数

函数说明

  • 注册一个信号处理函数

函数原型

C++
#include <signal.h>
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);

函数参数

  • signum:捕捉的信号
  • act: 传入参数,新的处理方式。
  • oldact: 传出参数,旧的处理方式
C++
struct sigaction {
    void  (*sa_handler)(int);// 信号处理函数
    void  (*sa_sigaction)(int, siginfo_t*, void*); //信号处理函数
    sigset_t  sa_mask; //信号处理函数执行期间需要阻塞的信号
    int      sa_flags; //通常为0,表示使用默认标识
    void     (*sa_restorer)(void);
};

总结

  • sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
  • sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
  • sa_flags:通常设置为0,使用默认属性。
  • sa_restorer:已不再使用

练习:编写程序,使用sigaction函数注册信号捕捉函数,并使用这个程序验证信号是否支持排队。

知识点: 信号处理不支持排队:

  • 在XXX信号处理函数执行期间,XXX信号是被阻塞的,如果该信号产生了多次,在XXX信号处理函数结束之后, 该XXX信号只被处理一次.
  • 在XXX信号处理函数执行期间,如果阻塞了YYY信号,若YYY信号产生了多次,当XXX信号处理函数结束后,YYY信号只会被处理一次.

内核实现信号捕捉的过程

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

  1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
  2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
  3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
  4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
  5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
  6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。