Linux系统编程之信号以及相关API函数详解

本文详细介绍了信号的概念、产生方式、递达与未决状态、处理方法、信号集、常用信号及其默认行为,以及相关的API函数如kill(),alarm(),setitimer()等,并探讨了信号的捕捉性、注意事项和内核实现。
摘要由CSDN通过智能技术生成

目录

信号的概念

信号的机制

与信号相关的事件和状态

产生信号

递达

未决

信号的处理方式

未决信号集

阻塞信号信号集

查看信号

信号的四要素

常用信号介绍

信号相关的API函数

kill()

alarm()

setitimer()

sigemptyset()

sigfillset()

sigaddset()

sigdelset()

sigismember()

sigprocmask()

sigpending()

signal()

sigaction()

信号的捕捉性

信号的注意事项


信号的概念

        信号再我们的生活中随处可见,如:古代战争中摔杯为号;现在战争中的信号弹;体育比赛中使用的信号强等,它们有相同的共性:

  1. 简单
  2. 不能携带大量信息
  3. 满足某个设定条件才发送
  4. 信号是信息的载体,Linux/UNIX环境下古老的通信方式,线下依然是主要的通信手段。

        UNIX早期版本就提供了信号机制,但不可靠,信号可能丢失,Berkley和AT&T都对信号模型做了更改,增加了可靠信号的机制,但彼此不兼容,POSIX.1对可靠信号进行可标准化。

信号的机制

        A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,取处理信号,处理完毕再继续执行,与硬件和中断类似(异步模式),但信号是软件层面上实现的中断,早期被称为"软中断"。

        由于信号是通过软件方法实现的,其实现手段导致信号有很强的延时性,但对用户来说这个延时非常短,不易察觉。

        每个进程收到的所有信号都是由内核负责发送的,并且由 内核处理。

与信号相关的事件和状态

产生信号

  1. 按键产生:Ctrl + c、Ctrl + z、Ctrl + \
  2. 系统调用产生:kill、raise、abort
  3. 软件条件产生:定时器、alarm
  4. 硬件异常产生:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
  5. 命令产生:kill命令

递达

  • 信号产生之后传递并且到达进程

未决

  • 产生和递达之间的状态,由于信号的屏蔽或阻塞导致的该状态

信号的处理方式

  1. 执行默认动作
  2. 忽略(丢弃)
  3. 捕捉(调用用户编写的信号畜栏里函数)

        Linux内核的进程控制块PCB是一个结构体,除了包含进程id、状态、工作目录、用户id、组id、文件按描述符,还包含了信号相关的信息,主要是指阻塞信号集和未决信号集。

未决信号集

        当信号产生后,未决信号集中描述该信号的为立刻反转为1,表示该信号处于未决状态,这一时刻非常短暂,当信号被处理后该位对应的位立刻反转为0。

        信号产生后由于阻塞或屏蔽导致信号不能递达,这类信号的集合被称为未决信号集,再解除屏蔽前,信号会一直处理未决状态。

阻塞信号信号集

        将某些信号加入该集合,就会对该信号进行屏蔽,当该信号被屏蔽后,再次收到该信号时,对该信号的处理将会推迟(直到该信号解除屏蔽)

查看信号

        再Linux操作系统下,可以使用 kill -l 指令查看当前系统可以使用的信号有哪些

信号的四要素

  1. 编号:每个信号都有一个唯一的编号,用于在系统中标识该信号。例如,SIGHUP的编号是1,SIGINT的编号是2。
  2. 名称:每个信号都有一个对应的名称,用于在编程或命令行中引用该信号。例如,SIGHUP的名称是SIGHUP,SIGINT的名称是SIGINT。
  3. 事件:每个信号都与一个特定的事件相关联。当发生该事件时,操作系统会向相应的进程发送对应的信号。例如,SIGINT与用户在终端上按下Ctrl+C键相关联。
  4. 默认处理动作:每个信号都有一个默认的处理动作,即操作系统在收到信号时所采取的默认行为。默认处理动作可以是终止进程、忽略信号、产生核心转储文件等。可以通过编程或命令行来修改信号的默认处理动作。

        信号4要素对于理解和处理信号非常重要。在编程中,可以使用信号处理函数来捕获和处理特定的信号,以便根据需要采取适当的操作。

常用信号介绍

SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。
SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。
SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
SIGTTOU: 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。
SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
SIGPWR:关机。默认动作为终止进程。
SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
SIGRTMIN ~ (64) SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

信号相关的API函数

kill()

功能
	向指定进程发送信号。可以通过指定进程ID将特定信号发送给目标进程
头文件
	#include <sys/types.h>
    #include <signal.h>
原型
	int kill(pid_t pid, int signum)
