lv5 嵌入式开发-9 信号机制(上)

news2024/11/28 21:44:44

目录

1 信号机制

2 信号的产生

3 常用信号

4 相关命令

4.1 信号相关命令 kill / killall 

4.2 信号发送 – kill / raise

4.3 定时器函数相关函数 – alarm /ualarm/ pause

4.4 信号捕捉:设置信号响应方式 – signal /sigaction,闹钟实现

4.5 子进程结束信号(使用SIGCHLD信号实现回收子进程)

4.6 练习


掌握:信号机制、常用信号、信号相关命令、信号发送、定时器、信号捕捉、信号集和信号屏蔽

1 信号机制

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。

linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

Linux对早期的unix信号机制进行了扩展

进程对信号有不同的响应方式:

  • 缺省方式  
  • 忽略信号  
  • 捕捉信号

2 信号的产生

  • 按键产生
  • 系统调用函数产生(比如raise, kill)
  • 硬件异常
  • 命令行产生 (kill)
  • 软件条件(比如被0除,访问非法内存等)

3 常用信号

SIGCHLD              子进程 状态改变发给父进程的                                                                                

4 相关命令

4.1 信号相关命令 kill / killall 

kill [-signal] pid

默认发送SIGTERM  

-sig 可指定信号  

pid  指定发送对象  

示例:

killall [-u  user | prog]

prog  指定进程名  

user  指定用户名

4.2 信号发送 – kill / raise

#include <unistd.h>
#include <signal.h>
int kill(pid_t pid, int sig);

功能:发送信号

参数:

pid:  > 0:发送信号给指定进程

        = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程

        < -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。

        = -1:发送信号给,有权限发送的所有进程

signum:待发送的信号

int raise(int sig);       //给自己发信号

给自己发信号,等价于kill(getpid(), signo);

示例

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(){
    
//    kill(24149,11);  //对进程24149,发送一个段错误信号,进程24149接收到信号报错结束
   raise(11);        //对自己的进程,发送一个段错误信号
}

4.3 定时器函数相关函数 – alarm /ualarm/ pause

int alarm(unsigned int seconds);

功能:定时发送SIGALRM给当前进程(一次性)

返回值:成功时返回上个定时器的剩余时间,失败时返回EOF  

参数:seconds 定时器的时间  

一个进程中只能设定一个定时器,时间到时产生SIGALRM

示例

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(){
    
    alarm(3);
    while(1);
}

//运行结果
linux@linux:~/Desktop$ ./alarm 
Alarm clock

重复性闹钟函数 

useconds_t ualarm(useconds_t usecs, useconds_t interval); (循环发送)

以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生 

发送alarm信号函数

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

struct itimerval {
    struct timeval it_interval;  // 闹钟触发周期
    struct timeval it_value;    // 闹钟触发时间
};

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

功能:定时的发送alarm信号

参数:

-which:

        ITIMER_REAL:以逝去时间递减。发送SIGALRM信号

        ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号

        ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时

                                  间。 发送SIGPROF信号

-new_value:  负责设定 timout 时间

-old_value:   存放旧的timeout值,一般指定为NULL

int pause(void);

进程一直阻塞,直到被信号中断  

被信号中断后返回-1,errno为EINTR

示例:

#include <stdio.h>  
#include <stdlib.h>
#include <unistd.h>
int main() {
   alarm(3);
   pause();
   printf(“I have been waken up!\n”);
   return 0;
}
$ ./a.out 
Alarm clock

重要:alarm经常用于实现超时检测     

4.4 信号捕捉:设置信号响应方式 – signal /sigaction,闹钟实现

信号捕捉过程:

  1. 定义新的信号的执行函数handle。
  2. 使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。

signal函数

typedef void (*sighandler_t)(int);
//它使用了typedef关键字定义了一个名为sighandler_t的新类型,这个类型是一个指向函数的指针,该函数以一个整型参数作为信号值,返回值类型为void(即不返回任何值)。通过这个定义,可以方便地声明和使用信号处理函数,以对接收到的信号做出相应的处理。

