Linux信号

本文详细介绍了Linux系统中的信号机制,包括信号的产生来源(如键盘输入、系统函数和异常)、存储方式(位图与信号屏蔽字),以及执行流程(默认动作、信号处理和信号阻塞)。特别关注了如何通过编程手段捕获和屏蔽信号,以及示例代码演示。

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

  使用 kill -l 可查看所有的信号,其中1- 31是普通信号(对于两个以上的相同信号会丢失,因为只有一位位图保存);34 - 64是实时信号(用链表队列形式将实时信号进行保存,可以保存多份)。
在这里插入图片描述

NumberNameDefault actionCorresponding event
1SIGHUPterminate终端线挂起
2SIGINTterminate来自键盘的中断(Ctrl + c)
3SIGQUITterminate来自键盘的退出(Ctrl + \)
4SIGILLterminate非法指令
5SIGTRAPterminate and dump core跟踪陷阱
6SIGABRTterminate and dump core来自abort函数的终止信号
7SIGBUSterminate总线错误
8SIGFPEterminate and dump core浮点异常( 计算1 / 0,Floating point exception)
9SIGKILLterminate*杀死进程(无法被signal自定义捕捉,系统级杀死进程)
10SIGUSR1terminate用户定义的信号1
11SIGSEGVterminate and dump core无效的存储器引用(野指针、segmentation fault)
12SIGUSR2terminate用户定义的信号2
13SIGPIPEterminate向一个没有读用户的管道写操作
14SIGALRMterminate来自alarm函数的定时信号
15SIGTERMterminate软件终止信号
16SIGSTKFLTterminate协处理器上的栈故障
17SIGCHLDignore一个子进程暂停或终止
18SIGCONTignore继续进程如果该进程停止
19SIGSTOPstop until next SIGCONT*不来自终端的暂停信号
20SIGTSTPstop until next SIGCONT来自终端的暂停信号(Ctrl + z)
21SIGTTINstop until next SIGCONT后台进程从终端读
22SIGTTOUstop until next SIGCONT后台进程从终端写
23SIGURGignore套接字上的紧急情况
24SIGXCPUterminateCPU时间限制超出
25SIGXFSZterminate文件大小限制超出
26SIGVTALRMterminate虚拟定时器期满
27SIGPROFterminate剖析定时器期满
28SIGWINCHignore窗口大小变化
29SIGIOterminate在某个描述符上可执行I/O操作
30SIGPWRterminate电源故障

1.信号的产生

所有的信号都是由操作系统发送的,因为操作系统会考虑独立和安全

  • 键盘按键输入,但是只能用来终止前台进程。

以下程序可以用来查看按键输入的信号对应的number

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handler(int signum)
{
	printf("receive signal: %d\n", signum);
    exit(1);
}
int main()
{
	for (int i = 1; i <= 31; i++)
	{ 
    	signal(i, handler);
    } 
    while(1)
    { 
        printf("------------------------------\n");
        sleep(1);
    } 
    return 0;
}
  • 程序中的异常问题

  本质是程序中存在异常问题导致软件或硬件的错误,被操作系统识别后,操作系统向对应的进程发送相应的信号。
Floating point exception(8) \ segmentation fault(11)

  异常退出会设置退出码和退出信号,有时也会设置core dump标志位为1(不是所有情况)。

ulimit -a  # 查看core的大小,若core file size = 0,则不允许core dump。
ulimit -c 1024 # 设置core的大小,使得运行core dump
gdb main

在这里插入图片描述
core.1331——1331是进程的PID。

(gdb) core-file core.1331  # 使用gdb事后调试,直接查看异常的位置

在这里插入图片描述

程序查看异常退出码以及core dump标志位:

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

int main()
{
	int status = 0;
	if(fork() == 0)
    { 
    	while(1)
        {   
            printf("child PID:%d\n",getppid());
            int *p = NULL;
            *p = 10; 
        }   
    }   
    waitpid(-1, &status, 0); 
    printf("exit code: %d , exit signal: %d , core dump : %d\n", 
    	(status>>8)&0xff, status & 0x7f, (status >> 7) & 0x01);
    return 0;                                                                      
}

  • 由系统函数产生
int kill(pid_t pid, int sig); 	//给进程发相应的信号
int raise(int sig); 			// 给自己发相应的信号
void abort(void);  				//SIGABRT
  • 由alarm函数产生
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	int ret = alarm(1); // 返回值为剩余的几时数
	while(1)
	{
		printf("hello world\n");
        alarm(0); // 取消闹钟
	}
	return 0;
}