参数
	pid			要发送信号的进程ID。
	signum	要发送的信号编号,不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值
	成功 0
    失败 -1
  
	pid > 0:  发送信号给指定的进程。
	pid = 0:  发送信号给 与调用kill函数进程属于同一进程组的所有进程。
	pid < 0:  取|pid|发给对应进程组。
	pid = -1:发送给进程有权限发送的系统中所有进程。

alarm()

功能
	设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。
头文件
	#include <unistd.h>
原型
	unsigned int alarm(unsigned int seconds)
参数
	seconds	要设置的定时器的时间,单位为秒
返回值
	返回之前设置的定时器剩余的时间,如果之前没有设置定时器,则返回0
  
alarm函数定时与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。

setitimer()

功能
	设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
头文件
	#include <sys/time.h>
原型
	int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
参数
  which	指定要设置的定时器类型,可以是ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF之一。
  new_value	指向一个itimerval结构的指针,用于设置新的定时器值。
  old_value	指向一个itimerval结构的指针,用于获取之前的定时器值。
返回值
	成功 0
  失败 -1

sigemptyset()

功能
	将指定的信号集清空,即将所有信号位都置为0
头文件
	#include <signal.h>
原型
	int sigemptyset(sigset_t *set)
参数
	set	一个指向sigset_t类型的指针,用于表示要清空的信号集
返回值
	成功 0
  失败 -1

sigfillset()

功能
	将指定的信号集置为1,即将所有信号位都置为1
头文件
	#include <signal.h>
原型
	int sigfillset(sigset_t *set);
参数
	set	一个指向sigset_t类型的指针,用于表示要置1的信号集
返回值
	成功 0
  失败 -1

sigaddset()

功能
	将指定的信号添加到信号集中,即将对应信号位置为1
头文件
	#include <signal.h>
原型
	int sigaddset(sigset_t *set, int signum);
参数
	set	一个指向sigset_t类型的指针,用于表示要添加信号的信号集;
  signum	要添加的信号编号。
返回值
成功	0
失败	-1

sigdelset()

功能
	将指定的信号从信号集中删除,即将对应信号位置为0
头文件
	#include <signal.h>
原型
	int sigdelset(sigset_t *set, int signum);
参数
	set		一个指向sigset_t类型的指针,用于表示要从中删除信号的信号集;
  signum	要删除的信号编号。
返回值
	成功0
  失败-1

sigismember()

功能
	判断指定的信号是否在信号集中,即判断对应信号位是否为1。
头文件
	#include <signal.h>
原型
	int sigismember(const sigset_t *set, int signum);
参数
	set		一个指向sigset_t类型的指针,用于表示要判断的信号集;
  signum	是要判断的信号编号。
返回值
  在集合中返回1
  不在集合中返回0
  出错返回-1。

sigprocmask()

功能
	设置或修改进程的信号屏蔽字,即设置进程的信号屏蔽集。
头文件
	#include <signal.h>
原型
	int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
参数
	how	指定如何修改信号屏蔽字,可以是以下几种取值之一:
      SIG_BLOCK:将set中的信号添加到当前信号屏蔽字中。
      SIG_UNBLOCK:将set中的信号从当前信号屏蔽字中移除。
      SIG_SETMASK:将当前信号屏蔽字设置为set中的值。
	set	指向sigset_t类型的指针,表示要设置的信号屏蔽集。
	oldset	指向sigset_t类型的指针,用于获取之前的信号屏蔽集。
返回值
	成功 0
  失败 -1

sigpending()

功能
	读取当前进程的未决信号集
头文件
	#include <signal.h>
原型
	int sigpending(sigset_t *set)
参数
	set	传出参数,未决信号集
返回值
	成功 0
  失败 -1

signal()

功能
	设置信号的处理函数,即指定在接收到某个信号时要执行的处理函数
头文件
	#include <signal.h>
原型
	void (*signal(int signum, void (*handler)(int)))(int);
参数
	ignum:要设置处理函数的信号编号,可以是以下几种取值之一:
        SIGABRT:程序异常终止。
        SIGFPE:算术异常。
        SIGILL:非法指令。
        SIGINT:中断信号。
        SIGSEGV:段错误。
        SIGTERM:终止信号。
  handler:指向处理函数的指针,可以是以下几种取值之一:
        SIG_DFL:默认处理函数。
        SIG_IGN:忽略信号。
自定义的信号处理函数。
返回值
	成功 指向之前信号处理函数的指针
  失败 返回SIG_ERR
  
  signal函数在设置信号处理函数时,会返回指向之前信号处理函数的指针,以便在需要恢复之前的处理函数时使用

sigaction()

功能
	设置信号的处理函数和处理方式,相比于signal函数,它提供了更多的功能和灵活性。
头文件
	#include <signal.h>
