Linux C 下信号屏蔽:保障稳定运行的重要手段 (linux c 信号屏蔽)

在 Linux C 程序开发的过程中,信号的处理是一个非常重要的话题。信号的正确处理能够保证程序的正常运行,也能帮助开发者找出程序中的一些潜在问题。在信号处理中,信号屏蔽是一个非常重要的概念。它能够保护程序在信号处理期间不被打断,从而保证程序的稳定运行。

一、信号的基本概念

在 Linux 系统中,信号是进程之间通信的一种手段。当一个进程需要通知另一个进程某个事件已经发生时,就可以通过发送信号的方式来通知。另外,信号也是操作系统向进程通知系统事件的一种方式。

当一个进程收到一个信号时,它需要对该信号进行处理。通常,进程可以选择忽略该信号,使用默认方式处理该信号,或者将信号交给处理程序进行处理。处理程序需要在接收到信号时执行一些指定的操作,例如关闭文件,释放资源等。

在 Linux C 中,信号可以通过调用 signal() 系列函数来设置。例如,可以通过下面的代码将 SIGINT 信号指向处理函数:

“`

#include

void handler(int sig)

{

printf(“Received signal %d\n”, sig);

}

int mn()

{

signal(SIGINT, handler);

printf(“Press Ctrl+C to send SIGINT signal\n”);

while(1);

return 0;

}

“`

该程序设置了 SIGINT 的处理程序为 handler() 函数。当程序运行时,如果用户按下 Ctrl+C,就会发送一个 SIGINT 信号,程序将会执行 handler() 函数。

二、信号的屏蔽

在信号处理期间,有些信号可能会中断当前任务的执行。为了确保程序的稳定运行,我们需要将一些信号屏蔽掉,这将会使它们在信号处理期间不会打断当前任务的执行。

在 Linux C 中,通过 sigprocmask() 函数来设置信号屏蔽。该函数的原型为:

“`

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

“`

该函数有三个参数:

1. how:表示信号屏蔽方式,可以是以下三种取值:

– SIG_BLOCK:将 set 指向信号集中的信号添加到进程的信号屏蔽字中;

– SIG_UNBLOCK:将 set 指向信号集中的信号从进程的信号屏蔽字中删除;

– SIG_SETMASK:用 set 指向信号集中的信号替换进程的信号屏蔽字;

2. set:指向要设置的信号;

3. oldset:用来保存之前屏蔽的信号的。

该函数的具体用法可以参考下面的例子:

“`

#include

int mn()

{

sigset_t mask;

sigemptyset(&mask);

sigaddset(&mask, SIGINT);

sigprocmask(SIG_BLOCK, &mask, NULL);

printf(“SIGINT signal is blocked.\n”);

while (1);

return 0;

}

“`

该程序将屏蔽 SIGINT 信号。运行该程序后,按下 Ctrl+C 并不会触发 SIGINT 信号的处理,因为该信号已经被屏蔽了。

三、信号的处理函数中的信号屏蔽

在信号处理函数中,除了需要对信号进行处理外,还需要考虑信号的屏蔽问题。一些信号,在信号处理期间可能会被再次触发。如果在信号处理函数中没有进行信号屏蔽,就可能会导致栈溢出和无限循环等问题。

解决该问题的方法是在信号处理函数的开头对信号进行屏蔽操作,在函数结束时取消屏蔽。这个过程可以通过 sigaction() 函数中的 sa_mask 字段来实现。例如:

“`

#include

void handler(int sig)

{

sigset_t mask;

sigemptyset(&mask);

sigaddset(&mask, sig);

sigprocmask(SIG_BLOCK, &mask, NULL);

printf(“Received signal %d\n”, sig);

sigprocmask(SIG_UNBLOCK, &mask, NULL);

}

int mn()

{

struct sigaction sa;

sigemptyset(&sa.sa_mask);

sigaddset(&sa.sa_mask, SIGINT);

sa.sa_flags = 0;

sa.sa_handler = handler;

sigaction(SIGINT, &sa, NULL);

while(1);

return 0;

}

“`

该程序中,handler() 函数在开头将 SIGINT 信号屏蔽,然后进行信号处理,最后再将 SIGINT 信号取消屏蔽。这样就可以保证在信号处理中不会再次收到该信号。

四、

相关问题拓展阅读:

linux 内核中断 用什么锁

首先我阐明一下,用锁的情况只有两种:

线程

文件

内核程序在使用的时候也脱离不了这两种锁的概念。

中断,是信号,是否要处理中断信号唤茄?或者产生中断信号仔核?

对信号来说只有:

信号屏蔽、信号捕捉、信号排队、可重如函数等概念。

你想问的念链掘问题,我没猜测,在处理某个信号时,不想让其他信号中断,那么使用信号屏蔽字:

先设置要屏蔽的信号集,要保存的信号集,初始信号集,可供协调使用的函数有几个:

#include

signal(这个不建议使用,应为有些老的实现是有问题的),设置信号处理程序

sig_atomic_t 数据类型

sigprocmask,设置信号屏蔽字

sigaction,设置信号处理程序,功能跟强悍,可控性更好

sigsuspend,以原子性方式,等待某些信号发生,然后返回

你具体要做啥不清楚,但使用上面的信号相关的函数,肯定能实现你的功能。参考APUE的论述。

LINUX软中断通信

我验证下阿…一不小心就fork多了..

刚开始我把kill的参数弄反了,信号和pid位置弄错了,调了半个小时,很郁闷..

你只是忽略了一点…,我也给忽略了。。。后来才想起来

你按下ctrl+C的时候,另外两个fork出来的进程,他们也会接到SIGINT。伍搭橡。。就退出了。。所以你要先在子进程里面忽略这个SIGINT信号,用枝或signal(SIGINT,SIG_IGN)就OK了….

程序如下…

有解释,你可以自己看腔旁看…

#include”stdio.h”

#include”unistd.h”

#include”signal.h”

#include”sys/types.h”

#include”stdlib.h”

int k=0;

pid_t child1=0,child2=0;

void func_main(int sig);

void func_child1(int sig);

void func_child2(int sig);

int main()

{

while((child1=fork())==-1);

if(child1==0)

{

printf(“child1 OK\n”);

signal(SIGINT,SIG_IGN);

signal(SIGUSR1,func_child1);

sleep(60);

}

else if(child1 >0)

{

while((child2=fork())==-1);

if(child2==0)

{

printf(“child 2 OK\n”);

signal(SIGINT,SIG_IGN);//你按下ctrl+C,子进程也会接受到ctrl的信号…所以,子进程忽略

//所提子进程要忽略掉这个SIGINT信号

signal(SIGUSR2,func_child2);

sleep(60); //这里为了验证,如果进程没退出,40妙之后自动会退出的

//不然就得手动在终端里面kill掉这个进程了…

//有时候成了僵尸进程需要kill -9 才能杀死

}

else if(child2 >0)

{

signal(SIGINT,func_main);

printf(“children forked OK…\n”);

wait(0);

printf(“child return…\n”);

sleep(100);

return 0;

}

}

}

void func_main(int sig)

{

k++;

printf(“to send signal\n”);

//printf(“child1=%d,child2=%d\n”,child1,child2);

//if(k==1)

kill(child1,SIGUSR1);

//if(k==2)加上这句,再按一次ctrl C,子进程2才会退出

就是你想要的效果了

kill(child2,SIGUSR2);

signal(SIGINT,SIG_DFL); //这里恢复ctrl+C的效果

//子进程退出之后,我们再按一次ctrl+C,当前的父进程就会像平常一样,退出。

}

void func_child1(int sig)

{

printf(“child1 is killed by parent!\n”);

exit(0);

}

void func_child2(int sig)

{

printf(“child2 is killed by parent!\n”);

exit(0);

}

我也是初学者,这里抄一段《Linux设备驱动程序》书上的给你:

Linux的中断宏观分为两种:软中断和硬中断。声明一橘启数下,这里的软和硬的意思是指和软件相关以及和硬件相关,而不是软件实现的中断或硬件实现的中断。软中断就是“信号机制”。软中断不是软件中断。Linux通过信号来产生对进程的各种中断操作,我们现在知道的信号共有31个,其具体内容这里略过。

一般来说,软中断是由内核机制的触发事件引起的(例如进程运行超时),但是不可忽视有大量的软中断也是由于和硬件有关的中断引起的,例如当打印机端口产生一个硬件中断时,会通知和硬件相关的硬中断,硬中断就会产生一个软中断并送到操作系统内核里,这样内核就会根据这个软中断唤醒睡眠在打印机任务队列中的处理进程。