sighandler_t signal(int signum, sighandler_t handler);

功能:捕捉信号执行自定义函数 

返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR

参数:

  • signo 要设置的信号类型  
  • handler 指定的信号处理函数: SIG_DFL代表缺省方式;SIG_IGN 代表忽略信号;  

示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>

typedef void (*sighandler_t)(int);

sighandler_t oldact;

void handle(int sig){
    printf("I cath the SIGINT \n");
    signal(SIGINT,oldact);   //改回原先的信号处理,如按ctrl+c结束进程
}

int main(){

    oldact = signal(SIGINT,handle);

    while(1){

        sleep(1);
    }    


}

sigaction函数 

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数说明:

  • signum:要设置或获取处理方式的信号编号。
  • act:指向struct sigaction结构体的指针,用于设置新的处理方式。如果为NULL,则不会改变原有的处理方式。
  • oldact:指向struct sigaction结构体的指针,在函数返回时用于存储旧的处理方式。如果为NULL,则不会保存旧的处理方式。

struct sigaction 结构体定义如下:

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}

sigaction 结构体的字段解释如下:

  • sa_handler:是一个函数指针,用于指定信号处理函数,表示简单的信号处理。
  • sa_sigaction:是一个函数指针,用于指定信号处理函数,表示复杂的信号处理。

        它有三个参数,可以获得关于信号的更详细的信息。

        sa_flags参考值如下:

        SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

        SA_RESTART:使被信号打断的系统调用自动重新发起。

        SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

        SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

  • sa_mask:用于设置在处理当前信号时要阻塞的信号集。
  • sa_flags:用于设置一些标志位,例如SA_RESTART表示在被信号中断的系统调用重新启动。/通过将其设置为0,表示不使用任何额外的标志位,即默认行为。
  • sa_restorer:保留字段,已不再使用。

函数返回值:

  • 成功:返回0,并将旧的信号处理方式保存到 oldact 中(如果 oldact 不为 NULL)。
  • 失败:返回-1,并设置 errno 来指示错误的原因。

通过调用 sigaction 函数,可以为指定的信号设置新的处理方式,或者获取当前的处理方式。这在编写信号处理程序时非常有用,以便对信号进行正确的处理和响应。

示例:定时器的实现(使用alarm)

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>

typedef void (*sighandler_t)(int);

sighandler_t oldact;

void handle(int sig){
   if(sig == SIGINT){
        printf("I cath the SIGINT \n");
   }else if (sig==SIGALRM){
       printf("second timer \n");
       alarm(1);
   }
    //    signal(SIGINT,oldact);
}


int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    alarm(1);
    sigaction(SIGALRM,&act,NULL);
//    oldact = signal(SIGINT,handle);

    while(1){
        sleep(1);
    }

} 

 示例:定时器的实现(settimer实现)

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
#include <sys/time.h>


typedef void (*sighandler_t)(int);

sighandler_t oldact;

void handle(int sig){
   if(sig == SIGINT){
        printf("I cath the SIGINT \n");
   }else if (sig==SIGALRM){
       printf("second timer \n");
//       alarm(1);
   }
    //    signal(SIGINT,oldact);
}


int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
//    sigaction(SIGINT,&act,NULL);
//    alarm(1);
    struct itimerval timevalue;
    timevalue.it_interval.tv_sec = 1;
    timevalue.it_interval.tv_usec = 0;
    timevalue.it_value.tv_sec = 5;
    timevalue.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL,&timevalue, NULL);
    sigaction(SIGALRM,&act,NULL);
//    oldact = signal(SIGINT,handle);

    while(1){
  //      sleep(1);   //sleep也是用alarm实现的
    }

} 

示例

// 头文件省略
void handler (int signo) {
     if (signo == SIGINT) {
        printf(“I have got SIGINT!\n”); }
     if (signo == SIGQUIT) {
        printf(“I have got SIGQUIT\n”); }
}