原型
	int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数
	signum:要设置处理函数的信号编号,可以是以下几种取值之一,也可以是自定义的信号编号。
	act:指向struct sigaction类型的指针,表示要设置的信号处理方式。
	oldact:指向struct sigaction类型的指针,用于获取之前的信号处理方式。
返回值
	成功 0
  失败 -1
  
struct sigaction {
    void (*sa_handler)(int); // 处理函数的指针
    void (*sa_sigaction)(int, siginfo_t *, void *); // 扩展的处理函数的指针
    sigset_t sa_mask; // 用于阻塞的信号集
    int sa_flags; // 标志位
    void (*sa_restorer)(void); // 未使用
};

信号的捕捉性

        信号捕捉特性是指在接收到信号时,进程可以通过设置信号处理函数来对信号进行处理。这种特性允许进程在接收到特定信号时执行自定义的操作,例如处理异常情况、优雅地终止进程等。

        信号捕捉特性的实现依赖于操作系统提供的信号机制。当进程接收到一个信号时,操作系统会中断当前的执行流程,调用相应的信号处理函数来处理该信号。

        在信号处理函数中,可以执行一些特定的操作,例如打印日志、发送通知、保存数据等。处理函数的具体逻辑由程序员根据实际需求自行编写。

        在设置信号处理函数时,可以使用signal函数或sigaction函数。signal函数是较早的信号处理函数,功能相对简单,只能指定处理函数的指针。而sigaction函数提供了更多的功能和灵活性,可以指定处理函数的指针、阻塞信号集、处理方式等。

        需要注意的是,在信号处理函数中,应尽量避免执行耗时操作和非可重入函数,以免影响程序的正常执行。另外,某些信号是不可捕捉的,例如SIGKILL和SIGSTOP,它们的处理函数无法被设置或修改。

信号捕捉特性在很多场景下都有重要的作用,例如:

  • 处理异常情况:当程序发生异常时,可以通过捕捉相应的信号来进行错误处理,例如打印错误信息、记录日志、进行资源清理等。
  • 优雅地终止进程:通过捕捉SIGINT信号(通常由Ctrl+C发送),可以在用户希望终止进程时进行一些清理工作,例如保存数据、关闭文件、释放资源等。
  • 进程间通信:信号可以用于进程间的简单通信,例如通过信号来触发另一个进程执行某个操作。
  • 定时任务:通过捕捉定时器信号(如SIGALRM),可以实现定时任务的功能,例如定时执行某个操作、定时检查某个状态等。

        总之,信号捕捉特性为进程提供了一种灵活的机制,可以在特定的时刻对信号进行处理,从而实现更多的功能和逻辑。但在使用信号捕捉特性时,需要注意处理函数的安全性和可靠性,以及避免滥用信号机制导致不可预料的问题。

内核实现信号捕捉的过程:

信号的注意事项

  1. 信号处理函数的安全性:信号处理函数应该是线程安全的,因为它可能会在任何时候被调用,甚至在关键代码段中断执行。因此,应避免在信号处理函数中使用不可重入函数、共享数据、非线程安全的库函数等。
  2. 信号处理函数的执行时间:由于信号处理函数会中断当前的执行流程,因此应尽量保持信号处理函数的执行时间尽可能短。长时间的处理函数可能会导致延迟和性能问题。
  3. 信号的排队和丢失:在某些情况下,多个相同类型的信号可能会在处理函数执行之前被合并为一个信号,这可能导致信号的丢失。为了避免信号丢失,可以使用信号屏蔽集或实时信号。
  4. 不可捕捉的信号:有一些信号是不可捕捉的,例如SIGKILL和SIGSTOP。这些信号的处理函数无法被设置或修改。
  5. 信号的默认处理方式:每个信号都有一个默认的处理方式,例如终止进程、忽略信号、产生核心转储等。在设置信号处理函数之前,应该了解信号的默认处理方式,并根据需要选择合适的处理方式。
  6. 信号的可重入性:某些信号处理函数是不可重入的,即不能在信号处理函数中再次调用该信号的处理函数。这是因为信号处理函数可能会被递归调用,导致不可预料的问题。为了避免这种情况,可以使用sigaction函数的SA_RESTART标志来避免信号中断系统调用。
  7. 信号的阻塞和解除阻塞:可以使用sigprocmask函数来设置信号屏蔽集,阻塞或解除阻塞指定的信号。这可以用于控制信号在某些关键代码段的中断执行。
  8. 信号的发送和接收:可以使用kill函数向指定的进程发送信号,也可以使用raise函数向自身发送信号。在接收信号时,可以使用signal函数或sigaction函数来设置信号处理函数。

        使用信号时需要注意处理函数的安全性、执行时间和可重入性,同时要了解信号的默认处理方式和不可捕捉的信号。合理地使用信号处理机制可以帮助我们实现一些重要的功能和逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值