欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training
目录
- 👉🏻信号递达,信号未决,信号阻塞
- block表 ,pending表 ,handler表
- sigset_t类型
- 👉🏻信号集操作函数
- 常见信号集操作函数
- sigaction函数
- sigprocmask函数
- sigpending函数
👉🏻信号递达,信号未决,信号阻塞
在操作系统中,进程之间可以通过信号进行通信。当一个进程向另一个进程发送信号时,会出现以下几种情况:信号递达、信号未决和信号阻塞。
-
信号递达(Signal Delivery):当一个进程向目标进程发送信号时,操作系统会将该信号递送给目标进程。递达的信号会触发目标进程相应的信号处理函数(signal handler)执行,或者引起默认的信号处理行为。这意味着信号已经成功传递到了目标进程。
-
信号未决(Signal Pending):如果目标进程正在处理一个信号,而此时又有一个相同的信号递达给它,那么这个信号就会被标记为未决状态。未决信号是一种被记录下来但
尚未被处理
的信号。 -
信号阻塞(Signal Blocking):进程可以选择性地阻塞某些信号,使得它们在阻塞状态下不会递达到该进程。当信号被阻塞时,即使有该信号的递达请求,进程也不会接收到该信号。当解除对信号的阻塞时,之前被阻塞的信号会立即递达到进程。
信号被阻塞——>信号一定是未决状态
block表 ,pending表 ,handler表
在内核中,信号是一种异步事件,可能随时被发送给进程,并且进程需要及时响应。为了管理信号的处理过程,内核维护了三个重要的数据结构:block表、pending表和handler表。
-
block表:用于记录被阻塞的信号集合。当一个进程调用sigprocmask函数时,它可以指定一组要阻塞的信号。这些信号会被放入该进程对应的block表中,使得它们在阻塞状态下不会递达到该进程。当进程需要解除对某个信号的阻塞时,可以再次调用sigprocmask函数来修改block表。
-
pending表:用于记录未决的信号集合。当一个信号递达到目标进程时,如果目标进程正在处理另外一个相同的信号,那么这个信号就会被标记为未决状态,放入该进程对应的pending表中。当当前信号处理完毕后,内核会检查pending表中是否有未决的信号,如果有,则将它们取出并递达到进程。
-
handler表:用于记录每种信号对应的信号处理函数。当一个信号递达到目标进程时,内核会根据该信号对应的handler表中的处理函数来执行相应的操作。如果进程没有安装该信号的处理函数,则会执行默认的信号处理行为。
这些表都是在进程控制块(PCB)中维护的,并且可以通过一些系统调用和库函数来修改和查询。例如,sigprocmask函数可以修改block表,sigpending函数可以查询pending表,signal和sigaction函数可以修改handler表。
sigset_t类型
sigset_t
是一个数据类型,用于表示一组信号的集合。在 POSIX 标准中,sigset_t
被定义为一个整数数组。
使用 sigset_t
可以方便地管理进程的信号屏蔽字和未决信号集合。进程的信号屏蔽字是一个 sigset_t
类型的变量,它用于标记哪些信号是被阻塞的。当一个信号被阻塞时,即使该信号有递达请求,也不会发送给进程。我们可以使用 sigprocmask
系统调用来修改进程的信号屏蔽字(信号屏蔽集合)。
另外,sigset_t
还可以用于查询进程的未决信号集合。未决信号集合记录了已经递达到进程,但尚未被处理的信号。我们可以使用 sigpending
系统调用来查询未决信号集合。该函数会将未决信号集合写入 sigset_t
类型的变量中返回。
在使用 sigset_t
时,我们可以使用一些辅助函数来对信号集合进行操作。例如:
sigemptyset
:将一个信号集合清空,即将该集合中所有信号都设置为未包含状态。sigfillset
:将一个信号集合填充满,即将该集合中所有信号都设置为包含状态。sigaddset
:将一个信号添加到信号集合中。sigdelset
:将一个信号从信号集合中删除。
这些辅助函数可以方便我们对信号集合进行操作,从而更好地管理进程的信号处理。
👉🏻信号集操作函数
常见信号集操作函数
当使用 sigset_t
表示信号集合时,可以使用以下函数进行信号集合的操作:
-
int sigemptyset(sigset_t *set)
:
该函数用于清空信号集合,将所有信号都设置为未包含状态(比特位清0)。它会将set
指向的信号集合清空,成功返回0,失败返回-1。 -
int sigfillset(sigset_t *set)
:
该函数用于填充信号集合,将所有信号都设置为包含状态(比特位置为1)。它会将set
指向的信号集合填充满,成功返回0,失败返回-1。 -
int sigaddset(sigset_t *set, int signum)
:
该函数用于将指定的信号添加到信号集合中。set
是要操作的信号集合的指针,signum
是要添加的信号编号。成功返回0,失败返回-1。 -
int sigdelset(sigset_t *set, int signum)
:
该函数用于从信号集合中删除指定的信号。set
是要操作的信号集合的指针,signum
是要删除的信号编号。成功返回0,失败返回-1。 -
int sigismember(const sigset_t *set, int signum)
:
该函数用于检查指定的信号是否在信号集合中。set
是要检查的信号集合的指针,signum
是要检查的信号编号。如果信号在信号集合中,返回1;如果信号不在信号集合中,返回0;如果发生错误,返回-1。
这些函数可以帮助我们方便地对信号集合进行操作,例如创建空的信号集合、将所有信号添加到信号集合中、从信号集合中删除特定的信号、检查信号是否在信号集合中等。
🌧 以下是每个函数的用法示例:
int sigemptyset(sigset_t *set)
:
该函数用于清空信号集合,将所有信号都设置为未包含状态。
#include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set); // 清空信号集合
return 0;
}
int sigfillset(sigset_t *set)
:
该函数用于填充信号集合,将所有信号都设置为包含状态。
#include <signal.h>
int main() {
sigset_t set;
sigfillset(&set); // 填充信号集合
return 0;
}
int sigaddset(sigset_t *set, int signum)
:
该函数用于将指定的信号添加到信号集合中。
#include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set); // 清空信号集合
sigaddset(&set, SIGINT); // 将 SIGINT 信号添加到信号集合中
return 0;
}
int sigdelset(sigset_t *set, int signum)
:
该函数用于从信号集合中删除指定的信号。
#include <signal.h>
int main() {
sigset_t set;
sigfillset(&set); // 填充信号集合
sigdelset(&set, SIGINT); // 从信号集合中删除 SIGINT 信号
return 0;
}
int sigismember(const sigset_t *set, int signum)
:
该函数用于检查指定的信号是否在信号集合中。
#include <signal.h>
#include <stdio.h>
int main() {
sigset_t set;
sigemptyset(&set); // 清空信号集合
sigaddset(&set, SIGINT); // 将 SIGINT 信号添加到信号集合中
if (sigismember(&set, SIGINT)) {
printf("SIGINT is a member of the signal set\n");
} else {
printf("SIGINT is not a member of the signal set\n");
}
return 0;
}
sigaction函数
sigaction函数是用于设置和修改信号处理函数的系统调用。通过调用该函数,可以为特定的信号注册一个信号处理函数,并指定信号处理的行为。
sigaction函数的原型如下:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数说明:
- signum:表示要注册的信号。
- act:一个指向struct sigaction类型的指针,用于指定新的信号处理函数以及信号处理的行为。
- oldact:一个指向struct sigaction类型的指针,用于保存之前的信号处理函数以及信号处理的行为。
函数返回值为0表示成功,返回-1表示出错。
下面是一个简单的示例程序,演示了如何使用sigaction函数:
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("Received signal %d\n", sig);
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
sigaction(SIGINT, &sa, NULL);
while(1) {}
return 0;
}
在这个示例程序中,首先定义了一个信号处理函数handler来处理 SIGINT 信号。然后创建了一个struct sigaction类型的结构体sa,并将其中的sa_handler成员设置为handler,其他成员设置为默认值。接着调用sigaction函数,为 SIGINT 信号注册了信号处理函数sa。
最后程序进入一个无限循环,等待 SIGINT 信号的到来。当进程收到 SIGINT 信号时,会调用事先注册的信号处理函数handler来处理该信号,并打印一些信息。
通过使用sigaction函数,可以为特定的信号注册一个信号处理函数,并指定信号处理的行为。与signal函数相比,sigaction函数提供了更多的灵活性和可靠性。
sigprocmask函数
sigprocmask函数是一个用于设置和修改进程信号屏蔽字的系统调用。通过调用该函数,可以控制进程对特定信号的处理方式。
sigprocmask函数的原型如下:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数说明:
-
how:表示如何修改信号屏蔽字的方式,可以取以下值:
- SIG_BLOCK:将set中的信号添加到当前进程的信号屏蔽字中,即阻塞这些信号。(oldset原有的基础上添加新的信号屏蔽字)
- SIG_UNBLOCK:从当前进程的信号屏蔽字中移除set中的信号,即解除对这些信号的阻塞。
- SIG_SETMASK:将当前进程的信号屏蔽字设置为set中的值,即使用set中的信号屏蔽字替换当前进程的信号屏蔽字。
-
set:一个指向sigset_t类型的指针,用于指定要修改的信号集合。
-
oldset:一个指向sigset_t类型的指针,用于保存之前的信号屏蔽字。
函数返回值为0表示成功,返回-1表示出错。
下面是一个简单的示例程序,演示了如何使用sigprocmask函数:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
printf("Received signal %d\n", sig);
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
sigaction(SIGINT, &sa, NULL);
printf("Blocking SIGINT...\n");
// 阻塞 SIGINT 信号
sigset_t set, oldset;
sigemptyset(&set);
sigemptyset(&oldset);//先都清空
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, &oldset);
printf("SIGINT is blocked. Sleeping for 10 seconds...\n");
sleep(10);
printf("Unblocking SIGINT...\n");
// 解除对 SIGINT 信号的阻塞
sigprocmask(SIG_SETMASK, &oldset, NULL);
printf("SIGINT is unblocked. Sleeping for 10 seconds...\n");
sleep(10);
return 0;
}
在这个示例程序中,首先注册了一个信号处理函数handler来处理 SIGINT 信号。然后调用sigprocmask函数,将 SIGINT 信号添加到进程的信号屏蔽字中,即阻塞 SIGINT 信号。之后程序会打印一些信息,并休眠10秒钟。在这段时间内,如果进程收到 SIGINT 信号,则该信号会被暂时挂起,直到解除对该信号的阻塞。
接着,程序调用sigprocmask函数解除对 SIGINT 信号的阻塞,再次打印一些信息并休眠10秒钟。在这段时间内,如果进程收到 SIGINT 信号,则会调用事先注册的信号处理函数handler来处理该信号。
sigpending函数
sigpending函数用于获取当前进程未决的信号集,即已经发送但尚未被进程处理的信号集。
sigpending函数的原型如下:
int sigpending(sigset_t *set);
参数说明:
- set:一个指向sigset_t类型的指针,用于保存未决信号集。
函数返回值为0表示成功,返回-1表示出错。
下面是一个简单的示例程序,演示了如何使用sigpending函数:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
printf("Received signal %d\n", sig);
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
sigaction(SIGUSR1, &sa, NULL);
// 发送 SIGUSR1 信号
printf("Sending SIGUSR1...\n");
kill(getpid(), SIGUSR1);
// 检查未决信号集
sigset_t set;
sigpending(&set);
if (sigismember(&set, SIGUSR1)) {
printf("SIGUSR1 is pending\n");
} else {
printf("SIGUSR1 is not pending\n");
}
return 0;
}
在这个示例程序中,首先定义了一个信号处理函数handler来处理 SIGUSR1 信号。然后创建了一个struct sigaction类型的结构体sa,并将其中的sa_handler成员设置为handler,其他成员设置为默认值。接着调用sigaction函数,为 SIGUSR1 信号注册了信号处理函数sa。
然后程序通过kill函数向当前进程发送了 SIGUSR1 信号。接着调用sigpending函数,获取当前进程的未决信号集,并将结果保存在set中。然后使用sigismember函数判断 SIGUSR1 是否在未决信号集中,如果是,则打印相应的信息。
通过使用sigpending函数,可以获取当前进程未决的信号集,即已经发送但尚未被进程处理的信号集。这对于需要了解当前进程是否收到某个信号以及是否处理了该信号非常有用。