2.信号的存储与执行

  进程收到信号后不会立即处理,会将信号以位图的形式存储在进程的PCB中;当进程从内核态返回用户态的时候,进行检测并处理。
  用户的数据和代码会被加载到内存,对应的虚拟内存和物理内存的映射叫用户级页表;而操作系统的数据和代码也会被加载到内存,对应的映射关系叫系统级(内核)页表,而这个页面只有一份,所有进程共享一份操作系统的内存。进程可以通过CPU中相应的寄存器切换用户和内核的访问权限。

内核态:执行系统的代码和数据,使用内核级页表
用户态:执行用户的代码和数据,使用用户级页表

信号的处理过程分为:

  • 实际执行——递达:
    • 默认动作(SIG_DFL : 0)
    • 忽略 ( SIG_IGN : 1)
    • 自定义捕捉(handler)
  • 产生到递达间——未决:在信号位图中暂存
  • 信号阻塞

OS可以使用系统调用接口设置位图的数据结构:sigset_t

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
    sigset_t isset, osset;
    sigset_t pending;

    //清空结构体内容
    sigemptyset(&isset);
    sigemptyset(&osset);

    //设置3号信号的屏蔽字——block位图
    sigaddset(&isset, 3);
    //设置
    sigprocmask(SIG_SETMASK, &isset, &osset);

    while(1)
    {
        printf("hello world\n");
        sigemptyset(&pending);
        sigpending(&pending); // 读出pending的位图

        int count = 0;
        int i = 1;
        for(; i <= 31; i++)
        {
            if(sigismember(&pending, i))
                printf("1");
            else
                printf("0");
        }
        printf("\n");
        
        count++;
        if(count == 10)
        {
            sigprocmask(SIG_SETMASK, &osset, NULL);  //将旧的信号屏蔽字写回
        }
        sleep(1);                                                                          
    }
    return 0;
}

  使用ctrl + \ 发现无法终止该程序,原因是3号信号被屏蔽了。另外使用kill -3 + PID也无法杀死该进程,但是会使得对应的pending位图置1。过10秒后,信号屏蔽字被重新置0,进程会默认立即终止,不会再执行后面的语句。
  处理器通常在某些控制寄存器中使用模式位来限制应用程序可以执行的指令,以及它可以访问的地址空间部分。在内核模式下运行的进程可以执行指令集中的任何指令,并访问系统中的任何内存位置。未设置模式位时,进程以用户模式运行。用户模式下的进程不允许执行特权指令,这些指令执行诸如暂停处理器、更改模式位或启动I/O操作等操作。也不允许直接引用地址空间内核区域中的代码或数据。进程从用户模式更改为内核模式的唯一方法是通过异常,例如中断、故障或陷阱系统调用。当异常发生时,控制传递给异常处理程序,处理器将模式从用户模式更改为内核模式,处理程序以内核模式运行。当它返回到应用程序代码时,处理器将模式从内核模式更改回用户模式。
信号执行的过程可以总结为:

  用户态进入内核态调用系统函数,从内核态返回用户态进行相应的信号检测。若无信号产生直接返回用户态,若产生信号,则执行信号的相应处理过程(默认、忽略、自定义捕捉),信号捕捉在用户态执行,执行完重新返回内核。

信号捕捉的接口函数:

sighandler_t signal(int signum, sighandler_t handler);

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

struct sigaction
{
	void (*sa_handler) (int); // 捕捉函数
	...
	..
	sigset_t sa_mask; //设置进程捕捉过程中的屏蔽字
}
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

void handler(int signum)
{
    while(1)
    {   
        printf("signal number:%d\n",signum);
        sleep(1);                                                                          
    }   
}

int main()
{
    struct sigaction act;
    memset(&act, 0, sizeof(act));
           
    //act.sa_handler = SIG_IGN; //将某一个信号设置成忽略
    //act.sa_handler = SIG_DFL; // 将某一个信号设置成默认
    act.sa_handler = handler; 
    //在执行信号自定义捕捉时,系统会设置信号屏蔽字,防止自定义捕捉被调用多次

    sigemptyset(&act.sa_mask); 
    //手动设置信号的屏蔽字,当执行3号信号捕捉时,该2号的信号被屏蔽
    sigaddset(&act.sa_mask, 2); 

    sigaction(3, &act, NULL); // 注册3号信号    
    return 0;
}

参考资料

[1]: 《Computer Systems:A Programmer’s Perspective》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值