操作系统——Linux信号处理

本文详细介绍了Linux信号处理的基本概念,包括中断、信号、常见信号类型及其处理方式。讨论了信号的来源、捕获机制,以及信号处理函数的注册与使用。还涉及到了信号的发送方法,如键盘快捷键、错误情况、命令和函数。此外,文章阐述了进程休眠、信号集和信号阻塞的概念,并展示了如何使用`sigprocmask`进行信号屏蔽。最后,文章提到了附带数据信息的信号处理和定时器的使用,如`sigaction`和`setitimer`函数的应用。

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

Linux信号处理

一、基本概念

    1、中断

        当程序接收到消息后中止当前正在执行的程序,转而执行其它任务,等其它任务执行完成后再返回,这种执行模式叫做中断

        分为硬件中断和软件中断

    2、信号

        是一种软件中断,由操作系统发出,程序接收后会执行相应的操作

    3、常见的信号

        命令 kill -l 查看所有信号

        SIGINT(2)   Ctrl+c          终止

        SIGQUIT(3)  Ctrl+\          终止+core

        SIGFPE(8)   除零            终止+core

        SIGKILL(9)  终止信号  终止  不能捕获、忽略

        SIGSEGV(11) 非法访问内存    终止+core

    4、不可靠信号和可靠信号

        建立在早期的信号处理机制上(1~31)的信号是不可靠信号

            不支持排队、可能会丢失,如果同一个信号连续产生多次,进程可能只相应了一次

        建立在新的信号处理机制上(34~64)的信号是可靠信号

            支持排队、信号不会丢失

    5、信号的来源

        硬件异常:除零、非法访问内存、未定义的指令、总线错误

        软件异常:通过一些命令、函数产生的信号

    6、信号的处理方式

        1、忽略

        2、终止进程

        3、终止进程并产生core文件(记录内存映像)

        4、停止

        5、继续

        6、捕获并处理   (在信号发生前,先向内核注册一个函数,当信号来临时系统会自动执行该函数)

二、信号捕获  

    typedef void (*sighandler_t)(int);

    功能:说明信号处理函数的格式

    sighandler_t signal(int signum, sighandler_t handler);

    功能:向内核注册一个信号处理函数

    signum:信号编号

    handler:函数指针

        也可使用以下参数

        SIG_IGN 忽略

        SIG_DFL 按默认方式处理

    返回值:

        之前的信号处理方式

#include <stdio.h>

#include <signal.h>

void sigint(int num)

{

    printf("你别乱按!%d\n",num);    

}

int main(int argc,const char* argv[])

{

//  signal(SIGINT,sigint);

//  signal(SIGQUIT,sigint);

    for(int i=1; i<=31; i++)

    {

        signal(i,sigint);  

    }

    for(;;);

}

    注意:

        1、SIGKILL(9)、SIGSTOP(19)信号不能被捕获和忽略处理

        2、当信号处理完后可能会返回产生信号的代码继续运行,如果我们捕获并处理段错误、算术异常等信号可能会产生死循环,正确的处理段错误、算数异常信号应该是备份数据并直接结束程序

        3、有些系统通过signal注册的信号处理函数只能执行一次,如果想要持续有效,可以在信号处理函数中再重新注册一次

        4、子进程会继承父进程的信号处理方式,但是通过exec系列函数创建的子进程,会恢复默认的信号处理方式

   

    信号的发送方式:

    1、键盘

        Ctrl+c

        Ctrl+\

        Ctrl+z 暂停\挂起  fg 继续

    2、错误

        除零

        非法访问内存

        总线错误

    3、命令

        kill -信号编号 进程号

        功能:向指定的进程发送信号

        killall -信号编号 进程名

            可以给同名进程发送同一个信号

    4、函数

        int kill(pid_t pid, int sig);

        功能:向指定进程发送指定信号

        pid:进程号

        sig:信号编号

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <signal.h>

int main(int argc,const char* argv[])

{

    for(int i=10; i<=31; i++)

    {

        printf("%d\n",kill(4127,i));    

        sleep(1);

    }

}


 

        int raise(int sig);

        功能:向进程自己发送信号sig

        void abort(void);

        功能:向进程自己发送信号SIGABRT

        unsigned int alarm(unsigned int seconds);

        功能:让内核在seconds秒后向进程发送SIGALRM信号

        返回值:上次alarm设置的剩余秒数

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

