热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux信号处理全面解析(第六篇)

本文深入探讨了信号及其来源。信号本质上是对中断机制的软件层面模拟,从原理上看,进程接收到信号与处理器接收到中断请求类似。信号具有异步特性,能够在进程执行过程中随时触发,从而中断当前操作并执行相应的处理程序。文章详细分析了信号的生成、传递和处理机制,并讨论了常见的信号类型及其应用场景。此外,还介绍了如何在Linux系统中使用信号进行进程间通信和错误处理,为开发者提供了实用的技术指导。

一、信号及信号来源

信号本质

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

信号来源

信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

回页首

二、信号的种类

可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通信(一):管道及有名管道》的附1中列出了系统所支持的所有信号。

1、可靠信号与不可靠信号

"不可靠信号"

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:

  • 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
  • 信号可能丢失,后面将对此详细阐述。 
    因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。

Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。

"可靠信号"

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。

信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。

对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

2、实时信号与非实时信号

早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

回页首

三、进程对信号的响应

进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。

Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。

回页首

四、信号的发送

发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

1、kill() 
#include  
#include  
int kill(pid_t pid,int signo)

参数pid的值信号的接收进程
pid>0进程ID为pid的进程
pid=0同一个进程组的进程
pid<0 pid!&#61;-1进程组ID为 -pid的所有进程
pid&#61;-1除发送进程自身外&#xff0c;所有进程ID大于1的进程

Sinno是信号值&#xff0c;当为0时&#xff08;即空信号&#xff09;&#xff0c;实际不发送任何信号&#xff0c;但照常进行错误检查&#xff0c;因此&#xff0c;可用于检查目标进程是否存在&#xff0c;以及当前进程是否具有向目标发送信号的权限&#xff08;root权限的进程可以向任何进程发送信号&#xff0c;非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号&#xff09;。

Kill()最常用于pid>0时的信号发送&#xff0c;调用成功返回 0&#xff1b; 否则&#xff0c;返回 -1。 注&#xff1a;对于pid<0时的情况&#xff0c;对于哪些进程将接受信号&#xff0c;各种版本说法不一&#xff0c;其实很简单&#xff0c;参阅内核源码kernal/signal.c即可&#xff0c;上表中的规则是参考red hat 7.2。

2、raise&#xff08;&#xff09; 
#include  
int raise(int signo) 
向进程本身发送信号&#xff0c;参数为即将发送的信号值。调用成功返回 0&#xff1b;否则&#xff0c;返回 -1。

3、sigqueue&#xff08;&#xff09; 
#include  
#include  
int sigqueue(pid_t pid, int sig, const union sigval val) 
调用成功返回 0&#xff1b;否则&#xff0c;返回 -1。

sigqueue()是比较新的发送信号系统调用&#xff0c;主要是针对实时信号提出的&#xff08;当然也支持前32种&#xff09;&#xff0c;支持信号带有参数&#xff0c;与函数sigaction()配合使用。

sigqueue的第一个参数是指定接收信号的进程ID&#xff0c;第二个参数确定即将发送的信号&#xff0c;第三个参数是一个联合数据结构union sigval&#xff0c;指定了信号传递的参数&#xff0c;即通常所说的4字节值。

typedef union sigval {int sival_int;void *sival_ptr;}sigval_t;

sigqueue()比kill()传递了更多的附加信息&#xff0c;但sigqueue()只能向一个进程发送信号&#xff0c;而不能发送信号给一个进程组。如果signo&#61;0&#xff0c;将会执行错误检查&#xff0c;但实际上不发送任何信号&#xff0c;0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

在调用sigqueue时&#xff0c;sigval_t指定的信息会拷贝到3参数信号处理函数&#xff08;3参数信号处理函数指的是信号处理函数由sigaction安装&#xff0c;并设定了sa_sigaction指针&#xff0c;稍后将阐述&#xff09;的siginfo_t结构中&#xff0c;这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号&#xff0c;所以比kill()系统调用的功能要灵活和强大得多。

注&#xff1a;sigqueue&#xff08;&#xff09;发送非实时信号时&#xff0c;第三个参数包含的信息仍然能够传递给信号处理函数&#xff1b; sigqueue&#xff08;&#xff09;发送非实时信号时&#xff0c;仍然不支持排队&#xff0c;即在信号处理函数执行过程中到来的所有相同信号&#xff0c;都被合并为一个信号。

