[Linux]一篇文章带你全面理解信号

文章目录

  • 初识信号
    • 一、什么是信号
    • 二、为什么要有信号
  • 看见信号
    • 一、先见一下Linux中的信号:
    • 二、如何产生信号
    • 三、自定义信号的处理行为(自定义捕捉)
  • 了解信号
    • 一、信号的保存
    • 二、block、pending表使用代码查看
    • 三、一些倔强的,无法被屏蔽的信号
    • 四、详谈信号的处理
  • 运用信号
    • 一、term与core
    • 二、异常处理-善后处理
    • 三、进程间通信(信号的方式不常用)
    • 四、系统调试(不常用)

初识信号

一、什么是信号

在操作系统(OS)中,信号(signal)是一种进程间通信(IPC)的机制,特别是在Unix、类Unix以及其他POSIX兼容的操作系统中。信号是一种异步的通知机制,用来提醒进程一个事件已经发生。

这种说明未免会有些晦涩难懂,现在将从以下几点带你认识信号:

  1. 信号是系统提供的一种让用户(进程)能够给其他进程发送异步信息(可以理解任意时间都可以发送,接受信号的进程无法预料到信号到来的时间)的方式
  2. 信号的发送,是为了让接受信号的进程做一些自己执行流之外的操作(如:你正在打游戏,家里人喊你吃饭,这就是给你的一个信号)
  3. 进程对于信号的到来,可以选择处理、暂时不管或忽略的策略

二、为什么要有信号

  1. 异步通知:信号提供了一种异步通知的方式,允许一个进程或线程在不中断其正常执行的情况下接收通知,以便采取适当的措施。这对于处理外部事件,如用户输入、硬件故障或其他进程的状态变化等非常有用。
  2. 进程间通信:进程可以通过发送信号的方式进行通信。例如,一个进程在完成了某项工作之后,可以向另一个进程发送信号,通知其进行下一步的操作。这对于多任务协作非常有用。
  3. 异常处理:当程序出现异常情况,如除以零或无效内存访问时,操作系统可以通过发送信号来通知进程。进程可以定义信号处理函数来处理这些异常情况,从而防止程序崩溃或数据损坏。
  4. 系统调试:信号还可以用于程序的调试。例如,在程序运行时,可以向进程发送信号以打印程序的状态信息等,帮助开发人员定位问题。
  5. 用户交互:用户可以通过键盘快捷键或类似的输入方式发送信号给正在运行的进程,以请求特定操作。这提供了一种灵活的用户交互方式。

这部分我们将在后面使用代码的形式展现出来。

看见信号

一、先见一下Linux中的信号:

在这里插入图片描述

其中31之后的都是实时信号,这种信号出现了就要立刻处理,常见于实时操作系统

其中介绍几个比较典型的信号

  • 2)SIGINT :‘‘Interrupt from keyboard’’,键盘中断信号,可以通过组合键ctrl+c触发

  • 8)SIGFPE:“Floating-point exception”,除0异常

  • 9)SIGKILL:“Kill signal”,杀死信号

  • 11)SIGSEGV:“Invalid memory reference”,段错误

  • 13)SIGPIPE:“Broken pipe: write to pipe with no readers”,管道信号,如果没有读信号的进程则中断进程

  • 14)SIGALRM:“Timer signal from alarm(2)”,可以在代码中输入alarm(int),来设置闹钟

  • 19)SIGSTOP:“Stop process”,暂停进程

  • 18)SIGCONT:“Continue if stopped”,开始被暂停的进程

二、如何产生信号

  1. kill -num pid

    使用ps axj | grep process_name来查询进程的pid,再向指定进程发送signum信号

  2. 键盘产生

    ctrl+c:触发SIGINT信号

    ctrl+\:触发SIGQUIT信号

  3. 系统调用

    kill(pid_t pid, int sig):对pid进程发送sig信号

    raise(int sig):对自己发送sig信号

    abort():引起进程直接终止,对自己发送SIGABRT信号

  4. 软件条件

    软件条件指:系统在持续时间内会一直对信号产生条件进行判断,如果满足则发送信号,不满足则继续持续查询

    alarm(uint second):设置时钟,在second秒后,系统发送SIGALRM信号。在此期间,系统会不断判断时钟是否满足条件

    SIGPIPE:在创建管道的时候,常常会发现,如果读端全关闭了,则写端就会立刻关闭。这是因为系统会一直检测读写端的情况,如果一旦读端没了,就会发送SIGPIPE信号

  5. 异常

    1)首先,对于除0异常段错误这两个异常是和寄存器相关的,寄存器检测出问题就会分别发送SIGFPE、SIGSEGV信号

    2)对于语言级别的异常,例如throw,他们抛出的异常和信号有关系。不过会对异常进行进一步的封装,以实现各自的目的(这里可以类比后面所要讲signal()函数)

