阻塞信号

本文介绍了如何使用信号阻塞函数sigprocmask()控制信号的传递,以避免信号打断敏感操作。文中详细解释了信号集的概念及其操作函数,并通过实例展示了如何在关键代码段禁用特定信号。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      信号的阻塞就是让系统暂时保留信号待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

     

      当需要修改某些全局变量时,可以通过sigprocmask()函数阻塞处理函数中也使用该变量的信号。

      在某些信号处理函数中,为了防止同类信号的到来,可以使用sigaction()函数的sa_mask阻塞特定的信号。

 

阻塞信号的作用

      使用函数sigprocmask()阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。这种情况往往需要在信号程序和其它程序共享全局变量时,如果全局变量的类型不是sig_atomic_t类型,当一部分程序恰好读、写到变量过程中,产生某个信号,而信号程序里会改变该变量,那么就会产生混乱。为了避免这种混乱,提供程序的可靠性,你必须在操作这类变量前阻塞信号,操作完成后恢复信号的传递。

 

信号集

      所有的信号阻塞函数都使用称作信号集的数据结构来表明受到影响的信号。每一个操作都包括两个阶段:创建信号集,传递信号集给特定的库函数。下面说明信号集和相关的数据类型:
      sigset_t:这个数据类型用来代表信号的集合,有两种方法对它进行初始化。一种是通过函数sigemptyset()使之不包含任何信号,然后用sigaddset()函数加入需要的信号。另一种方法是通过函数sigfillset()使之包含所以信号,然后通过sigdelset()函数删除我们不需要的信号。注意,千万不用试图通过手工方式直接操作这种类型变量,否则会带来严重的错误。下面介绍相关的函数。
      int sigemptyset(sigset_t *set):初始化信号集set使之不包含任何信号,这个函数总是返回0。
      int sigfillset(sigset_t *set):初始化信号集set使之包含所有的信号,这个函数也是总返回0。
      int sigaddset(sigset_t *set, int signum):该函数把信号signum加入到信号集set中,需要注意的是这个函数只是修改了set变量本身,并不作其它操作。该函数成功操作返回0,失败返回-1,错误代码设置成EINVAL,表示signum不是有效的信号代码。
      int sigdelset(sigset_t *set, int signum):该函数从信号集set中删除信号signum,其它方面和sigaddset()函数类似,不再赘述。
      int sigismember(const sigset_t *set,int signum):这个函数测试信号signum是否包含在信号集合set中,如果包含返回1,不包含返回0,出错返回-1。错误代码也只有一个EINVAL,表示signum不是有效的信号代码。

进程的信号掩码

      我们称正在阻塞的信号的集合为信号掩码(signal mask)。每个进程都有自己的信号掩码,创建子进程时子进程将继承父进程的信号掩码。我们可以通过修改当前的信号掩码来改变信号的阻塞情况。
      int sigprocmask(int how, const sigset_t *set,sigset_t *oldset),该函数用来检查和改变调用进程的信号掩码,其中的how参数指出信号掩码改变的方式,必须是下面的值之一:
      SIG_BLOCK,阻塞set中包含的信号。意思是说把set中的信号加到当前的信号掩码中去,新的信号掩码是set和旧信号掩码的并集。
      SIG_UNBLOCK,解除set中信号的阻塞,从当前信号掩码中去除set中的信号。
      SIG_SETMASK,设置信号掩码,既按照set中的信号重新设置信号掩码。

       最后一个参数是进程原来的信号集。如果你只需要改变信号的阻塞情况而不需要关心原来的值,可以传递NULL指针给函数。如果你希望什么也不改变,只是想获得当前信号掩码的信息,那么把set设置成NULL,old中返回当前的设置。
      sigprocmask()函数成功返回0,失败返回-1。失败时错误代码只可能是EINVAL,表示参数how不合法。

      不能阻塞SIGKILL和SIGSTOP等信号,但是当set参数包含这些信号时sigprocmask()不返回错误,只是忽略它们。另外,阻塞SIGFPE这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。
  

