1.信号是什么?
信号实际上是一个软件中断。
例子:
通过键入以下命令在shell 下启动前台进程:当用户按下Ctrl-C 时,键盘输入会生成硬件中断。如果CPU当前正在执行该进程的代码,则该进程的用户空间代码将被挂起,并且CPU从用户模式切换到内核模式以服务硬件中断。终端驱动程序将Ctrl-C 解释为SIGINT 信号并将其记录在进程的PCB 中(也可以说向进程发送了SIGINT 信号)。在某个时刻,当内核返回到进程的用户空间代码继续执行时,它首先处理PCB中记录的信号,发现有SIGINT信号需要处理,并默认处理。该信号的作用是终止进程,因此进程直接终止,不会返回任何用户空间代码执行。在这个例子中,信号是ctrl+c产生的硬件中断。使用Ctrl+C 生成的信号只能发送到前台进程。您可以在命令后面添加它以在后台运行它。 shell 可以同时运行一个前台进程和任意数量的后台进程。只有前台进程才能接收CTRL+C 等控制键生成的信号。
2.信号的种类
使用命令查看:
kill -l 不可靠信号:1 到31 号信号,信号可能丢失可靠信号:34 到64 号信号,信号可能不会丢失
SIGHUP:信号编号1,在控制终端上检测到挂起或控制进程终止(在控制终端上挂起信号或终止进程),ation:术语
SIGINT:2号信号,来自键盘的中断(键盘输入中断,ctrl+c),动作:term
SIGQUIT:3号信号,从键盘退出(键盘输入,ctrl+|退出),动作:core,生成core dump文件
SIGABRT:6号信号,来自abort(3)的abort信号(异常终止,双重释放),动作:核心
SIGKILL:9号信号,Kill Signal(杀死进程信号),动作:Term,该信号不能被阻止、忽略或自定义。
SIGSEGV:11号信号,无效内存引用(无效内存引用、取消引用的NULL指针、内存越界访问),动作:core
SIGPIPE:信号13,损坏的管道: 在没有读取器的情况下写入管道(管道中止: 写入无人读取的管道会导致管道损坏),操作:术语
SIGCHLD:信号号17,子进程停止或终止(信号从子进程发送到父进程,信号被忽略)
SIGSTOP:19 号信号,进程停止,操作:停止
SIGTSTP:信号号20,终端上的停止类型(在终端上发出停止信号,ctrl + z),动作:停止
信号执行的具体动作以及更多信息可以在man 7 signal 中找到。
3.信号的产生
3.1硬件产生
硬件生成的信号由终端按键生成。
ctrl + c:将SIGINT(2) 发送到前台进程并在后台运行该进程。 fg 会将刚刚放入后台的进程放到前台并运行它。 ctrl + z:SIGTSTP(20),除非有特定场景,通常不使用。 Ctrl + |:SIGQUIT(3),生成核心转储文件。 生成核心转储文件的条件:
现代操作系统需要无限的核心转储文件大小。 ulimit - 所需的磁盘空间。 如何生成: 3.1 取消引用空指针并接收信号#11 以生成核心转储文件。当程序崩溃时,会收到11 号信号并生成核心转储文件。 3.3 接收到6 号信号并生成核心转储。 3.4 自由(NULL),不会崩溃
3.2软件产生
当软件生成时,它调用系统函数向进程发送信号。
Kill 函数#include sys/types.h#include signal.hint Kill(pid_t pid, int sig); 参数说明: pid:进程号sig:要发送的信号值返回值:成功返回0,- 返回1。失败时,设置错误终止命令:kill -[signal] pid,中止:void abort(void);收到信号号6,该函数的调用者收到信号警报:unsigned int Alarm(unsigned int Seconds);接收信号号14 告诉内核在几秒钟后向进程发送SIGALRM 信号。该信号的默认处理操作是终止当前进程。
4.信号的注册
信号注册分为可信信号注册和不可信信号注册。 信号注册实际上是位图和sigqueue 队列。
4.1非可靠信号的注册
如果进程收到不可靠信号:
将一个sigqueue 节点添加到sigqueue 队列中,并将不可靠信号对应的位位置设置为1。但是在添加sigqueue节点时,队列中已经有该信号的sigqueue节点,所以没有添加。当进程收到可靠信号时:
将sig位图中信号对应的位改为1。将sigqueue 节点添加回sigqueue 队列,无论sigqueue 队列中之前是否存在该信号的sigqueue 节点。
4.2可靠信号的注册
5.信号的注销
通过将信号对应的位从1 设置为0,使信号的sigqueue 节点从sigqueue 队列中出列。
5.1非可靠信号的注销
要将信号sigqueue 节点从sigqueue 队列中出队,需要确定sigqueue 队列中是否仍存在相同的sigqueue 节点。 不再:信号位从1 设为0。 另外:sig 位图中的位没有改变
5.2可靠信号的注销
6.信号阻塞
阻止信号不会阻止信号注册。信号可以注册,但不能立即处理。将块位图中对应的信号位位置设置为1。当信号被阻塞并且进程接收到信号时,这将继续被注册。如果已经返回内核空间并准备返回用户空间,则调用do_signal 函数时不会立即处理该信号。如果信号没有被阻塞,就可以被处理。
6.1信号是怎样阻塞的?
函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
如何以及做什么SIG_BLOCK:将信号设置为阻塞。 SIG_UNBLOCK:解除阻塞信号。 SIG_SETMASK:替换阻塞位图。 set:用于设置阻塞位图。 SIG_BLOCK:将信号设置为阻塞。 block(新)=block(旧)| setSIG_UNBLOCK:解锁信号。 block(新)=block(旧)(~set) SIG_SETMASK:替换阻塞位图。 block (new)=setoldset: 原始块位图。所有信号都被屏蔽。阻塞。使用kill -9 终止进程。
#include stdio.h#include signal.h#include unistd.hvoidsigncallback(int signnumber){ printf('signal %d\n',signnumber);}int main(){ sigset_t set);//所有位时;设置为1,所有信号都被阻止sigprocmask(SIG_BLOCK,set,oldset); while(1) { sleep(1) } return 0;} 结果:此时发送的信号不起作用。使用kill -9 杀死它。
6.2sigprocmask
7.信号未决
信号处理动作的实际执行称为信号传递,信号从产生到传递的状态称为信号挂起。 进程可以选择阻止信号。阻塞的信号在生成时保持待决状态,并且在进程解除阻塞信号之前不会采取传递操作。请注意,阻止和忽略之间是有区别的。信号只要被阻塞就不会被传递,但忽略是传递后可选的处理动作。
7.1 未决概念
函数原型:int sigpending(sigset_t *set); 读取当前进程的待处理信号集,并通过set参数发送。此调用成功时返回0,出错时返回-1。
例子:
#include stdio.h#include unistd.h#include signal.hvoid signalcallback(intsignnumber){ printf('changsignnumber%d\n',signnumber);}void printsigset(sigset_t *set){ int i=0;i 32;i++) { if(sigismember(set,i)) { putchar('1'); } else{ putchar('0') } }}int main(){ signal(2,signalcallback); 10,signalcallback); sigset_t oldset(set);//如果所有位都设置为1,则所有信号都被阻塞sigprocmask(SIG_BLOCK,set,oldset); printsigset(pending); } 结果:
8。
每个信号都有两个标志位来指示阻塞和挂起,以及一个函数指针来指示处理动作。在上面的例子中:
SIGHUP 信号既不会被阻止,也不会生成,并且在传递时执行默认的处理操作。 SIGINT信号已产生,但被阻塞,暂时无法下发。虽然处理动作被忽略,但进程仍然有机会改变处理动作并解除阻塞,因此在解除阻塞之前不能忽略该信号。从未生成SIGQUIT 信号。当生成SIGQUIT 信号时,其处理动作会被用户定义的函数singhandler 阻止。
7.2 sigpending
此功能允许您更改信号处理操作。
typedef void (*sighandler_t)(int);sighandler_t signal(intsignum,ighandler_t handler);参数说明:signum:改变的信号值handler:函数指针,改变的动作是什么?其实sigaction内部也会被调用。这个函数的作用。
8.1signal函数
读取和修改与指定信号关联的处理操作。
int sigaction(intsignum, const struct sigaction *act, struct sigaction *oldact);
Signum:改变struct sigaction结构的信号值:
void (*sa_handler)(int);//函数指针存放内核的信号处理方法void (*sa_sigaction)(int, siginfo_t *, void *);//sigset_t sa_mask;//函数指针存放内核的信号处理处理信号时,进程保存接收到的信号int sa_flags;//SA_SIGINFO。当操作系统处理信号时,它会调用//sa_sigaction 函数指针中存储的值0。处理信号时,调用sa_handler()保存的函数void。 *sa_restorer)(void);示例:
#include stdio.h#include unistd.h#include signal.hvoidsigncallback(intsignnumber){ printf('changesignnumber%d\n',signnumber);}int main(){ struct sigaction act;//act为输入参数sigemptyset(act.sa_mask); act.sa_flags=0; act.sa_handler=struct sigaction oldact;//oldact 返回参数sigaction(1) { sleep(1);
8.3 自定义信号处理流程
task_struct 结构体内部是一个structighand_struct 结构体。 structighand_struct 结构有一个**struct k_sigaction action[_NSIG]** 结构数组。在此数组中, **_sighandler_t sa_handler** 存储信号处理方法,您可以通过更改其指针来处理自定义信号。
8.2sigaction函数
9.信号的捕捉
如果信号处理动作是用户自定义函数,则在信号下发时调用该函数。这称为信号捕获。
9.1信号捕捉的条件
当内核模式返回用户模式时,do_signal函数被调用。有两种情况:
无信号:sys_return函数,返回用户态。 带信号:先处理信号,返回信号,然后调用do_signal函数。 示例:程序注册SIGQUIT信号处理函数singhandler。当main函数正在执行时,发生中断或异常,系统切换到内核态。中断处理完毕后,返回用户态主函数之前,会检查信号SIGQUIT是否已发送。返回用户态后,内核决定继续执行,不恢复main函数的上下文,但是ighhandler和main函数是独立于调用和被调用的。两个独立的控制过程。在ighandler函数返回后,会自动执行一个特殊的系统调用sigreturn以重新进入内核态。如果没有新信号要传递,则主函数的上下文将恢复,并在返回用户模式时继续执行。
9.2信号捕捉流程
int sigemptyset(sigset_t *set); //设置所有位图为0int sigfillset(sigset_t *set);//设置所有位图为1int sigaddset(sigset_t *set, intsignum);//设置位图为1int sigdelset(sigset_t *set, intsignum);//设置配置位图的信号编号为0int sigismember(const sigset_t *set, intsignum);//信号的signum是
10.常用信号集操作函数
中的信号还是set位图?最终由子进程发送给父进程,但信号是默认处理的。 如果父进程忽略子进程发送的SIGCHLD信号,则子进程将成为僵尸进程。
您可以自定义该信号的处理方式。
#include stdio.h#include unistd.h#include signal.h#include string.h#include sys/wait.h#include stdlib.hvoidsigncallback(int signalnumber){ printf('更改信号%d\n',signnumber ) ; wait(NULL);}int main(){ signal(17,signcallback); if(pid 0) { pererror('fork') };=0) { printf('我是孩子\n'); 12); } else{ while(1) { sleep(1) } } return 0;} 显示背景的命令: ps aux |
原文地址:https://blog.csdn.net/w903414/article/details/109802539?utm_source=appapp_version=4.18.0utm_source=app
版权声明:本文转载于网络,版权归作者所有。如有侵权,请联系本站编辑删除。