硬中断就是通常意义上的“中断处理程序”,它是直接处理由硬件发过来的中断信号的。当硬中断收到它应当处理的中断信号以后,就回去自己驱动的设备上去看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。

对于软中断,我们不做讨论,那是进程调度里要考虑的事情。由于我们讨论的是设备驱动程序的中断问题,所以焦点集中在硬中断里。我们这里讨论的是硬中断,即和硬件相关的中断。

要中断,是因为外设需要通知操作系统她那里发生了一些事情,但是中断的功能仅仅是一个设备报警灯,当灯亮的时候中断处理程序只知道有事情发生了,但发生了什么事情还要亲自到设备那里去看才行。也就是说,当中断处理程序得知设备发生了一个中断的时候,它并不知道设备发生了什么事情,只有当它访问了设备上的一些状态寄存器以后,才能知道具体发生了什么,要怎么去处理。

设备通过中断线向中断控制器发送高电平告诉操作系统它产生了一个中断,而操作系统会从中断控制器的状态位知道是哪条中断线上产生了中断。PC机上使用的中断控制器是8259,这种控制器每一个可以管理8条中断线,当两个8259级联的时候共可以控制15条中断线。这里的中断线是实实在在的电路,他们通过硬件接口连接到CPU外的设备控制器上。

并不是每个设备都可以向中断线上发中断信号的,只有对某一条确定的中断线勇有了控制权,才可以向这条中断线上发送信号。由于计算机的外部设备越来越多,所以15条中断线已经不够用了,中断线是非常宝贵的资源。要使用中断线,就得进行中断线的申请,就是IRQ(Interrupt Requirement),我们也常把申请一条中断线成为申请一个IRQ或者是申请一个中圆首断号。

IRQ是非常宝贵的,所以我们建议只有当设备需要中断的时候才申请占用一个IRQ,或者是在申请IRQ时采用共享中断的方式,这样可以让更多的设备使用中断。无论对IRQ的使用方式是独占还是共享,申请IRQ的过程都是一样的,分为3步:

1.将所有的中断线探测一遍,看看哪些中断还没有被占用。从这些还没有被占用的中断中选一个作为该设备的IRQ。

2.通过中断申请函数申请选定的IRQ,这是要指定申请的方式是独占还是共享。

3.根据中断申请函数的返回值决定怎么做:如果成功了万事大吉,如果没成功则或者重新申请或者放弃申请并返回错误。

Linux中的中断处理程序很有特色,它的一个中断处理程序分为两个部分:上半部(top half)和下半部(bottom half)。之所以会有上半部和下半部之分,完全是考虑到中断处理的效率。

上半部的功能是“登记中断”。当一个中断发生时,他就把设备驱动程序中中断例程的下半部挂到该设备的下半部执行队列中去,然后就没事情了–等待新的中断的到来。这样旁薯一来,上半部执行的速度就会很快,他就可以接受更多她负责的设备产生的中断了。上半部之所以要快,是因为它是完全屏蔽中断的,如果她不执行完,其它的中断就不能被及时的处理,只能等到这个中断处理程序执行完毕以后。所以,要尽可能多得对设备产生的中断进行服务和处理,中断处理程序就一定要快。

但是,有些中断事件的处理是比较复杂的,所以中断处理程序必须多花一点时间才能够把事情做完。可怎么样化解在短时间内完成复杂处理的矛盾呢,这时候 Linux引入了下半部的概念。下半部和上半部更大的不同是下半部是可中断的,而上半部是不可中断的。下半部几乎做了中断处理程序所有的事情,因为上半部只是将下半部排到了他们所负责的设备的中断处理队列中去,然后就什么都不管了。下半部一般所负责的工作是察看设备以获得产生中断的事件信息,并根据这些信息(一般通过读设备上的寄存器得来)进行相应的处理。如果有些时间下半部不知道怎么去做,他就使用著名的鸵鸟算法来解决问题–说白了就是忽略这个事件。

由于下半部是可中断的,所以在它运行期间,如果其它的设备产生了中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完了,再回头来运行它。但是有一点一定要注意,那就是如果一个设备中断处理程序正在运行,无论她是运行上半部还是运行下半部,只要中断处理程序还没有处理完毕,在这期间设备产生的新的中断都将被忽略掉。因为中断处理程序是不可重入的,同一个中断处理程序是不能并行的。