三、自定义信号的处理行为(自定义捕捉)

前面谈到,进程对于信号的处理有3种方式:忽略、暂时不管、处理

这里先去探究处理的方式:

如下是一些系统的默认处理方式

Term   Default action is to terminate the process.Ign    Default action is to ignore the signal.Core   Default action is  to terminate the process and dump core.Stop   Default action is to stop the process.Cont   Default action is to continue the process if it is currently stopped.

其中对于Ign方式,就是所说的忽略,不去接收和处理信号。

其余的常见方式有:中断(Term、Core,后面会说明两者的差别)、暂停(Stop)、继续(Cont)

但是!上述的信号处理方式不一定满足我们的需求,比如:我在打手游,家里人喊我吃饭,家里的默认处理方式就是放下手机去吃饭,但是我想去倒个垃圾,在路上继续玩游戏。这时候就需要自定义捕捉(自定义信号的处理行为)

方法1:

signal()函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

sighandler_t

作为一个参数:用来回调自定义函数,返回类型为void,参数为int(要自定义捕捉的信号)

作为一个返回值:返回一个函数指针,这个函数指针用来记录自定义捕捉之前该信号的捕捉方式。可以拿来和SIG_ERR、SIG_DFL、SIG_IGN比较来判断此前的捕捉方式

int signum
需要自定义捕捉的信号名字或者编号

举例:下述代码更改了2)SIGINT信号的处理方式,并对返回的信息做了判断

#include <stdio.h>  
#include <signal.h>  
#include <unistd.h>  void signal_handler(int signum) {  printf("Caught signal %d\n", signum);  // 清理并关闭  // ...  exit(signum);  
}  int main() {  // 注册信号处理函数  void (*prev_handler)(int);  prev_handler = signal(SIGINT, signal_handler);  //或者         signal(2, signal_handler);  if (prev_handler == SIG_ERR) {  fprintf(stderr, "Error setting signal handler\n");  return 1;  }  // 打印之前为该信号设置的处理函数  if (prev_handler == SIG_DFL) {  printf("Previous handler was default.\n");  } else if (prev_handler == SIG_IGN) {  printf("Previous handler was ignore.\n");  } else {  printf("Previous handler was a function.\n");  }  // 等待信号  while(1) {  cout<<"pro running..."<<endl;sleep(1);  }  return 0;  
}

方法2(了解):

sigaction()函数

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

该函数比signal()函数难用,但是功能也更全面,比如:可以在设置自定义捕捉的时候同时屏蔽信号

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);  // 已被弃用,在大多数系统上应该设置为 NULL  
};

