信号

41 篇文章 0 订阅

信号(p161)

  • 每个进程收到的所有信号,都是由内核负责发送,内核处理。
  • 信号的响应方式:
    1.忽略信号:信号处理完成后忽略/丢弃。
    2.执行系统默认动作:1.终止进程,2.终止进程且core文件,3.忽略,4.暂停stop,5.继续。
    3.捕捉信号:不让信号执行默认动作,按自己规定的方式执行。
  • 信号屏蔽字(阻塞信号集set):将某些信号加入集合,对他们设置屏蔽,当屏蔽该信号后,再接收到该信号,该信号的处理将推后。
  • 未决信号集(信号未处理,产生的抵达之间的状态):
    1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理时,对应位翻转回为0。这一时刻往往非常短暂。
    2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
  • 信号屏蔽字影响未决信号集
Linux常规信号一览表

1)SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2)SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
3)SIGQUIT:当用户按下<ctrl+>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信,默认动作为终止进程。
4)SIGlLL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5)SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止里程并产生core文件。
6)SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7)SlGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8)SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9)SIGKlLL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法
10)SIGUSR1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11)SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
金.
12)SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13)SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
14)SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
15)SIGTERM:程序结束信号,与SGKIL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kil时,缺省产生这个个信号。默认动作为终止进程。
16)SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17)SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。子进程的运行状态发生变化的时候,会向他的父进程发送SIGCHLD信号
18)SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19)SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20)51GTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
21)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
22)SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
23)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
24)SlGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
25)SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
26)SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
27)SGlPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
28)SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
AEANE4
29)SIGIO:此信号向进程指示发出一个异步IO事件。默认动作为忽略。
30)SIGPWR:关机,默认动作为终止进程
31)SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
34)SIGRTMIN ~(64)SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

  • 9号SIGKILL和19号SIGSTOP信号,不允许忽略和捕捉,不能阻塞。
按键终止信号
  • ctrl+c:(2)SIGINT,中断,interrupt
  • ctrl+z:(20)SIGTSTP,暂停,停止,terminal
  • ctrl+\:(3)SIGQUIT:退出
kill系统调用允许进程将信号发送给某个进程或进程组

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

pid == 0:将sig发送给调用进程同组的进程,包括进程自己。
pid == -1:进程有发送信号权限时,把sig发送给处init进程之外的所有进程(权限:CAP_KILL)
pid< -1:sig发送给进程组号为pid绝对值的进程。

sig:最好写英文宏名称
成功:返回0,失败:返回-1
  • raise函数
给当前进程发信号
#include<signal.h>
int raise(int sig);
成功:0,失败:非0
  • abort函数
使进程异常终止
#include<signal.h>
void abort(void);
返回值:无
调用此函数使内核向调用进程发送SIGABRT信号并使进程异常结束
  • alarm函数
设置定时器,当指定seconds后,内核会给当前进程发送SIGALRM信号,终止当前进程。
每个进程都有定时器,且只有一个
#include<unistd.h>
unsigned int alarm(unsigned int seconds);

返回上一个定时器的剩余时间,如果没有则返回0。

取消定时器:alarm(0);返回旧闹钟余下的秒数
  • 例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>    

typedef void (*sighandler_t)(int);

void caught()
{
        printf("the signal is caught\n");
}

void caught_sigaction()
{
        printf("the signal is caught\n");
}

void mysleep_signal(unsigned int seconds)   方法1
{
        sighandler_t sig = signal(SIGALRM,caught);
        alarm(seconds);
        int pau = pause();
        if(pau == -1)
        {
                printf("pause successful\n");
        }
        alarm(0);
}

void mysleep_sigaction(unsigned int seconds)  方法2
{
        struct sigaction *act;
        act = (struct sigaction *)malloc(sizeof(struct sigaction));
        struct sigaction *oldact;
        oldact = (struct sigaction *)malloc(sizeof(struct sigaction));

        act->sa_handler = caught_sigaction;
        act->sa_flags = 0;
        sigemptyset(&act->sa_mask);

        int sig = sigaction(SIGALRM, act,oldact);
        if(sig == -1)
        {
                perror("sigaction error:");
                exit(1);
        }
        alarm(seconds);
        pause();

        alarm(0);
        sigaction(SIGALRM,oldact,oldact);
}
int main()
{
        while(1)
        {
                mysleep_signal(3);
                printf("sleep_signal......\n");
                
                mysleep_sigaction(3);
                printf("sleep_sigaction......\n");
        }
        return 0;
}
  • setitimer函数(实现微秒级定时,周期定时)