void sigalrm(int num)

{

    printf("起床了!\n");  

}

int main(int argc,const char* argv[])

{

    signal(SIGALRM,sigalrm);

    printf("1----%d\n",alarm(10));

    sleep(3);

    printf("2----%d\n",alarm(10));

    pause();

}

三、进程休眠信号

    int pause(void);

    功能:让调用者进入休眠状态,直到进程遇到信号才会唤醒

    返回值:要么不返回在休眠,要么唤醒后返回-1

    相当于没有时间限制的sleep

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

void sigint(int num)

{

    printf("有人叫我!\n");  

}

int main(int argc,const char* argv[])

{

    signal(SIGINT,sigint);

    printf("我准备睡了!\n");

//  printf("%d\n",pause());

    printf("%u\n",sleep(3));

    printf("我醒来~\n");

}

    unsigned int sleep(unsigned int seconds);

    功能:让调用者进入休眠指定的秒数,当遇到信号时会提前唤醒返回

    返回值:剩余的休眠时间

四、信号集和信号阻塞

    信号集:是一种数据类型,定义的变量可以存储多个信号

    sigset_t 128位的二进制数,每一位都固定代表了一种信号

    相关函数:

    int sigemptyset(sigset_t *set);

    功能:清空信号集

    int sigfillset(sigset_t *set);

    功能:填满信号集

    int sigaddset(sigset_t *set, int signum);

    功能:向信号集set中添加信号signum

    int sigdelset(sigset_t *set, int signum);

    功能:从信号集set中删除信号signum

    int sigismember(const sigset_t *set, int signum);

    功能:测试信号集中是否存在signum信号

    返回值:

        0   不存在

        1   存在

        -1  signum信号非法

#include <stdio.h>

#include <signal.h>

int main(int argc,const char* argv[])

{

    sigset_t set;

    printf("size:%d\n",sizeof(set));

    sigemptyset(&set);

    sigfillset(&set);

//  sigaddset(&set,5);

    sigdelset(&set,5);

    for(int i=1; i<=128; i++)

    {

        printf("i=%d,ret=%d\n",i,sigismember(&set,i));  

    }

}

    信号阻塞:

        当程序执行到一些特殊操作时,不适合处理信号,此时可以让内核先屏蔽信号,等操作执行完成后再解除屏蔽重新发送信号

        当信号产生时,内核会在其维护的信号表中为对应的进程设置与该信号对应的标记,这个过程叫做递送

        从信号产生到完成递送有个时间间隔,处于这个间隔的信号状态称为未决

        信号屏蔽\阻塞就是让被屏蔽的信号先处于未决状态、暂停递送,当屏蔽解除时在继续递送

     int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

     功能:设置要屏蔽的信号、这些信号是存储在信号集里面

     how:信号屏蔽的方式

        SIG_BLOCK   把set中的信号添加到要屏蔽的信号集里

        SIG_UNBLOCK 从信号屏蔽集中删除set中的信号 解除

        SIG_SETMASK 把set替换之前的信号屏蔽集

    set:准备好的信号集

    oldset:获取旧的信号屏蔽集

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

void sigint(int num)

{

    printf("你别乱按!\n");  

}

void sigquit(int num)

{

    printf("你还乱按!\n");  

}

int main(int argc,const char* argv[])

{

    signal(SIGINT,sigint);

    signal(SIGQUIT,sigquit);

    //  定义信号集变量

    sigset_t set, old_set;

    //  清空信号集

    sigemptyset(&set);

   

    //  添加要屏蔽的信号到信号集

    sigaddset(&set,SIGINT);

    sigaddset(&set,SIGQUIT);

    sigaddset(&set,SIGKILL);

    //  屏蔽信号

    sigprocmask(SIG_BLOCK,&set,&old_set);

    sleep(5);

    //  解除屏蔽

    sigprocmask(SIG_SETMASK,&old_set,NULL);

   

    for(;;);

}

五、附带数据信息的信号处理

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

    功能:向内核注册一个信号处理函数

    signum:要捕获的信号编号

    act:   设置要处理的动作

    olact: 获取原来的处理动作

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