其中定义了有:处理方法(其中第二个sa_sigaction主要用来处理实时信号)、信号掩码(用来屏蔽信号)、信号标志位(其中在使用``sa_sigaction时需要设置为SA_SIGINFO`)

act && oldact

oldact返回旧的处理信息(struct sigaction结构体),act用来传入新的处理信息

举例:下述代码更改了2)SIGINT信号的处理方式

#include <stdio.h>  
#include <signal.h>  
#include <unistd.h>  void handle(int sig)
{cout<<"sig "<<sig<<" can't kill me..."<<endl;
}int main()
{struct sigaction newact;struct sigaction oldact;newact.sa_handler=handle;newact.sa_restorer=NULL;newact.sa_flags=0;sigemptyset(&newact.sa_mask);//将旧的信号掩码清空sigaction(2,&newact,&oldact);//处理2号信号while(true){cout<<"pro running..."<<endl;sleep(1);}return 0;
}

了解信号

一、信号的保存

前面我们已经知道了信号是如何产生的,但是内核大多都不能第一时间检测到信号的产生(对!你没听错,信号产生后,OS第一时间不一定会对其进行处理,因为他“不知道”有信号产生了,至于何时才能检测到,我们留到后续说明),所以我们就需要将产生的信号进行保存,以方便后续内核的信号检测

信号的保存于以下3部分息息相关:

  1. 信号递达(执行信号的处理动作,如:前面的handle函数或默认的信号处理)
  2. 信号未决(在信号产生但是还未递达的时候,该信号就处于未决状态)
  3. 信号阻塞(决定了信号能否被递达处理)tips:注意区分阻塞和忽略,忽略是一种递达的处理方式

对于上述3部分,采用了位图和函数指针数组进行保存

在这里插入图片描述

在进程PCB中维护了这样3张表:bolck(信号阻塞表)、pending(信号未决表)、handler(信号递达表)

前2个位图的类型都是sigset_t,handler的类型是,Linux内核中各部分结构体内容如下:

/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;//hanler
sigset_t blocked, real_blocked;//block
sigset_t saved_sigmask;
struct sigpending pending;//pending/*位图结构*/
typedef struct {unsigned long sig[_NSIG_WORDS];
} sigset_t;/*pending*/
struct sigpending {struct list_head list;sigset_t signal;
};/*handler*/
struct sighand_struct {atomic_t		count;struct k_sigaction	action[_NSIG];spinlock_t		siglock;wait_queue_head_t	signalfd_wqh;
};
struct sigaction {__sighandler_t sa_handler;unsigned long sa_flags;
#ifdef SA_RESTORER__sigrestore_t sa_restorer;
#endifsigset_t sa_mask;		/* mask last for extensibility */
};
typedef void __signalfn_t(int);//函数指针
typedef __signalfn_t __user *__sighandler_t;

其中对应比特位/数组下表位置编号对应信号编号,如:1号位置 -> 1号信号SIGHUP

  1. block位图:
    用来阻塞信号(不是忽略),当某一位置置1的时候,就将该位的信号就行屏蔽(即可以接收该信号,但是不处理该信号)

  2. pending位图:

    用来保存接收到的信号,当有信号产生的时候,会将对应位置置1,而内核每次将检测pending位图,对所有置1的位置的信号进行处理,处理完后再将该位置置0

  3. handler数组:

    用来记录对应信号的处理方法,数组中每个元素中都封装包含了一个函数指针,指向你对该信号的自定义捕捉或默认处理方法

二、block、pending表使用代码查看

前面通过内核代码看到了这两个表的定义,现在让我们通过代码查看:在此之前我们先介绍一系列函数、结构体,以方便后续的操作

struct sigset_t位图结构体,具体结构见前面

我们可以创建一个sigset_t结构体,并对其内容进行修改

sigset_t set;
//阻塞signo信号
sigaddset(sigset_t* set, int signo);
//取消阻塞signo信号
sigdelset(sigset_t* set, int signo);
//判断signo是否阻塞
sigismember(sigset_t* set, int signo);
//清空阻塞位图
sigemptyset(sigset_t* set);
//全阻塞
sigfillset(sigset_t* set);

这里可能有些人有疑问:为什么非要通过系统提供的函数来修改位图呢?不能我自己直接去修改set内容吗?反正sigset_t内部就只有一个元素(数组),我轻轻松松就可以按照我的需求进行修改

原因:对于不同的内核环境下,可能实现屏蔽的操作有些许不同,所以必须通过调用函数来修改位图,以便屏蔽不同内核的底层差异


sigprocmask()修改进程的信号屏蔽位(block位图)

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

set:

我们希望修改的信号屏蔽字

oldset:

保存修改前的信号屏蔽字

how: 指示如何修改,其可以选择如下选项

  1. SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
  2. SIG_UNBLOCK:set包含了我们希望从信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
  3. SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set

sigpending()获取pending位图的内容

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

通过sigpending函数,可以将当前进程的pending位图放入sigset_t set中

如果想打印pending位图内容,只需用sigismember和set一起使用就行

具体代码演示

  1. block表的查看

    我们先创建一个sigset_t结构体,打印其前31个信号的屏蔽状态,过几秒后,我们手动添加对2、5、7、13、27号信号的屏蔽再次打印查看结构(这里由于还没有调用sigprocmask函数,所以进程的信号屏蔽字不会被修改i)

    int main()
    {sigset_t set;cout<<"修改屏蔽字之前:"<<endl;for(int i=1;i<=31;i++){cout<<sigismember(&set,i)<<" ";}cout<<endl;sigaddset(&set,2);sigaddset(&set,5);sigaddset(&set,7);sigaddset(&set,13);sigaddset(&set,27);sleep(2);cout<<"修改屏蔽字之后:"<<endl;for(int i=1;i<=31;i++){cout<<sigismember(&set,i)<<" ";}cout<<endl;return 0;
    }

    运行结果:

    改屏蔽字之前:
    1 0 0 0 1 0 1 0 1 0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 1 1 1 1 1 0 1 
    修改屏蔽字之后:
    1 1 0 0 1 0 1 0 1 0 0 0 1 1 1 1 0 1 1 0 0 0 1 1 1 1 1 1 1 0 1 
    

    有些信号一开始就是默认被屏蔽的,但是其余的信号在我们添加屏蔽后确实有了改变

  2. pending表的查看

    我们先打印进程当前的pending位图,查看有没有未决信号,然后使用sigprocmask将2号信号屏蔽了,再次查看pending位图(这里我们自定义捕捉一下2号信号,方便观察)

    void handler(int sig)
    {cout<<"捕捉到了 "<<sig<<" 号信号"<<endl;sleep(1);
    }
    void print(sigset_t* set)
    {for(int i=1;i<31;i++){if(sigismember(set,i)){cout<<1<<" ";}else {cout<<0<<" ";}}cout<<endl;
    }
    int main()
    {signal(SIGINT,handler);//自定义捕捉sigset_t set;sigpending(&set);cout<<"未屏蔽之前,发送信号之前,当前进程的pending位图:"<<endl;print(&set);sleep(5);//留时间发送信号,此处是为了方便演示cout<<"未屏蔽之前,发送信号之后,当前进程的pending位图:"<<endl;sigpending(&set);print(&set);sigaddset(&set,2);sigprocmask(SIG_BLOCK,&set,nullptr);//这里就不保存原来的屏蔽字了cout<<"修改屏蔽字,屏蔽2号信号...:"<<endl;sigpending(&set);cout<<"屏蔽之后,发送信号之前,当前进程的pending位图:"<<endl;print(&set);sleep(5);//留时间发送信号,此处是为了方便演示sigpending(&set);cout<<"屏蔽之后,发送信号之后,当前进程的pending位图:"<<endl;print(&set);return 0;
    }
    

    运行结果:

    未屏蔽之前,发送信号之前,当前进程的pending位图:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    ^C捕捉到了 2 号信号
    未屏蔽之前,发送信号之后,当前进程的pending位图:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    修改屏蔽字,屏蔽2号信号...:
    屏蔽之后,发送信号之前,当前进程的pending位图:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    ^C屏蔽之后,发送信号之后,当前进程的pending位图:
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    
    1. 在设置屏蔽位之前,pending位图全0,即没有信号发送
    2. 发送2号信号,调用了handler函数(自定义捕捉),同时pending位图也为全0,因为2号信号被处理了,所以变回了0
    3. 在设置屏蔽位之后,pending位图全0,因为此时还没有发送信号
    4. 发送2号信号,没有调用handler函数,通过pending位图2号位置变为1,说明2号信号保存了,但是没有被递达(处理)

    三、一些倔强的,无法被屏蔽的信号

    问题:如果我将进程的所有信号都屏蔽了,那么一旦我运行了这个进程,那完蛋了,杀不掉了!!!

    不用担心,OS不会让你这么干,或许是他早就预料到你会犯下这么尴尬的错误。

    对于一些信号是无法屏蔽的,如:

    1. 9)SIGKILL 杀掉信号
    2. 19)SIGSTOP 暂停信号
    3. 18)SIGCONT这个会做特殊处理,他会额外解除某些信号的屏蔽

    代码演示:

void print(sigset_t* set){for(int i=1;i<31;i++){if(sigismember(set,i)){cout<<1<<" ";}else {cout<<0<<" ";}}cout<<endl;
}int main()
{sigset_t set;sigaddset(&set,9);sigaddset(&set,19);print(&set);sigprocmask(SIG_BLOCK,&set,nullptr);while(true){cout<<"proc running..."<<endl;sleep(1);}return 0;}

运行结果

   1 0 0 0 1 0 1 0 1 0 0 0 0 1 1 0 0 0 1 1 0 1 0 1 0 1 0 0 1 0proc running...proc running...proc running...proc running...proc running...proc running...Killed

可以通过信号屏蔽字看出,我们确实屏蔽了9,19号信号,但是让我们发送kill -9 pid指令后,进程还是被杀掉了。同样的发送kill -19 pid指令后,进程还是会被暂停

四、详谈信号的处理

在前面我们已经了解到了信号处理的代码级表现:位图保存、阻塞,自定义捕捉…现在我们浅谈内核来进一步了解信号的处理:

先说结论:信号的处理发生在由内核态转向用户态的时候发生

在这里插入图片描述

为什么要这样设计,从以下的几点问题来解答:

  1. 为什么要先进入内核态

    进入内核态的原因不单单是需要处理信号,对于异常以及系统调用都会让进程进入内核态。进入内核态就相当于提高权限,如此一来,CPU就有权力去做处理你的一系列需求

  2. 如果没有系统调用和异常的产生,我还能进入内核态吗?

    当然可以,别忘了对于大多数操作系统都支持并发运行,这就涉及到进程调度的操作(时间片到期后就要调度)。而对进程的调度需要相当高的权限,所以每次调度的时候就会陷入内核态,这样一来就成功进入内核态了

  3. 为什么一定要在内核态向用户态转换的时候才会处理信号?

    首先,处于内核态的进程具有极高的权限,他能够直接去修改PCB等一系列核心内容(同时信号的处理也需要修改PCB内容),这样大大提高了处理的效率与能力。其次,从内核态转向用户态这一时间点正好符合的运行的逻辑,下一步要么处理自定义捕捉(需要转向用户态)、要么处理完成返回主执行流(需要转回用户态)

  4. 为什么对于自定义捕捉需要转向用户态再处理,而不是直接在内核态处理?

    为了安全!!!!因为自定义捕捉函数是用户写的,如果在内核态处理用户程序,那么此时用户的代码权限是非常高,OS担心用户恶意访问内核,所以就必须要在用户态执行用户写的自定义捕捉函数

  5. 对于一些可以杀掉进程的信号,为什么不直接杀掉进程,而是要先放进pending中

    为了安全,不出未定义问题!!!!如果进程此时有更重要的事情需要做,直接杀掉进程可能会导致未定义行为,所以让进程先保存信号,把需要回收的回收、释放的释放、没做完的做完,最后在让OS回收进程

运用信号

说了一大堆,如果不会运用信号,那学再多都没用,这里提供几个简单的信号运用例子,以供参考:

一、term与core

有些朋友可能会在平时玩Linux的时候发现,信号的终止类型有两种:term和core。这两种都是可以直接杀掉进程的,很多人可能并不知道他们的区别

term:直接用来杀掉进程,不做任何处理

core:core dump(核心转储),但是需要设置后才能发挥作用,不然就同term一样。

其核心作用是:

  • 通过core定位到进程为什么退出,以及执行到哪行代码退出的

  • 将进程在内存中运行的核心数据(与调试有关)转储到磁盘中形成的core、core.pid(Centos)文件

  • 协助我们进行调试,可以用gdb调试下,输入core-file corename就可以获取详细信息

如何打开core dump?

ulimit命令

-a :查看全部内容

-c num :开启core dump功能,并将core文件大小设定为num

运行结果:

sll@SFT:~/git/testsignal$ ulimit -acore file size          (blocks, -c) 0 // 这一行data seg size           (kbytes, -d) unlimitedscheduling priority             (-e) 0file size               (blocks, -f) unlimitedpending signals                 (-i) 7319max locked memory       (kbytes, -l) 65536max memory size         (kbytes, -m) unlimitedopen files                      (-n) 65535pipe size            (512 bytes, -p) 8POSIX message queues     (bytes, -q) 819200real-time priority              (-r) 0stack size              (kbytes, -s) 8192cpu time               (seconds, -t) unlimitedmax user processes              (-u) 7319virtual memory          (kbytes, -v) unlimitedfile locks                      (-x) unlimitedsll@SFT:~/git/testsignal$ ulimit -c 1024// 设置大小
sll@SFT:~/git/testsignal$ ulimit -a
core file size          (blocks, -c) 1024 //打开了核心转储
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7319
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7319
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

运用core dump:

C代码:

#include <stdio.h>  
#include <stdlib.h>  int main() {  int *ptr = NULL;  printf("Dereferencing a NULL pointer...\n");  *ptr = 42;  // 这将导致段错误  return 0;  
}

运行代码:

sll@SFT:~/git/testsignal$ ./a.out 
Dereferencing a NULL pointer...
Segmentation fault (core dumped)

目录内容:

sll@SFT:~/git/testsignal$ ll
total 176
drwxrwxr-x  3 sll sll   4096 May 15 11:41 ./
drwxrwxr-x 24 sll sll   4096 May  7 20:43 ../
-rwxrwxr-x  1 sll sll  16696 May 15 11:40 a.out*
-rw-rw-r--  1 sll sll   4870 May 15 11:23 code.cc
-rw-------  1 sll sll 380928 May 15 11:41 core  //新增了core dump文件
-rw-rw-r--  1 sll sll     69 Apr 22 14:20 Makefile
-rwxrwxr-x  1 sll sll  17600 May 15 11:20 test*
-rw-rw-r--  1 sll sll    195 May 15 11:40 test.c

可以发现多了个core文件,使用gdb调试core

sll@SFT:~/git/testsignal$ gdb ./a.out core //方法1启动core
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
[New LWP 181336]
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000555d29e3616d in main ()(gdb) core-file core //方法2启动core
[New LWP 181336]
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000555d29e3616d in main ()

可以看到最后有提示,SIGSEGV提示等一系列信息

二、异常处理-善后处理

当你的进程因为某些原因而退出了,此时你可能还有很多需要释放的资源、需要回收的进程、需要做完的任务,此时如果直接退出就会引发一系列未定义行为,这个时候就需要自定义捕捉去完成善后
同时这也体验的用户的交互性,发送了信号后,进程给予了回应

代码演示:

#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  // 信号处理函数  
void signal_handler(int signum) {  printf("Received signal %d, cleaning up and exiting...\n", signum);  // 在这里可以进行清理工作,如关闭文件、释放资源等  // 退出程序  exit(signum);  
}  int main() {  // 注册SIGINT信号的处理函数  if (signal(SIGINT, signal_handler) == SIG_ERR) {  perror("signal");  return 1;  }  // 模拟一个长时间运行的程序  printf("Running, press Ctrl+C to stop...\n");  while (1) {  sleep(1);  // 休眠一秒  printf("Still running...\n");  }  return 0; // 这个return语句实际上永远不会被执行  
}

三、进程间通信(信号的方式不常用)

父进程创建了一个子进程,并在子进程中设置了一个信号处理函数来捕获SIGUSR1信号。然后父进程等待5秒钟,之后向子进程发送SIGUSR1信号。子进程在接收到信号后执行其信号处理函数,并输出一条消息。最后,父进程等待子进程结束并输出子进程的退出状态。

#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <sys/wait.h>  // 自定义的信号处理函数  
void signal_handler(int signum) {  printf("Child process received signal %d\n", signum);  // 在这里执行你想要的操作  
}  int main() {  pid_t pid; // 用于存储子进程的ID  // 创建子进程  pid = fork();  if (pid < 0) { // fork失败  fprintf(stderr, "Fork failed\n");  return 1;  } else if (pid == 0) { // 子进程  // 设置信号处理函数  if (signal(SIGUSR1, signal_handler) == SIG_ERR) {  perror("signal");  return 1;  }  // 子进程等待信号  printf("Child process is waiting for signal...\n");  while (1) {  sleep(1);  }  } else { // 父进程  // 父进程等待一段时间然后发送信号给子进程  printf("Parent process will send signal to child process after 5 seconds...\n");  sleep(5);  // 发送SIGUSR1信号给子进程  if (kill(pid, SIGUSR1) < 0) {  perror("kill");  return 1;  }  // 等待子进程结束  int status;  waitpid(pid, &status, 0);  printf("Child process exited with status %d\n", status);  }  return 0;  
}

四、系统调试(不常用)

在这个示例中,我们使用了sigaction来注册SIGUSR1信号的处理函数。在信号处理函数中,我们打印了一条包含进程ID和信号编号的消息。然后,程序进入一个无限循环,模拟一个长时间运行的进程。你可以使用kill命令或者其他方式向该程序发送SIGUSR1信号来触发调试输出。

在终端中,你可以使用以下命令来向该程序发送SIGUSR1信号(假设程序的进程ID为12345):

kill -SIGUSR1 12345
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <string.h>  // 自定义的信号处理函数  
void signal_handler(int signum, siginfo_t *info, void *context) {  printf("Received signal %d for process %d\n", signum, getpid());  // 在这里添加你的调试代码,比如打印堆栈跟踪、记录日志等  // ...  
}  int main() {  struct sigaction sa;  memset(&sa, 0, sizeof(struct sigaction));  sa.sa_sigaction = signal_handler;  sa.sa_flags = SA_SIGINFO;  // 注册SIGUSR1信号的处理函数  if (sigaction(SIGUSR1, &sa, NULL) == -1) {  perror("sigaction");  return 1;  }  printf("Process %d is running, send SIGUSR1 to trigger debug output\n", getpid());  // 模拟一个长时间运行的程序  while (1) {  sleep(1);  // ... 其他代码 ...  }  return 0; // 注意:实际上这行代码永远不会被执行  
}

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

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

相关文章

【Redis】Redis键值存储

大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#xff0c;但是也想日更的人。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4aa;&#x1f4aa;&#x1f4aa…

用wxPython和PyMuPDF将PNG图像合并为PDF文件

在日常工作中,我们经常需要将多个图像文件合并到一个PDF文档中,以便于查看、共享或存档。虽然现有的一些工具可以实现这一功能,但开发一个自定义的GUI工具可以更好地满足特定需求,并提供更好的用户体验。 在本文中,我将介绍如何使用Python、wxPython和PyMuPDF库创建一个简单的…

基于SpringBoot设计模式之创建型设计模式·生成器模式

文章目录 介绍开始架构图样例一定义生成器定义具体生成器&#xff08;HTML格式、markdown格式&#xff09;实体类HTML格式生成器MarkDown格式生成器 测试样例 总结优点缺点 介绍 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。   如…

flowable工作流设置审批人为指定角色+部门的实现方式

一、绘制流程图页面配置 1、指定固定审批角色组织的实现 如上图红框部分&#xff0c;需要修改此处为需求对应。比如此时红框不支持指定某个部门下的指定角色这种组合判断的审批人。则需要修改页面变成选完角色同时也选择上部门统一生成一个group标识。 修改完后&#xff0c;生…

股指期货基差衡量的是什么?

在股指期货市场中&#xff0c;基差、升水和贴水是三个关键的术语&#xff0c;这些基差衡量的是现货市场的价格与期货市场的价格之间的差异。 一、基差&#xff1a;现货与期货的价差 1. 定义&#xff1a;基差是指现货价格与相应期货合约价格之间的差额。计算方式是现货价格减去…

SFTPGO 整合minio AD群组 测试 |sftpgo with minio and ldap group test

SFTP-GO 研究 最近在测试sftpgo&#xff0c;发现中文的资料比较少&#xff0c;在企业中很多存储开始支持S3&#xff0c;比如netapp 于是想尝试把文件服务器换成sftpgoS3的存储&#xff0c;sftp go和AD 群组的搭配测试比较少 自己测试了一把&#xff0c;觉得还是没有server-u的A…

JVS物联网、无忧企业文档、规则引擎5.14功能新增说明

项目介绍 JVS是企业级数字化服务构建的基础脚手架&#xff0c;主要解决企业信息化项目交付难、实施效率低、开发成本高的问题&#xff0c;采用微服务配置化的方式&#xff0c;提供了 低代码数据分析物联网的核心能力产品&#xff0c;并构建了协同办公、企业常用的管理工具等&am…

函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身&#xff0c;称为函数的递归&#xff08;recursive&#xff09;调用。C和C允许函数的递归调用。例如&#xff1a; int f(int x) { int y,z; zf(y); //在调用函数 f 的过程中&…

云服务器修改端口通常涉及几个步骤

云服务器修改端口通常涉及几个步骤 远程连接并登录到Linux云服务器&#xff1a; 使用SSH工具&#xff08;如PuTTY、SecureCRT等&#xff09;远程连接到云服务器。 输入云服务器的IP地址、用户名和密码&#xff08;或密钥&#xff09;进行登录。 修改SSH配置文件&#xff1a…

Jmeter使用While控制器

1.前言 对于性能测试场景中&#xff0c;需要用”执行某个事物&#xff0c;直到一个条件停止“的概念时&#xff0c;While控制器控制器无疑是首选&#xff0c;但是在编写脚本时&#xff0c;经常会出现推出循环异常&#xff0c;获取参数异常等问题&#xff0c;下面总结两种常用的…

如何将Excel表格中的图片链接直接显示成图片?

在 Excel 中&#xff0c;你可以通过以下步骤将图片链接转换为直接显示图片&#xff1a; 1. **插入图片链接**&#xff1a;首先&#xff0c;在 Excel 表格中插入图片的链接。你可以在某个单元格中输入图片的链接地址&#xff0c;或者使用 Excel 的“插入图片”功能插入链接。 2.…

从新手到高手,教你如何改造你的广告思维方式!

想要广告震撼人心又让人长时间记住&#xff1f;答案肯定是“创意”二字。广告创意&#xff0c;说白了就是脑洞大开&#xff0c;想法新颖。那些很流行的广告&#xff0c;都是因为背后的想法特别、新颖。做广告啊&#xff0c;就得不停地思考&#xff0c;创新思维是关键。 广告思…

天锐绿盾 | 如何防止电脑内文件遭到泄露?

天锐绿盾是一款专为企业设计的数据防泄漏软件系统&#xff0c;它通过一系列综合性的安全措施来有效防止电脑内文件遭到泄露。 PC地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是天锐绿盾防止文件泄露的主要功能和方法&a…

【CSP CCF记录】202009-1 称检测点查询

题目 过程 难点&#xff1a;编号和位置的一一对应&#xff0c;不同位置的距离可能相等。 所以使用一个结构体记录不同检测点的编号和到居民地的距离。 sort函数进行排序。Sort函数使用方法 参考&#xff1a;http://t.csdnimg.cn/Y0Hpi 代码 #include <bits/stdc.h>…

人工智能AI聊天chatgpt系统openai对话创作文言一心源码APP小程序功能介绍

你提到的是一个集成了多种智能AI创作能力的系统&#xff0c;它结合了OpenAI的ChatGPT、百度的文言一心&#xff08;ERNIE Bot&#xff09;以及可能的微信WeLM&#xff08;或其他类似接口&#xff09;等。这样的系统确实能够极大地提高创作效率&#xff0c;并且在各种场景下为用…

Rust Web开发框架actix-web入门案例

概述 在看书的时候&#xff0c;用到了actix-web这个框架的案例。 书里面的版本是1.0&#xff0c;但是我看官网最新都4.4了。 为了抹平这种信息差&#xff0c;所以我决定把官方提供的示例代码过一遍。 核心代码 Cargo.toml [package] name "hello" version &q…

随笔:贝特弹琴

半年前&#xff0c;我买了一架朗朗代言的智能电子琴。所谓智能是指&#xff0c;它配套的手机软件知道你在按哪个键&#xff0c;它还能让任意按键发光。用专业术语说&#xff0c;它的键盘具有输入和输出功能&#xff0c;和软件组合起来是一个完整的计算机系统。 随着软件练习曲…

使用Postman来调用Salesforce Bulk API 2.0的方法

简介 Bulk API 2.0 可以支持大量数据增删改查&#xff0c; 用新版的Dataloader也可以进行访问&#xff0c;但Dataloader会把CSV里的数据先转成Bean对象&#xff0c;这样会耗费大量的时间&#xff0c;而且数据量过大会卡死&#xff0c;所以直接上传CSV会节省大量时间和避免卡死风…

关于电源1

电源的定义 广义定义&#xff1a;电源是将其它形式的能转换成电能的装置。 例如&#xff1a;发电机&#xff1a;将热能、水能、风能、核能、光照、震动等转化为电能的装置。 电池&#xff1a;将化学能转换为电能。 狭义定义&#xf…