首页 > 系统相关 >Linux进程信号(二)

Linux进程信号(二)

时间:2024-09-22 12:50:38浏览次数:3  
标签:set sigset int 递达 阻塞 信号 Linux 进程

前文简单介绍了Linux中的信号产生和信号捕捉的初步认识,这一篇文章我们将进一步了解Linux信号中的阻塞信号,并深入理解信号捕捉的具体过程。

阻塞信号

概念解释

在介绍阻塞信号之前,我们需要了解一些信号相关的概念:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生带递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞(Block)某个信号
  • 被阻塞的信号产生时将保持未决状态,直到进程解除对此信号的阻塞,才执行递达动作
  • 递达和忽略(Ignore)是不同的概念,只要信号被阻塞就不会被递达,而忽略是递达之后可选的一种处理动作

信号在Linux内核中的表示

image.png 从上图中可以看到,信号的表示是在对应的进程task_struct结构下的,每个信号都由两个位图信息和一个处理函数组成,第一个位图信息用于标识信号是否被阻塞,第二个位图信息用于标识信号是否处于未决状态,而处理函数则由一个函数指针数组组成,下标就是对应的信号的处理函数;

sigset_t(信号集)

由上图可以发现,阻塞信号和递达信号的标识结构都是类似的,所以可以使用同一种数据结构(sigset_t)表示它们:<br>

sigset_t

是一个数据类型,用于表示一个信号集合。它通常用位图(bitmask)来表示多个信号,每个位代表一个信号的状态(存在或不存在),常用在信号的阻塞、等待等场景中,来表示哪些信号已经阻塞,哪些信号正在等待处理。

typedef struct {
    unsigned long sig[__SIGSET_WORDS];
} sigset_t;
  • 用途:
    • 管理信号阻塞集(signal mask),表示哪些信号被阻塞。
    • 挂起信号集(pending signals),表示哪些信号已经发送给进程但尚未处理。 这里,阻塞信号集也被称为当前进程的信号屏蔽字(Signal Mask);

信号集操作函数

虽然上述的演示说明信号集是通过对应位置上的bit位的有无标识信号的有效或无效状态的,但是其内部的具体实现则更为复杂,因此不可通过简单的位操作进行更改; 为此,信号库提供了一系列的操作函数用于对信号集进行处理:

#include <signal.h>
int sigemptyset(sigset_t* set); // 初始化set指向的信号集,使所有bit清零,表示该信号集不包含任何有效信号;
int sigfillset(sigset_t* set);  // 初始化set指向的信号集,使所有bit置位,表示该信号集包含所有支持的信号;
int sigaddset(sigset_t* set, int signo); // 在set指向的信号集中向指定位置写入信号;
int sigdelset(sigset_t* set, int signo); // 在set指向的信号集中删除指定位置的信号;
int sigismember(const sigset_t* set, int signo);

注意,在使用sigset_t类型的变量之前,一定要调用sigemptysetsigfillset做初始化,是信号集处于确定的状态;

四个函数函数的返回值都是成功返回 0,失败返回 -1;sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,包含返回 1,不包含返回 0,出错返回 -1;

sigprocmask

该函数用于读取或更改进程的信号屏蔽字(阻塞信号集)

#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);

解释一下这里的参数含义:

  1. 如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出;
  2. 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改;
  3. 如果上述二者均为非空指针,则先将原来的信号屏蔽字备份到oldset中,然后根据set和how参数更改信号屏蔽字;

下面以mask作为当前信号屏蔽字为例,说明how参数的可选值:

参数可选值 操作
SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask = mask\|set
SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask&~set
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于maks = set

注意,如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmaks返回前,至少将其中一个信号递达;

sigpending

#include <signal.h>
int sigpending(sigset_t* set);

读取当前进程的未决信号集,通过set参数传出;成功返回 0,失败返回 -1, 下面进行实验演示:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <vector>

// 定义最大处理信号个数
#define NUM_SIG 32
// int sigemptyset(sigset_t *set);
// int sigfillset(sigset_t *set);
// int sigaddset(sigset_t *set, int signum);
// int sigdelset(sigset_t *set, int signum);
// int sigismember(const sigset_t *set, int signum);

// int sigprocmask(int how, const sigset_t* set, sigset_t* oset);
// int sigpending(sigset_t* set);


std::vector<int> sig_vec = {2, 3};

void print_sig(const sigset_t& set){
  for(int i = NUM_SIG; i >= 1; --i){
    // 检查信号是否存在于当前信号集
    if(sigismember(&set, i)) std::cout << '1';
    else std::cout << '0';
  }
  std::cout << std::endl;
}

void handle(int signo){
  std::cout << "信号编号:" << signo << " has been called." << std::endl;
}

void set_signal(sigset_t set, sigset_t out_set){
  // 设置
  for(const auto& it : sig_vec) sigaddset(&set, it);
  for(const auto& it : sig_vec) signal(it, handle);
  sigprocmask(SIG_SETMASK, &set, &out_set); // 设置阻塞信号
}