int setitimer(int which,const struct itimerval *new_value,struct itimerval *old_value);

在这里插入图片描述
在这里插入图片描述

  • signal允许进程设置信号的响应形式(信号捕捉)
#include<signal.h>
sighandler_t signal(int signum,sighandler_t handler);

signum:信号值
handler:信号指定的响应方式(执行操作或处理函数)
成功:返回前一个sighandler_t 的值,失败:返回SIG_ERR
  • 例子1:
#include<stdio.h>
#include<signal.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

typedef void (*sighandler_t)(int);

int num = 0;

void catch()
{
        num++;
        printf("the signal of SIGINT is being [%d]caught!!!\n",num);
}

int main()
{

       sighandler_t sig = signal(SIGINT,catch);
       if(sig == SIG_ERR)
       {
		       perror("signal error");
               exit(1);
       }
        while(1);

        return 0;
}
  • 例子2:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>
#include<stdlib.h>
/*
        struct itimerval {
                struct timeval it_interval;
                struct timeval it_value;
           };

        struct timeval {
                time_t      tv_sec;
                suseconds_t tv_usec;
*/
typedef void (*sighandler_t)(int);

void sigfunc()
{
        printf("the signal of SIGALRM is coming!!!\n");
        }

int main()
{
        struct itimerval time1,time2;

        sighandler_t sig = signal(SIGALRM,sigfunc);
        if(sig == SIG_ERR)
        {
                perror("signal error:");
                exit(1);
        }
        time1.it_interval.tv_sec = 1;两次任务之间的时间间隔
        time1.it_interval.tv_usec = 0;
        time1.it_value.tv_sec = 1;定时时间
        time1.it_value.tv_usec = 0;

        int setitime = setitimer(ITIMER_REAL,&time1,&time2);
        if(setitime == -1)
        {
        		perror("setitimer error:");
                exit(1);
        }
        while(1);

        return 0;
}

信号集设定

sigset_t :信号集数据类型
int sigemptyset(sigset t*set);
将某个信号集清0,成功:0;失败:-1

int sigfillset(sigset_t*set);
将某个信号集置1,成功:0;失败:-1

int sigaddset(sigset t*set,int signum);
将某个信号加入信号集,成功:0;失败:-1

int sigdelset(sigset t*set,int signum);
将某个信号清出信号集,成功:0;失败:-1

int sigismember(const sigset t*set,int signum);
判断某个信号是否在信号集中,返回值:在集合:1;不在:0;出错:-1

sigsett类型的本质是位图。但不应该直接使用位操作,
而应该使用上述函数,保证跨系统操作有效。对比认知select函数

sigprocmask (信号屏蔽字)

  • 用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
  • 严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。
设置或者获取信号屏蔽字
int sigprocmask(int how,const sigset_t*set,sigset_t*oldset);
成功:0;失败:-1,设置errno

set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集。

how参数取值:假设当前的信号屏蔽字为mask
1.SIG_BLOCK:当how设置为此值,set 表示需要屏蔽的信号。相当于mask=mask|set
2.SIG_UNBLOCK:当how设置为此,set 表示需要解除屏蔽的信号。相当于mask=mask &~set
3.SIG_SETMASK:当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask=set
若,调用 sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

sigpending(未决信号)

获取未决信号
#include<signal.h>
int sigpending(sigset_t *set);
set:信号集,传出参数(获取的信号包含在set信号集中)
成功:0,失败:-1
  • 例子:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void print_signal(sigset_t *set)
{
        for(int i = 1;i < 32;i++)
        {
                if(sigismember(set,i) == 1)
                {
                        putchar('1');
                }
                else
                {
                        putchar('0');
                }
        }
        printf("\n");
}
int main()
{
        sigset_t newset,oldset,outset;

        sigemptyset(&newset);
        
        for(int i = 1;i<32;i++)
        {
        sigaddset(&newset,i);
        }
        
        sigprocmask(SIG_BLOCK,&newset,&oldset);
        
        while(1)
        {
        sigpending(&outset);
        print_signal(&outset);
        sleep(1);
        }
        
        return 0;
}