int  main() {
     signal(SIGINT, handler);
     signal(SIGQUIT, handler);
     while ( 1 ) pause();
     return 0;
}

4.5 子进程结束信号(使用SIGCHLD信号实现回收子进程)

SIGCHLD的产生条件

1子进程终止时

2子进程接收到SIGSTOP信号停止时

3子进程处在停止态,接受到SIGCONT后唤醒时

SIGCHLD

可以用来处理子进程退出的僵尸

之前是使用wait来回收,现在使用信号来配合wait回收,解决父进程阻塞的问题

示例:

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


void handle(int sig){

    wait(NULL);
    printf("Get sig =%d\n",sig);

}


int main(){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    pid = fork();

    if(pid>0){
        //wait(NULL);   //利用信号处理,可以让父进程异步操作,接着干自己的事情而不阻塞

        sigaction(SIGCHLD,&act,NULL);
        while(1){
            printf("this is father process\n");
            sleep(1);
        }

    }else if(pid==0){
        sleep(5);
        exit(0);
    }


}

执行完通过ps 命令查看,没有僵尸进程。

4.6 练习

实现捕捉SIGINT信号,在屏幕上打印 "Ctrl + c"

#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>

typedef void (*sighandler_t)(int);

sighandler_t oldact;

void handle(int sig){
    printf("Ctrl+C\n");
    //signal(SIGINT,oldact);   //改回原先的信号处理,按ctrl+c结束进程
}

