Linux信号
信号是一种软件中断,它提供了一种处理异步事件的方法,也是进程间唯一的异步通信方式。在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某种程序发生了什么事件,还可以给进程传递数据。
一、信号的来源:
两种方式:硬件方式和软件方式
二、哪些情况会引发信号/p>
1.键盘事件 ctrl+c ctrl+l
2.非法内存 如果内存管理出错,系统就会发送一个信号进行处理
3.硬件故障
4.环境切换:比如从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统。
三.信号种类
kill -l 查看信号的种类
信号的值定义在signal.h中,在Linux中没有16和32这两个信号。
其中编号34以上的是实时信号(可靠信号),34以下的是普通信号(不可靠信号)
通过案例观察信号
out.sh
chmod u+x out.sh
./out.sh
另开一个终端
查找进程
ps -ef | head -n 1 ; ps -ef | grep “out.sh” | grep -v “grep”
进程ID是2774
kill给进程发信号,9(SIGKILLL)信号是杀死进程
kill -9 2774
此时终端1中会看到killed,进程被杀死了
linux中的这64个信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明,在命令行输入man 7 signal:
四、可靠信号和不可靠信号
信号的可靠性是指信号是否会丢失,或者说该信号是否支持排除。SIGHUP(1)~SIGSYS(31)之间的信号都是继承自UNIX系统是不可靠信号,Linux系统根据POSIX标准定义了SIGRTMIN(33) ~ SIGRTMAX(64)之间的信号,它们都是可靠信号,也称为实时信号。
五、信号的处理
知道了什么是信号,那么为什么有信号,信号是谁发送给谁的呢,怎么发送/p>
信号的发送者很多,比如终端驱动程序,进程,系统。而接收者大多是一个进程。
进程收到此信号,其可选的处理动作有以下三种:
- 忽略此信号(大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略SIGKILL和SIGSTOP,因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的进程,显然是内核设计者不希望看到的场景)
- 执行该信号的默认处理动作(终止该信号)对于每个信号来说,系统都对应默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分处理方式都比较粗暴,就是直接杀死该进程。
- 提供一个信号处理函数(自定义动作),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。(即就是需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理)
六.信号处理函数
1.signal()
函数原型:
#include<signal.h>
void (*signal(int signum,void(*handler))(int)))(int);
第一个参数指定信号的值,第二个参数指定针对前面信号值得处理,可以忽略该信号(参数设为SIG_IGN);课以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)
SIG_GIN:忽略信号
SIG_DFL:恢复信号的默认行为
如果signal调用成功,返回最后一次为安装信号signum而调用signal()时的handler值,失败则返回SIG_ERR.
2.sigaction()
#include<signal.h>
int sigaction(int signum,const struct sigaction*act,struct sigaction *oldact));
sigaction函数用于改变进程接收到特定信号后的行为。
sigaction函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)第二个参数是指向结构sigaction的一个实例指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省的方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二个第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
3、发送信号函数
(1) int raise(int sig); 对当前进程发送指定信号
(2) int pause(void); 将进程挂起等待信号
(3) int kill(pid_t pid,int sig); 通过进程编号发送信号
(4) unsigned int alarm(unsigned int seconds); 指定时间(秒)发送SIGALRM信号。 seconds 为0 时取消所有已设置的alarm请求;
(5)int sigqueue(pid_t pid,int sig,const union sigval val);类似于kill函数,多了附带共用体 union sigval形数,将共用体中的成员 int sival_int 或 void *sival_ptr 的值传递给 信号处理函数中的定义类型 siginfo_t 中的 int si_int 或 void *si_ptr;
(6)int setitimer(int which,const struct itimerval *value,struct itimerval *oldvalue); 可定时发送信号,根据which可指定三种信号类型:SIGALRM、SIGVTALRM 和 SIGPROF;作用时间也因which值不同而不同;struct itimerval 的成员 it_interval定义间隔时间,it_value 为0时,使计时器失效;
(7) void abort(void) 将造成进程终止;除非捕获SIGABORT信号;
4. 信号集及信号集操作
sigfillset(sigset_t *set); 设置所有的信号到set信号集中;
sigemptyset(sigset_t *set); 从set信号集中清空所有信号;
sigaddset(sigset_t *set,int sig);在set信号集中加入sig信号;
sigdelset(sigset_t *set,int sig);在set信号集中删除sig信号;
5、阻塞信号相关函数
int sigprocmask(int how,const sigset_t *set,sigset_t *set); 根据how值,设置阻塞信号集,或释放阻塞的信号集
int sigpending(sigset_t *set); 获取在阻塞中的所有信号;
int sigsuspend(const sigset_t *set); 类似于 pause()函数!
七、信号处理函数的过程
(1)注册信号处理函数
信号的处理是由内核来代理的,首先程序通过sigal或sigaction函数为每个信号注册处理函数,而内核中维护一张信号向量表,对应信号处理机制。这样,在信号在进程中注销完毕之后,会调用相应的处理函数进行处理。
(2)信号的检测与响应时机
在系统调用或中断返回用户态的前夕,内核会检查未决信号集,进行相应的信号处理。
(3)处理过程:
程序运行在用户态时->进程由于系统调用或中断进入内核->转向用户态执行信号处理函数->信号处理函数完毕后进入内核->返回用户态继续执行程序
首先程序执行在用户态,在进程陷入内核并从内核返回的前夕,会去检查有没有信号没有被处理,如果有且没有被阻塞就会调用相应的信号处理程序去处理。首先,内核在用户栈上创建一个层,该层中将返回地址设置成信号处理函数的地址,这样,从内核返回用户态时,就会执行这个信号处理函数。当信号处理函数执行完,会再次进入内核,主要是检测有没有信号没有处理,以及恢复原先程序中断执行点,恢复内核栈等工作,这样,当从内核返回后便返回到原先程序执行的地方了。
参考图:
文章知识点与官方知识档案匹配,可进一步学习相关知识CS入门技能树Linux入门初识Linux24720 人正在系统学习中
来源:凤鸣九天_
声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!