在Linux Kernel 2.0以前,中断分为快中断和慢中断(伪中断我们这里不谈),其中快中断的下半部也是不可中断的,这样可以保证它执行的快一点。但是由于现在硬件水平不断上升,快中断和慢中断的运行速度已经没有什么差别了,所以为了提高中断例程事务处理的效率,从Linux kernel 2.0以后,中断处理程序全部都是慢中断的形式了–他们的下半部是可以被中断的。

但是,在下半部中,你也可以进行中断屏蔽–如果某一段代码不能被中断的话。你可以使用cti、sti或者是save_flag、restore_flag来实现你的想法。

在处理中断的时候,中断控制器会屏蔽掉原先发送中断的那个设备,直到她发送的上一个中断被处理完了为止。因此如果发送中断的那个设备载中断处理期间又发送了一个中断,那么这个中断就被永远的丢失了。

之所以发生这种事情,是因为中断控制器并不能缓冲中断信息,所以当前一个中断没有处理完以前又有新的中断到达,他肯定会丢掉新的中断的。但是这种缺陷可以通过设置主处理器(CPU)上的“置中断标志位”(sti)来解决,因为主处理器具有缓冲中断的功能。如果使用了“置中断标志位”,那么在处理完中断以后使用sti函数就可以使先前被屏蔽的中断得到服务。

有时候需要屏蔽中断,可是为什么要将这个中断屏蔽掉呢?这并不是因为技术上实现不了同一中断例程的并行,而是出于管理上的考虑。之所以在中断处理的过程中要屏蔽同一IRQ来的新中断,是因为中断处理程序是不可重入的,所以不能并行执行同一个中断处理程序。在这里我们举一个例子,从这里子例中可以看出如果一个中断处理程序是可以并行的话,那么很有可能会发生驱动程序锁死的情况。当驱动程序锁死的时候,你的操作系统并不一定会崩溃,但是锁死的驱动程序所支持的那个设备是不能再使用了–设备驱动程序死了,设备也就死了。

A是一段代码,B是操作设备寄存器R1的代码,C是操作设备寄存器R2的代码。其中激发PS1的事件会使A1产生一个中断,然后B1去读R1中已有的数据,然后代码C1向R2中写数据。而激发PS2的事件会使A2产生一个中断,然后B2删除R1中的数据,然后C2读去R2中的数据。

如果PS1先产生,且当他执行到A1和B1之间的时候,如果PS2产生了,这是A2会产生一个中断,将PS2中断掉(挂到任务队列的尾部),然后删除了 R1的内容。当PS2运行到C2时,由于C1还没有向R2中写数据,所以C2将会在这里被挂起,PS2就睡眠在代码C2上,直到有数据可读的时候被信号唤醒。这是由于PS1中的B2原先要读的R1中的数据被PS2中的B2删除了,所以PS1页会睡眠在B1上,直到有数据可读的时候被信号唤醒。这样一来,唤醒PS1和PS2的事件就永远不会发生了,因此PS1和PS2之间就锁死了。

由于设备驱动程序要和设备的寄存器打交道,所以很难写出可以重入的代码来,因为设备寄存器就是全局变量。因此,最简洁的办法就是禁止同一设备的中断处理程序并行,即设备的中断处理程序是不可重入的。

有一点一定要清楚:在2.0版本以后的Linux kernel中,所有的上半部都是不可中断的(上半部的操作是原子性的);不同设备的下半部可以互相中断,但一个特定的下半部不能被它自己所中断(即同一个下半部不能并)。

由于中断处理程序要求不可重入,所以程序员也不必为编写可重入的代码而头痛了。编写可重入的设备驱动程序是可以的,编写可重入的中断处理程序是非常难得,几乎不可能。

我们都知道,一旦竞争条件出现了,就有可能会发生死锁的情况,严重时可能会将整个系统锁死。所以一定要避免竞争条件的出现。只要注意一点:绝大多数由于中断产生的竞争条件,都是在带有中断的

内核进程被睡眠造成的。所以在实现中断的时候,一定要相信谨慎的让进程睡眠,必要的时候可以使用cli、sti或者save_flag、restore_flag。

linux c 信号屏蔽的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux c 信号屏蔽,Linux C 下信号屏蔽:保障稳定运行的重要手段,linux 内核中断 用什么锁,LINUX软中断通信的信息别忘了在本站进行查找喔。


数据运维技术 » Linux C 下信号屏蔽:保障稳定运行的重要手段 (linux c 信号屏蔽)