4、alarm&#xff08;&#xff09; 
#include  
unsigned int alarm(unsigned int seconds) 
专门为SIGALRM信号而设&#xff0c;在指定的时间seconds秒后&#xff0c;将向进程本身发送SIGALRM信号&#xff0c;又称为闹钟时间。进程调用alarm后&#xff0c;任何以前的alarm()调用都将无效。如果参数seconds为零&#xff0c;那么进程内将不再包含任何闹钟时间。 
返回值&#xff0c;如果调用alarm&#xff08;&#xff09;前&#xff0c;进程中已经设置了闹钟时间&#xff0c;则返回上一个闹钟时间的剩余时间&#xff0c;否则返回0。

5、setitimer&#xff08;&#xff09; 
#include  
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); 
setitimer()比alarm功能强大&#xff0c;支持3种类型的定时器&#xff1a;

  • ITIMER_REAL&#xff1a; 设定绝对时间&#xff1b;经过指定的时间后&#xff0c;内核将发送SIGALRM信号给本进程&#xff1b;
  • ITIMER_VIRTUAL 设定程序执行时间&#xff1b;经过指定的时间后&#xff0c;内核将发送SIGVTALRM信号给本进程&#xff1b;
  • ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和&#xff0c;经过指定的时间后&#xff0c;内核将发送ITIMER_VIRTUAL信号给本进程&#xff1b;

Setitimer()第一个参数which指定定时器类型&#xff08;上面三种之一&#xff09;&#xff1b;第二个参数是结构itimerval的一个实例&#xff0c;结构itimerval形式见附录1。第三个参数可不做处理。

Setitimer()调用成功返回0&#xff0c;否则返回-1。

6、abort() 
#include  
void abort(void);

向进程发送SIGABORT信号&#xff0c;默认情况下进程会异常退出&#xff0c;当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号&#xff0c;调用abort()后&#xff0c;SIGABORT仍然能被进程接收。该函数无返回值。

回页首

五、信号的安装&#xff08;设置信号关联动作&#xff09;

如果进程要处理某一信号&#xff0c;那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系&#xff0c;即进程将要处理哪个信号&#xff1b;该信号被传递给进程时&#xff0c;将执行何种操作。

linux主要有两个函数实现信号的安装&#xff1a;signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数&#xff0c;不支持信号传递信息&#xff0c;主要是用于前32种非实时信号的安装&#xff1b;而sigaction()是较新的函数&#xff08;由两个系统调用实现&#xff1a;sys_signal以及sys_rt_sigaction&#xff09;&#xff0c;有三个参数&#xff0c;支持信号传递信息&#xff0c;主要用来与 sigqueue() 系统调用配合使用&#xff0c;当然&#xff0c;sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。

1、signal() 
#include  
void (*signal(int signum, void (*handler))(int)))(int); 
如果该函数原型不容易理解的话&#xff0c;可以参考下面的分解方式来理解&#xff1a; 
typedef void (*sighandler_t)(int)&#xff1b; 
sighandler_t signal(int signum, sighandler_t handler)); 
第一个参数指定信号的值&#xff0c;第二个参数指定针对前面信号值的处理&#xff0c;可以忽略该信号&#xff08;参数设为SIG_IGN&#xff09;&#xff1b;可以采用系统默认方式处理信号(参数设为SIG_DFL)&#xff1b;也可以自己实现处理方式(参数指定一个函数地址)。 
如果signal()调用成功&#xff0c;返回最后一次为安装信号signum而调用signal()时的handler值&#xff1b;失败则返回SIG_ERR。

2、sigaction() 
#include  
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值&#xff0c;可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号&#xff08;为这两个信号定义自己的处理函数&#xff0c;将导致信号安装错误&#xff09;。第二个参数是指向结构sigaction的一个实例的指针&#xff0c;在结构sigaction的实例中&#xff0c;指定了对特定信号的处理&#xff0c;可以为空&#xff0c;进程会以缺省方式对信号处理&#xff1b;第三个参数oldact指向的对象用来保存原来对相应信号的处理&#xff0c;可指定oldact为NULL。如果把第二、第三个参数都设为NULL&#xff0c;那么该函数可用于检查信号的有效性。

第二个参数最为重要&#xff0c;其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

sigaction结构定义如下&#xff1a;