int main(){
  sigset_t set, out_set;

  // 清空信号
  sigemptyset(&set);
  sigemptyset(&out_set);
  
  // 设置阻塞信号
  // 设置自定义处理函数
  set_signal(set, out_set);

  // for(const auto& it : sig_vec) sigaddset(&set, it);
  // for(const auto& it : sig_vec) signal(it, handle);
  // sigprocmask(SIG_SETMASK, &set, &out_set); // 设置阻塞信号

  int cnt = 10;
  int ans = 0;
  while(1){
    sigpending(&set);
    print_sig(set);
    if(!(cnt--)){
      sigprocmask(SIG_SETMASK, &out_set, &set); // 清除阻塞信号
      // 再次设置阻塞信号,用于下一次循环的正常进行
      set_signal(set, out_set);
      cnt = 10;
      ans++;
      if(ans == 3) kill(getpid(), 9);
      std::cout << std::endl << "ans = " << ans << std::endl;
    }
    else std::cout << std::endl << "cnt = " << cnt << std::endl;
    sleep(1);
  }
  return 0;
}

上述代码演示了通过signal提供的库函数对信号集内数据进行操作,实现了信号的阻塞、传递、递达等操作,至此我们便完成了Linux内核中有关信号的大部分操作,下一篇文章我将重点介绍Linux中的信号捕捉问题以及一种特殊的信号; 上面的代码可以在我的GitHub主页看到,位于signal文件夹中;

标签:set,sigset,int,递达,阻塞,信号,Linux,进程
From: https://blog.51cto.com/u_16271511/12080164

相关文章

  • Linux 7 主机名修改、查看
    使用hostname命令修改主机名,它修改是transient主机名,即临时生效的主机名。直接修改/etc/hostname文件,它瞬时生效,重启后也生效(因为内核会根据它初始化transient主机名)。使用nmtui命令在图形化界面修改主机名。它会直接修改/etc/hostname文件,因此也是瞬时生效+永久生效的。使......
  • linux运维之用户管理 堡垒机+普通用户sudo提权
    一、简介普通用户通过堡垒机登录linux服务器,不允许使用root用户登录堡垒机;普通用户登录后,限制普通用户使用一些命令,如禁止使用某些命令,仅允许使用部分命令等;普通用户不知道root密码,普通用户不能修改root密码;二、sudoers配置0.添加一个测试用户#添加用户useraddalibaby#设置密码......
  • Linux文件IO(七)-复制文件描述符
    在Linux系统中,open返回得到的文件描述符fd可以进行复制,复制成功之后可以得到一个新的文件描述符,使用新的文件描述符和旧的文件描述符都可以对文件进行IO操作,复制得到的文件描述符和旧的文件描述符拥有相同的权限,譬如使用旧的文件描述符对文件有读写权限,那么新的文件描述......
  • Linux文件IO(八)-文件共享
    什么是文件共享?所谓文件共享指的是同一个文件(譬如磁盘上的同一个文件,对应同一个inode)被多个独立的读写体同时进行IO操作。多个独立的读写体大家可以将其简单地理解为对应于同一个文件的多个不同的文件描述符,譬如多次打开同一个文件所得到的多个不同的fd,或使用dup()(或dup2......
  • 【信号传输】DMA传输只能收到一半数据,发送123456 只能收到 123, 发送abcd只能收到ab,缓
    系列文章目录1.元件基础2.电路设计3.PCB设计4.元件焊接5.板子调试6.程序设计7.算法学习8.编写exe9.检测标准10.项目举例11.职业规划文章目录方案一、改DMA中断方案二、改数据类型方案三、改数据长度后记方案一、改DMA中断每个DMA通道都可以在DMA传......
  • 操作系统:线程间通信方式(下)——信号量机制 (Semaphore) 与信号机制 (Signal)
    操作系统:线程间通信方式(下)——信号量机制(Semaphore)与信号机制(Signal)在多线程编程中,线程间的通信与同步至关重要。信号量机制(Semaphore)和信号机制(Signal)是两种常见且重要的线程间通信方式,它们各自解决不同场景下的线程控制问题。本文将详细介绍信号量和信号的基本概......
  • 进程空间管理:用户态和内核态
    用户态虚拟空间里面有几类数据,例如代码、全局变量、堆、栈、内存映射区等。在structmm_struct里面,有下面这些变量定义了这些区域的统计信息和位置。unsignedlongmmap_base;/*baseofmmaparea*/unsignedlongtotal_vm;/*Totalpagesmapped*/unsignedlonglock......
  • QT信号与槽机制
    写在开头:我们可以去这个网站进行学习C++的相关知识:https://github.com/0voice目录信号与槽机制连接方式信号与槽机制连接方式一个信号可以跟另一个信号相连:connect(object1,SIGNAL(signal1),object2,SIGNAL(signal1));作用:这里object1发出signal1信号时,会自......
  • 在Linux下安装MySQL
    摘要在学习MySQL语法之前,我们需要先解决在Ubuntu或CentOs环境下的“软件安装”的问题。本文梳理了安装前后的各个步骤及有关的注意事项,主要涵盖了安装前的准备工作、如何安装mysql,以及安装之后如何启动、如何正式使用这几个方面。建议读者先浏览一遍,留心相关的注意事项,或许能令自......
  • 27. 守护进程、进程间通信
    1.僵尸进程与孤儿进程 1.1前言在unix中,所有的子进程都是由父进程创建的,子进程再创建新的子进程子进程的结束和父进程的运行是一个异步的过程,即子进程运行完成时,父进程并不知道当子进程运行完成时,父进程需要调用wait()或waitpid()来获取子进程的运行状态1.2僵尸进程(1)概念......