int main(){

    oldact = signal(SIGINT,handle);

    while(1){

        sleep(1);
    }    


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1055475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试官让手撕红黑树,我直接向他秀一手手撕map与set

文章目录 一、map与set的STL源码分析二、改造红黑树1.基本结构2.比较 三、迭代器1.STL源码分析2. 迭代器3. 与库里面的迭代器的差异 四、map的[]操作五、map与set完整代码 一、map与set的STL源码分析 我们首先可以观察到&#xff0c;在set和map中包含有如下的头文件 于是我们可…

28299-2012 结构用热轧翼板钢 课堂随笔

声明 本文是学习GB-T 28299-2012 结构用热轧翼板钢. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了结构用热轧翼板钢的订货内容、尺寸、外形、重量及允许偏差、技术要求、试验方法、检 验规则、包装、标志和质量证明书等。 本…

Python绘图系统24:添加辅助坐标轴

文章目录 辅助坐标增减坐标轴时间轴**代码优化源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据…

面试必考精华版Leetcode199. 二叉树的右视图

题目&#xff1a; 代码&#xff08;首刷看解析&#xff09;&#xff1a; class Solution { public:vector<int> rightSideView(TreeNode* root) {unordered_map<int,int> rightmostvalue;queue<TreeNode*> nodeQueue;queue<int> depthQueue;int maxDe…

上古神器:十六位应用程序 Debug 的基本使用

文章目录 参考环境上古神器 DebugBug 与 DebuggingDebugDebug 应用程序淘汰原因使用限制 DOSBox学习 Debug 的必要性DOSBox-X Debug 的基本使用命令 R查看寄存器的状态修改寄存器的内容 命令 D显示内存中的数据指定起始内存空间地址指定内存空间的范围 命令 A使用命令语法错误查…

第8章 Spring(二)

8.11 Spring 中哪些情况下,不能解决循环依赖问题 难度:★★ 重点:★★ 白话解析 有一下几种情况,循环依赖是不能解决的: 1、原型模式下的循环依赖没办法解决; 假设Girl中依赖了Boy,Boy中依赖了Girl;在实例化Girl的时候要注入Boy,此时没有Boy,因为是原型模式,每次都…

量化交易全流程(四)

本节目录 数据准备&#xff08;数据源与数据库&#xff09; CTA策略 数据源&#xff1a; 在进行量化分析的时候&#xff0c;最基础的工作是数据准备&#xff0c;即收集数据、清理数据、建立数据库。下面先讨论收集数据的来源&#xff0c;数据来源可分为两大类&#xff1a;免…

最新宽字节注入攻击和代码分析技术

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 宽字节注入攻击 宽字节注入攻击的测试地址在本书第2章。 访问id1&#xff0c;页面的返回结果如图4-51所示&#xff0c;程序并没有报错&#xff…

基于树莓派CM4制作img系统镜像批量制作TF卡

文章目录 前言1. 环境与工具2. 制作镜像3. 烧录镜像4. 总结 前言 树莓派烧录完系统做定制化配置比较费时间。在面对大批量的树莓派要配置&#xff0c;那时间成本是非常巨大的。第一次配置完可以说是摸着石头过河&#xff0c;但是会弄了以后再配置&#xff0c;都是一些重复性操…

Canal实现数据同步

1、Canal实现数据同步 canal可以用来监控数据库数据的变化&#xff0c;从而获得新增数据&#xff0c;或者修改的数据。 1.1 Canal工作原理 原理相对比较简单&#xff1a; 1、canal模拟mysql slave的交互协议&#xff0c;伪装自己为mysql slave&#xff0c;向mysql master发送…

零代码编程:用ChatGPT将特定文件标题重命名为特定格式

一个文件夹里面是同一系列文件&#xff0c;但是有两种命名方法&#xff0c;现在想把文件标题格式统一。 在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个批量重命名的任务&#xff0c;具体步骤如下&#xff1a; 打开本地电脑文件夹&#xf…

38 翻转二叉树

翻转二叉树 理解题意&#xff0c;翻转即每个结点的左右子树翻转/对调题解1 递归——自下而上题解2 迭代——自上而下 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 提示&#xff1a; 树中节点数目范围在 [0, 100] 内-100 < Node.…

树莓派CM4开启I2C与UART串口登录同时serial0映射到ttyS0 开启多串口

文章目录 前言1. 树莓派开启I2C与UART串口登录2. 开启多串口总结&#xff1a; 前言 最近用CM4的时候使用到了I2C以及多个UART的情况。 同时配置端口映射也存在部分问题。 这里集中记录一下。 1. 树莓派开启I2C与UART串口登录 输入指令sudo raspi-config 跳转到如下界面&#…

机器人过程自动化(RPA)入门 9. 管理和维护代码

仅仅创建一个自动化项目是不够的。无论是决定使用哪种布局,还是正确命名步骤,以正确的方式组织项目都很重要。项目也可以在新的项目中重用,这对用户来说非常方便。本章解释了我们可以重用项目的方法。我们还将学习配置技术并看到一个示例。最后,我们将学习如何集成TFS服务器…

第十二章 类和对象——封装

C面向对象的三大特性为&#xff1a;封装、继承、多态 C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 例如&#xff1a; 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...&#xff0c;行为有走、跑、跳、吃饭、唱歌... 车也可以作为对象&#xff0c;…

89、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->Zset 相关命令

本次讲解要点&#xff1a; ** Set相关命令&#xff1a;是指value中的数据类型** 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe …

Android-Q 对 startActivity() 做了限制,怎么适配?

一. Q 禁用后台启动 Activity 今天来聊聊“Android Q 中后台禁止启动 Activity 对现有国内 App 中启动页设计的影响”这个话题&#xff0c;再聊聊 Android Q 限制后台启动 Activity 的具体细节。 有人可能会觉得 P 还没用上&#xff0c;Q 还远着。如果只是对于普通用户来说&a…

为什么要使用虚拟机?VMware安装使用

前言 大家好&#xff0c;本文是讲述了为什么需要使用虚拟机、使用虚拟机的好处&#xff0c;以及如何在Windows系统中安装VMware。希望对大家有所帮助~ 目录 前言一、为什么要安装使用虚拟机&#xff1f;1.1、什么是虚拟机&#xff1f;1.2、虚拟机的核心组件1.3、使用虚拟机的好…