struct sigaction {union{__sighandler_t _sa_handler;void (*_sa_sigaction)(int,struct siginfo *, void *)&#xff1b;}_u
sigset_t sa_mask&#xff1b;unsigned long sa_flags&#xff1b; }

1、联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数&#xff0c;即用户指定的信号处理函数。除了可以是用户自定义的处理函数外&#xff0c;还可以为SIG_DFL(采用缺省的处理方式)&#xff0c;也可以为SIG_IGN&#xff08;忽略信号&#xff09;。

2、由_sa_handler指定的处理函数只有一个参数&#xff0c;即信号值&#xff0c;所以信号不能传递除信号值之外的任何信息&#xff1b;由_sa_sigaction是指定的信号处理函数带有三个参数&#xff0c;是为实时信号而设的&#xff08;当然同样支持非实时信号&#xff09;&#xff0c;它指定一个3参数信号处理函数。第一个参数为信号值&#xff0c;第三个参数没有使用&#xff08;posix没有规范使用该参数的标准&#xff09;&#xff0c;第二个参数是指向siginfo_t结构的指针&#xff0c;结构中包含信号携带的数据值&#xff0c;参数所指向的结构如下&#xff1a;

  siginfo_t {int si_signo; /* 信号值&#xff0c;对所有信号有意义*/int si_errno; /* errno值&#xff0c;对所有信号有意义*/int si_code; /* 信号产生的原因&#xff0c;对所有信号有意义*/pid_t si_pid; /* 发送信号的进程ID,对kill(2),实时信号以及SIGCHLD有意义 */uid_t si_uid; /* 发送信号进程的真实用户ID&#xff0c;对kill(2),实时信号以及SIGCHLD有意义 */int si_status; /* 退出状态&#xff0c;对SIGCHLD有意义*/clock_t si_utime; /* 用户消耗的时间&#xff0c;对SIGCHLD有意义 */clock_t si_stime; /* 内核消耗的时间&#xff0c;对SIGCHLD有意义 */sigval_t si_value; /* 信号值&#xff0c;对所有实时有意义&#xff0c;是一个联合数据结构&#xff0c;/*可以为一个整数&#xff08;由si_int标示&#xff0c;也可以为一个指针&#xff0c;由si_ptr标示&#xff09;*/void * si_addr; /* 触发fault的内存地址&#xff0c;对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/int si_band; /* 对SIGPOLL信号有意义 */int si_fd; /* 对SIGPOLL信号有意义 */}

注&#xff1a;为了更便于阅读&#xff0c;在说明问题时常把该结构表示为附录2所表示的形式。

siginfo_t结构中的联合数据成员确保该结构适应所有的信号&#xff0c;比如对于实时信号来说&#xff0c;则实际采用下面的结构形式&#xff1a;

typedef struct {int si_signo;int si_errno; int si_code; union sigval si_value; } siginfo_t;

结构的第四个域同样为一个联合数据结构&#xff1a;

union sigval {int sival_int; void *sival_ptr; }

采用联合数据结构&#xff0c;说明siginfo_t结构中的si_value要么持有一个4字节的整数值&#xff0c;要么持有一个指针&#xff0c;这就构成了与信号相关的数据。在信号的处理函数中&#xff0c;包含这样的信号相关数据指针&#xff0c;但没有规定具体如何对这些数据进行操作&#xff0c;操作方法应该由程序开发人员根据具体任务事先约定。

前面在讨论系统调用sigqueue发送信号时&#xff0c;sigqueue的第三个参数就是sigval联合数据结构&#xff0c;当调用sigqueue时&#xff0c;该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样&#xff0c;在发送信号同时&#xff0c;就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

信号参数的传递过程可图示如下&#xff1a;

3、sa_mask指定在信号处理程序执行过程中&#xff0c;哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞&#xff0c;防止信号的嵌套发送&#xff0c;除非指定SA_NODEFER或者SA_NOMASK标志位。

注&#xff1a;请注意sa_mask指定的信号阻塞的前提条件&#xff0c;是在由sigaction&#xff08;&#xff09;安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

4、sa_flags中包含了许多标志位&#xff0c;包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO&#xff0c;当设定了该标志位时&#xff0c;表示信号附带的参数可以被传递到信号处理函数中&#xff0c;因此&#xff0c;应该为sigaction结构中的sa_sigaction指定处理函数&#xff0c;而不应该为sa_handler指定信号处理函数&#xff0c;否则&#xff0c;设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数&#xff0c;如果不设置SA_SIGINFO&#xff0c;信号处理函数同样不能得到信号传递过来的数据&#xff0c;在信号处理函数中对这些信息的访问都将导致段错误&#xff08;Segmentation fault&#xff09;。

注&#xff1a;很多文献在阐述该标志位时都认为&#xff0c;如果设置了该标志位&#xff0c;就必须定义三参数信号处理函数。实际不是这样的&#xff0c;验证方法很简单&#xff1a;自己实现一个单一参数信号处理函数&#xff0c;并在程序中设置该标志位&#xff0c;可以察看程序的运行结果。实际上&#xff0c;可以把该标志位看成信号是否传递参数的开关&#xff0c;如果设置该位&#xff0c;则传递参数&#xff1b;否则&#xff0c;不传递参数。

回页首

六、信号集及信号集操作函数&#xff1a;

信号集被定义为一种数据类型&#xff1a;

typedef struct {unsigned long sig[_NSIG_WORDS]&#xff1b;} sigset_t

信号集用来描述信号的集合&#xff0c;linux所支持的所有信号可以全部或部分的出现在信号集中&#xff0c;主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数&#xff1a;

#include
int sigemptyset(sigset_t *set)&#xff1b;
int sigfillset(sigset_t *set)&#xff1b;
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum)&#xff1b;
int sigismember(const sigset_t *set, int signum)&#xff1b;
sigemptyset(sigset_t *set)初始化由set指定的信号集&#xff0c;信号集里面的所有信号被清空&#xff1b;
sigfillset(sigset_t *set)调用该函数后&#xff0c;set指向的信号集中将包含linux支持的64种信号&#xff1b;
sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号&#xff1b;
sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号&#xff1b;
sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

回页首

七、信号阻塞与信号未决:

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集&#xff0c;该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数&#xff1a;

#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset))&#xff1b;
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask))&#xff1b;