举例:禁止关键代码时信号到达

      假定你建立信号SIGALRM的处理函数,在其中设置一个标志。主程序中检查标志并清除,使用函数sigprocmask()控制信号到达:
   #include
   volatile sig_atomic_t flag=0;
  
   int
   main(void)
   {
       sigset_t block_alarm;
       ... ...
       sigemptyset(&block_alarm);
       sigaddset(&block_alarm,SIGALRM);
       while(1)
       {
           sigprocmask(SIG_BLOCK,&block_alarm,NULL);
           if(flag)
           {
               ... ...
               flag=0;
           }
           sigprocmask(SIG_UNBLOCK,&block_alarm,NULL);
           ... ...
       }
   }

     

在 C 语言中,非阻塞信号量的使用主要依赖于 `sem_trywait()` 函数。与 `sem_wait()` 不同,`sem_trywait()` 不会阻塞线程以等待信号量变为可用状态;如果信号量当前不可用(即计数器为 0),则函数立即返回错误码 `EAGAIN`。 ### 非阻塞信号量的基本使用方法 #### 初始化信号量 使用 `sem_init()` 初始化一个信号量,其原型如下: ```c int sem_init(sem_t *sem, int pshared, unsigned int value); ``` - `sem`:指向信号量对象的指针。 - `pshared`:若为 0,则信号量用于线程间同步;若为非零值,则可用于进程间同步。 - `value`:信号量的初始值,表示资源的数量[^1]。 #### 非阻塞获取信号量 通过 `sem_trywait()` 尝试获取信号量而不阻塞: ```c int sem_trywait(sem_t *sem); ``` - 如果信号量计数器大于 0,则该函数将计数器减 1 并返回 0,表示成功获取信号量。 - 如果信号量计数器为 0,则该函数不会阻塞,而是立即返回 -1,并设置 `errno` 为 `EAGAIN`。 #### 释放信号量 使用 `sem_post()` 增加信号量计数器,表示释放一个资源: ```c int sem_post(sem_t *sem); ``` 该函数通常由生产者调用,以通知消费者有新的资源可用。 --- ### 示例代码:非阻塞信号量的使用 以下是一个简单的示例,演示了如何使用 `sem_trywait()` 实现非阻塞信号量: ```c #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <errno.h> #include <unistd.h> #define NUM_THREADS 5 sem_t semaphore; void* thread_func(void* arg) { int id = *(int*)arg; // 尝试获取信号量 if (sem_trywait(&semaphore) == 0) { printf("Thread %d acquired the semaphore.\n", id); sleep(1); // 模拟工作 printf("Thread %d releasing the semaphore.\n", id); sem_post(&semaphore); } else { if (errno == EAGAIN) { printf("Thread %d could not acquire the semaphore. Resource busy.\n", id); } else { perror("sem_trywait"); } } return NULL; } int main() { pthread_t threads[NUM_THREADS]; int ids[NUM_THREADS]; // 初始化信号量,初始值为 2 sem_init(&semaphore, 0, 2); // 创建多个线程 for (int i = 0; i < NUM_THREADS; ++i) { ids[i] = i + 1; pthread_create(&threads[i], NULL, thread_func, &ids[i]); } // 等待所有线程完成 for (int i = 0; i < NUM_THREADS; ++i) { pthread_join(threads[i], NULL); } // 销毁信号量 sem_destroy(&semaphore); return 0; } ``` 在这个示例中,我们创建了 5 个线程,而信号量的初始值为 2,意味着最多有两个线程可以同时访问共享资源。其他线程在尝试获取信号量时将因资源不足而失败,并输出提示信息。 --- ### 注意事项 - **信号量的销毁**:在程序结束前,应使用 `sem_destroy()` 销毁不再需要的信号量,以释放相关资源。 - **线程安全**:`sem_trywait()` 是线程安全的,可以在多线程环境中直接使用。 - **适用场景**:非阻塞信号量适用于那些不能长时间等待资源的应用场景,例如实时系统或响应时间敏感的任务调度。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值