sigaction(测试或设置信号的处理方式)

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

struct sigaction {
     void(*sa_handler)(int); 函数指针,传函数名,回调函数,被内核调用
     void(*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t sa_mask; 指定信号捕捉期间要屏蔽的信号集,仅在执行期间有用
     int sa_flags; 0时默认屏蔽本信号
     void(*sa_restorer)(void); 忽略
           };
           
act为传入参数
oldset为传出参数;返回-1
成功:返回0,失败

在这里插入图片描述

pause函数(时序竟态:程序先后执行顺序不一致)

#include<unistd.h>
int pause(void);

功能:进程调用pause会挂起,直到进程捕捉到一个信号,等待信号唤醒
当捕捉到信号时,返回-1,并且设置errno为EINTR

sigsuspend函数(解除对信号的阻塞&挂起等待同时执行,保证挂起的进程能收到该信号,原子操作p177)

#include<signal.h>
int sigsuspend(const sigset_t *mask);

功能:设置要被阻塞的信号,在对时序要求严格的场合下都应该使sigsuspend.替换pause。
成功:返回0,失败:返回-1

***解决时序问题***
可以通过设置屏蔽 SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”。sigsuspend.函数具备这个功能。

sigsuspend 函数调用期间,进程信号屏蔽字由其参数mask指定。
可将某个信号(如SlGALRM)从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号。

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>

void caught()
{
        printf("the signal is caught\n");
}

void signal1()
{
        //1.设置信号的捕捉方式
    
        struct sigaction *act;
        struct sigaction *oldact;
        act = (struct sigaction *)malloc(sizeof(struct sigaction));
        oldact = (struct sigaction *)malloc(sizeof(struct sigaction));

        act->sa_flags = 0;
        sigemptyset(&act->sa_mask);
        act->sa_handler = caught;

        int sig = sigaction(SIGTSTP,act,oldact);
        if(sig == -1)
        {
                perror("sigaction error:");
                exit(1);
        }

        //2.阻塞SIGALRM信号
        sigset_t *newmask;
        sigemptyset(newmask);
        sigaddset(newmask,SIGTSTP);
        sigsuspend(newmask);

        //恢复
        alarm(0);
        sigaction(SIGTSTP,oldact,oldact);
        }

int main()
{
        while(1)
        {
                signal1();
        }
        return 0;
}
  • 练习:父子进程间交替数数:
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>

int flags = 0,num = 0;
void father()
{
        num +=2;
        printf("I am father,mypid = %d,counting...[%d]\n",getpid(),num);
        flags = 1;
        sleep(1);
}

void child()
{
        num +=2;
        printf("I am child,mypid = %d,counting...[%d]\n",getpid(),num);
        flags = 1;
        sleep(1);
        }

struct sigaction sigact;
int main()
{
        pid_t pid = fork();
        if(pid > 0)
        {

                num = 0;
                sleep(1);
                //signal(SIGUSR1,father);
                sigact.sa_sigaction = father;
                sigact.sa_flags = 0;

                sigemptyset(&sigact.sa_mask);
                sigaction(SIGUSR1,&sigact,NULL);
                father();
                while(1)
                {
                        if(flags == 1)
                        {
                                kill(pid,SIGUSR2);
                                flags = 0;
                        }
                }
        }

        else if(pid == 0)
        {
                num = 1;
                //signal(SIGUSR2,child);
                sigact.sa_sigaction = child;
                sigact.sa_flags = 0;

                sigemptyset(&sigact.sa_mask);
                sigaction(SIGUSR2,&sigact,NULL);
                while(1)
                {
                        if(flags == 1)
                        {
                                kill(getppid(),SIGUSR1);
                                flags = 0;
                        }
                }
        }

        return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值