sigprocmask()函数能够根据参数how来实现对信号集的操作&#xff0c;操作主要有三种&#xff1a;

参数how进程当前信号集
SIG_BLOCK在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK如果进程阻塞信号集中包含set指向信号集中的信号&#xff0c;则解除对该信号的阻塞
SIG_SETMASK更新进程阻塞信号集为set指向的信号集

sigpending(sigset_t *set))获得当前已递送到进程&#xff0c;却被阻塞的所有信号&#xff0c;在set指向的信号集中返回结果。

sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行&#xff0c;直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后&#xff0c;进程将继续执行。该系统调用始终返回-1&#xff0c;并将errno设置为EINTR。

附录1&#xff1a;结构itimerval&#xff1a;

struct itimerval {struct timeval it_interval; /* next value */struct timeval it_value; /* current value */};struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};

附录2&#xff1a;三参数信号处理函数中第二个参数的说明性描述&#xff1a;

siginfo_t {
int si_signo; /* 信号值&#xff0c;对所有信号有意义*/
int si_errno; /* errno值&#xff0c;对所有信号有意义*/
int si_code; /* 信号产生的原因&#xff0c;对所有信号有意义*/
pid_t si_pid; /* 发送信号的进程ID,对kill(2),实时信号以及SIGCHLD有意义 */
uid_t si_uid; /* 发送信号进程的真实用户ID&#xff0c;对kill(2),实时信号以及SIGCHLD有意义 */
int si_status; /* 退出状态&#xff0c;对SIGCHLD有意义*/
clock_t si_utime; /* 用户消耗的时间&#xff0c;对SIGCHLD有意义 */
clock_t si_stime; /* 内核消耗的时间&#xff0c;对SIGCHLD有意义 */
sigval_t si_value; /* 信号值&#xff0c;对所有实时有意义&#xff0c;是一个联合数据结构&#xff0c;/*可以为一个整数&#xff08;由si_int标示&#xff0c;也可以为一个指针&#xff0c;由si_ptr标示&#xff09;*/void * si_addr; /* 触发fault的内存地址&#xff0c;对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/
int si_band; /* 对SIGPOLL信号有意义 */
int si_fd; /* 对SIGPOLL信号有意义 */
}

实际上&#xff0c;除了前三个元素外&#xff0c;其他元素组织在一个联合结构中&#xff0c;在联合数据结构中&#xff0c;又根据不同的信号组织成不同的结构。注释中提到的对某种信号有意义指的是&#xff0c;在该信号的处理函数中可以访问这些域来获得与信号相关的有意义的信息&#xff0c;只不过特定信号只对特定信息感兴趣而已。

转:https://www.cnblogs.com/jiangzhaowei/p/4126448.html