void sigint(int num,siginfo_t* info,void* ptr)

{

    printf("我是附带%s信息的信号处理函数\n",(char*)info->si_ptr);

}

int main(int argc,const char* argv[])

{

    struct sigaction act = {};

    act.sa_sigaction = sigint;

    act.sa_flags = SA_SIGINFO;

    sigaction(SIGINT,&act,NULL);

    sleep(3);

    union sigval val = {};

    val.sival_ptr = "呵呵";

    sigqueue(getpid(),SIGINT,val);

    for(;;);

}

    struct sigaction {

        void (*sa_handler)(int);// 不附带其他信息的信号处理函数  

        void (*sa_sigaction)(int,siginfo_t *,void *);

        // 附带其他信息的信号处理函数

        sigset_t   sa_mask; //  信号屏蔽集

        int        sa_flags;//  信号处理动作标志

            SA_NODEFER 在信号处理过程中不要屏蔽当前信号

            SA_SIGINFO  使用 sa_sigaction 函数指针

        void     (*sa_restorer)(void); // 保留函数

    };

     siginfo_t {

        int      si_signo;     /* Signal number */

        int      si_errno;     /* An errno value */

        int      si_code;      /* Signal code */

        int      si_trapno;    

        pid_t    si_pid;       /* Sending process ID */

        uid_t    si_uid;       /* Real user ID of sending process */

        int      si_status;    /* Exit value or signal */

        clock_t  si_utime;     /* User time consumed */

        clock_t  si_stime;     /* System time consumed */

        sigval_t si_value;     /* Signal value */

        int      si_int;       /* POSIX.1b signal */

        void    *si_ptr;       /* POSIX.1b signal */

        int      si_overrun;   /* Timer overrun count;

                                    POSIX.1b timers */

        int      si_timerid;   /* Timer ID; POSIX.1b timers */

        void    *si_addr;      /* Memory location which caused fault */

        long     si_band;      /* Band event (was int in

                                    glibc 2.3.2 and earlier) */

        int      si_fd;        /* File descriptor */

        short    si_addr_lsb;  /* Least significant bit of address

                                    (since Linux 2.6.32) */

        void    *si_call_addr; /* Address of system call instruction

                                    (since Linux 3.5) */

        int      si_syscall;   /* Number of attempted system call

                                    (since Linux 3.5) */

        unsigned int si_arch;  /* Architecture of attempted system call

                                    (since Linux 3.5) */

    }

    int  sigqueue(pid_t  pid,int sig, const union sigval value);

    功能:向指定进程发送信号并附加信息一起发送

    value:附加的信息

    union sigval {

               int   sival_int; //  整数

               void *sival_ptr; //  指针

           };

六、定时器

    #include <sys/time.h>

    int getitimer(int which,struct itimerval *curr_value);

    功能:获取当前的定时方案

    which:选择使用的计时器

        ITIMER_REAL     真实计时器 程序总的计时时间

        ITIMER_VIRTUAL  虚拟计时器 用户态的计时

        ITIMER_PROF     实际计时器 用户态+内核态计时

        真实计时器 = 实际计时器 + 休眠时间 + 切换时间


 

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

    功能:设置定时器计时方案

        which:选择使用的计时器

        ITIMER_REAL     真实计时器 程序总的计时时间SIGALRM(14)

        ITIMER_VIRTUAL  虚拟计时器 用户态的计时SIGVTALRM(26)

        ITIMER_PROF     实际计时器 用户态+内核态计时

        SIGPROF(27)

    struct itimerval {

        struct timeval it_interval;

        // 每次时钟信号产生的间隔时间

        struct timeval it_value;    

        //  第一次产生时钟信号的时间

    };

     struct timeval {

        time_t      tv_sec;   //    设置秒3

        suseconds_t tv_usec;  //    设置微秒50000

    };

#include <stdio.h>

#include <signal.h>

#include <sys/time.h>

void sigalrm(int num)

{

    printf("定时器响了%d\n",num);    

}

int main(int argc,const char* argv[])

{

    signal(SIGALRM,sigalrm);

    signal(SIGVTALRM,sigalrm);

    struct itimerval value = {{5,0},{3,0}};

    setitimer(ITIMER_VIRTUAL,&value,NULL);

    for(;;);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoyu1381

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值