首页 > 自考资讯 > 高考百科

Linux一篇文章彻底搞定信号(linux 信号编程)

小条 2024-10-30

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 号信号,信号可能不会丢失

adb621c1bd2c4ade9abf73f2a4f2041e~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=UF%2BAeJ9mIutxRIv%2FnVIIRx1ByvU%3D

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 队列。

ef6d80b996224508b12429b549b2ab8d~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=J%2Baq2nxuXeuSZRMQA1T3uBYDLxE%3D

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.信号阻塞

723f4afd58f24631af9cfc97f8554b38~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=KpowE7rxl84QU%2FMVnr1muyMG%2B5M%3D

阻止信号不会阻止信号注册。信号可以注册,但不能立即处理。将块位图中对应的信号位位置设置为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 杀死它。

80c34118402f4a9783f55730200d0d2d~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=06Wh8QIzIs76SIbpSBD128Bvz%2Bo%3D

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); } 结果:

999d316324e34c89aa8e95aef7bc22e0~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=5mD3Db6eTvYITOv%2BAtAe3muhZHM%3D8。

1d65c56ba16943c2bfe4f8c5b85fe049~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=BkGiSdB2esjcbPiu4TxWD%2FKM4yg%3D

每个信号都有两个标志位来指示阻塞和挂起,以及一个函数指针来指示处理动作。在上面的例子中:

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);

cfe1cfa54f924f8fa4885ec12cb332e0~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=WYg1SjL1WeX6ziJ%2F%2F5MaYZZH1FE%3D8.3 自定义信号处理流程

3da07d6d3d0a431a99be2f2f0e2f4df9~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=7uf1KgcKSzNDVGefu%2BHnwZru4%2Fc%3D

task_struct 结构体内部是一个structighand_struct 结构体。 structighand_struct 结构有一个**struct k_sigaction action[_NSIG]** 结构数组。在此数组中, **_sighandler_t sa_handler** 存储信号处理方法,您可以通过更改其指针来处理自定义信号。

8.2sigaction函数

9.信号的捕捉

如果信号处理动作是用户自定义函数,则在信号下发时调用该函数。这称为信号捕获。

9.1信号捕捉的条件

a64606a519db47b89fb9c28048577f22~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=%2B96WuqmpHOOHMlNNVR7SXR1Fcd0%3D

当内核模式返回用户模式时,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 |

b1aeea00dfc74f98becd2f50d7d90f33~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1730868042&x-signature=BsX6UZdSjUo%2BG%2BQ8ahWduumqcQ8%3D

原文地址:https://blog.csdn.net/w903414/article/details/109802539?utm_source=appapp_version=4.18.0utm_source=app

版权声明:本文转载于网络,版权归作者所有。如有侵权,请联系本站编辑删除。

猜你喜欢