推荐阅读
  • 深入RTOS实践,面对原子操作提问竟感困惑
    在实时操作系统(RTOS)的实践中,尽管已经积累了丰富的经验,但在面对原子操作的具体问题时,仍感到困惑。本文将深入探讨RTOS中的原子操作机制,分析其在多任务环境下的重要性和实现方式,并结合实际案例解析常见的问题及解决方案,帮助读者更好地理解和应用这一关键技术。 ... [详细]
  • 掌握DSP必备的56个核心问题,我已经将其收藏以备不时之需! ... [详细]
  • 在操作系统中,阻塞状态与挂起状态有着显著的区别。阻塞状态通常是指进程因等待某一事件(如I/O操作完成)而暂时停止执行,而挂起状态则是指进程被系统暂时移出内存,以释放资源或降低系统负载。此外,本文还深入分析了`sleep()`函数的实现机制,探讨了其在不同操作系统中的具体实现方式及其对进程调度的影响。通过这些分析,读者可以更好地理解操作系统如何管理进程的不同状态以及`sleep()`函数在其中的作用。 ... [详细]
  • BZOJ4240 Gym 102082G:贪心算法与树状数组的综合应用
    BZOJ4240 Gym 102082G 题目 "有趣的家庭菜园" 结合了贪心算法和树状数组的应用,旨在解决在有限时间和内存限制下高效处理复杂数据结构的问题。通过巧妙地运用贪心策略和树状数组,该题目能够在 10 秒的时间限制和 256MB 的内存限制内,有效处理大量输入数据,实现高性能的解决方案。提交次数为 756 次,成功解决次数为 349 次,体现了该题目的挑战性和实际应用价值。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • Linux磁盘管理入门指南:MBR分区格式详解与安装步骤
    在 CentOS 7.x 环境下,本文详细介绍了 MBR 分区格式的基本概念及其安装步骤。实验中使用了 SAS 和 SATA 硬盘,其中 SAS 硬盘主要用于企业级应用和服务器,而 SATA 硬盘则广泛应用于个人计算机和低端服务器。文章通过具体操作示例,帮助读者更好地理解和掌握 Linux 磁盘管理的基本技能。 ... [详细]
  • 提升工作效率:掌握这些技巧,IDEA 使用效率翻倍 | IDEA 高效操作指南
    提升工作效率:掌握这些技巧,IDEA 使用效率翻倍 | IDEA 高效操作指南 ... [详细]
  • 在幼儿园中,有 \( n \) 个小朋友需要通过投票来决定是否午睡。尽管这个问题对每个孩子来说并不是特别重要,但他们仍然希望通过谦让的方式达成一致。每个人都有自己的偏好,但为了集体和谐,他们决定采用一种最小割的方法来解决这一问题。这种方法不仅能够确保每个人的意愿得到尽可能多的尊重,还能找到一个最优的解决方案,使整体满意度最大化。 ... [详细]
  • 本研究基于状态空间方法,通过动态可视化技术实现了汉诺塔问题的求解过程,即将n个盘子从A柱移动到C柱。本文提供了一个使用C语言在控制台进行动画绘制的示例,并详细注释了程序逻辑,以帮助读者更好地理解和学习该算法。 ... [详细]
  • 本文探讨了将PEBuilder转换为DIBooter.sh的方法,重点介绍了如何将DI工具集成到启动层,实现离线镜像引导安装。通过使用DD命令替代传统的grub-install工具,实现了GRUB的离线安装。此外,还详细解析了bootice工具的工作原理及其在该过程中的应用,确保系统在无网络环境下也能顺利引导和安装。 ... [详细]
  • PJSIP 编译与开发指南:深入解析 PJSIP 库的应用与优化
    PJSIP 编译与开发指南:深入解析 PJSIP 库的应用与优化 ... [详细]
  • 本文深入探讨了 CF570D 问题中树的请求处理方法,重点分析了长链剖分技术的应用与优化。题目涉及一棵包含 n 个节点的树,每个节点上有一个字符。每次查询时,需要处理某个节点 x 的相关请求。通过长链剖分技术,可以高效地解决这类问题,显著提升算法性能。本文不仅介绍了基本的长链剖分原理,还详细讨论了其在具体实现中的优化技巧,为解决类似问题提供了宝贵的参考。 ... [详细]
  • Android开发常见问题汇总(含Gradle解决方案)第二篇
    本文继续深入探讨Android开发中常见的问题及其解决方案,特别聚焦于Gradle相关的挑战。通过详细分析和实例演示,帮助开发者高效解决构建过程中的各种难题,提升开发效率和项目稳定性。 ... [详细]
  • 基于STM32的智能太阳能路灯设计与华为云IOT集成方案
    基于STM32的智能太阳能路灯设计与华为云IOT集成方案 ... [详细]
  • 对于以压缩包形式发布的软件,其目录中通常包含一个配置脚本 `configure`。该脚本的主要功能是确定编译所需的各项参数,如头文件的位置和链接库的路径,并生成相应的 `Makefile` 以供编译使用。通过运行此脚本,开发者可以确保软件在不同环境下的正确编译与安装。此外,该脚本还能够检测系统依赖项,进一步提高编译过程的可靠性和兼容性。 ... [详细]
author-avatar
mobiledu2502872687
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有