信号深入学习

信号机制

A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,先去处理信号,处理完毕后再继续执行程序。与硬件中断类似——异步模式。但信号是软件层面上实现中断。

信号特质

由于信号是通过软件方法实现,其实现手段导致信号有很强的的延时性。但对于用户来说,这个延迟非常短,不易察觉
每个进程收到的所有信号,都是由内核负责发送的,内核处理。

信号共性

  • 1.简单
  • 2.不能携带大量信息
  • 3.满足某个特殊条件才能发送

信号产生以及处理方式

信号产生

  1. 按键产生 :Ctrl + c Ctrl + z Ctrl + \
  2. 系统调用产生: kill ,raise ,abort
  3. 软件条件产生 :定时器alarm
  4. 硬件异常产生: 非法访问内存(段错误),除零(浮点数例外),内存对齐出错(总线错误)
  5. 命令产生 :如: kill命令

递达:递送并到达进程
未决:产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态

信号处理方式

  1. 执行默认动作
  2. 忽略(丢弃)
  3. 捕捉(调用户处理函数)

阻塞信号集和未决信号集

Linux内核的进程控制块PCB是一个结构体,task struct 除了包含进程id,状态,工作目录,用户ID,
组ID,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决型号集

阻塞信号集(信号屏蔽字)

将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将退后(解除屏蔽后)

未决信号集

1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表示该信号处于未决状态。当信号被处理对应位翻转回为0,这一刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集,在屏蔽解除前。信号一直处于未解决状态。

信号查看

kill -l

可以查看当前信号系统可以使用的信号有哪些
在这里插入图片描述
不存在编号为0的信号,其中1-31号信号称之为常规信号(也叫常规信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同

信号四要素

信号编号,信号名称,信号对应事件,信号默认处理操作
可以使用下面命令查看

man 7 signal 

在这里插入图片描述

常用信号

SIGHUP:1号信号,Hangup detected on controlling terminal or death of controlling process(在控制终端上挂起信号,或让进程结束),ation:term

SIGINT:2号信号,Interrupt from keyboard(键盘输入中断,ctrl + c ),action:term

SIGQUIT:3号信号,Quit from keyboard(键盘输入退出,ctrl+ | ),action:core,产生core dump>>文件

SIGABRT:6号信号,Abort signal from abort(3)(非正常终止,double free),action:core

SIGKILL:9号信号,Kill signal(杀死进程信号),action:term,该信号不能被阻塞、忽略、自定义处理

SIGSEGV:11号信号,Invalid memory reference(无效的内存引用,解引用空指针、内存越界访问),action:core

SIGPIPE:13号信号,Broken pipe: write to pipe with no readers(管道中止: 写入无人读取的管道,会导致管道破裂),action:term

SIGCHLD:17号信号,Child stopped or terminated(子进程发送给父进程的信号,但该信号为忽略处理的)

SIGSTOP:19号信号,Stop process(停止进程),action:stop

SIGTSTP:20号信号,Stop typed at terminal(终端上发出的停止信号,ctrl + z),action:stop

具体的信号采取的动作和详细信息可查看:man 7 signal

kill函数/信号产生

kill命令产生信号:kill -SIGKILL pid

kill函数:给指定的进程发送指定信号(一定不杀死)

  int kill(pid_t pid, int sig);

成功:0
失败:-1
非法,普通用户杀死init进程等权级问题,设置error

pid>0:发送信号给指定进程
pid = 0:发送信号给与调用kill函数进程属于同一进程组的所有进程
pid<0&&pid!=-1:取 |pid| 发送给对应进程组
pid = -1:发送给进程有权限发送的系统中所有权限

eg:

#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<errno.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    pid_t pid=fork();
    if(pid<0)
    {
        perror("fork");
        exit(1);
    }
    else  if(pid==0)//子进程
    {
        printf("child pid = %d ,ppid = %d\n",getpid(),getppid());
        sleep(2);
        kill(getppid(),SIGKILL);
    }
    else//父进程
    {
        printf("parent pid = %d\n",getpid());
        while(1);
    }

    return 0;
}

结果:
在这里插入图片描述

alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送 14 SIGALRM信号。进程收到该信号,默认动作终止

 #include <unistd.h>
 unsigned int alarm(unsigned int seconds);

返回值:
上次定时剩余时间
无错误现象
定时,与进程状态无关(自然定时法)!就绪,运行,终止,挂起(阻塞,暂停),僵尸…无论进程处于何种状态,alarm都会计时。
eg: 编写程序测试你使用的计算机1秒能数对多少个数。

#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<errno.h>
#include<unistd.h>
int main()
{
   int i=0;
   alarm(1);
   while(1)
   printf("%d\n",i++);
   return 0;
}

time命令可以查看程序执行的时间
实际执行时间=系统时间+用户时间+等待时间
在这里插入图片描述

setitimer函数

setitimer函数为设置定时器(闹钟),可替代alarm函数,比alarm函数精确度更高,精度为微秒,可以实现周期定时。

函数头文件为 #include<sys/time.h>
函数原型为 int setitimer(int which,const struct itimerval *new_value,struct itimerval *old_value);

返回值
成功返回 0
失败返回 -1 error
参数

which:指定定时方式
1.自然定时:ITIMER_REAL 计算自然时间
2.虚拟空间记时(用户空间):ITIMER_VIRTUAL 只计算进程占cpu的时间
3.运行时记时(用户+内核):ITIMER_PROF 计算占用cpu及系统调用的时间
old_value:上次定时剩余时间
new_value:新计时时间

struct itimerval 结构体

 struct itimerval {
               struct timeval it_interval; /* Interval for periodic timer */
               struct timeval it_value;    /* Time until next expiration */
           };

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

在这里插入图片描述eg : 编写程序测试你使用的计算机1秒能数对多少个数

#include<stdio.h>
#include<sys/time.h>
int main()
{
    int i=0;
    struct itimerval new_t;
    struct itimerval old_t;
    new_t.it_interval.tv_sec=0;//周期定时  //秒
    new_t.it_interval.tv_usec=0;         //微妙
    new_t.it_value.tv_sec=1;//定时时长    //秒
    new_t.it_value.tv_usec=0;           //微妙
    if(setitimer(ITIMER_REAL,&new_t,&old_t)==-1)
    {
        perror("setitimer error");
        return -1;
    }
    while(1)
    printf("%d\n",++i);
    return 0;
}

信号集操作函数

自定义信号集操作函数

#include <signal.h>

       int sigemptyset(sigset_t *set);  //清空信号集,全置为零

       int sigfillset(sigset_t *set); //填满信号集 ,全置为一

       int sigaddset(sigset_t *set, int signum); //将一个信号添加到信号集中

       int sigdelset(sigset_t *set, int signum);//将一个信号从信号集中移除

       int sigismember(const sigset_t *set, int signum);
       //判断一个信号是否在信号集合中(在返回1,不在返回0)

设置信号屏蔽字和解除屏蔽

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

参数:
how

SIG_BLOCK
设置阻塞

SIG_UNBLOCK
取消阻塞

SIG_SETMASK
用自定义set替换mask

set

自定义set

oldset

旧有的mask

查看未决信号集

  int sigpending(sigset_t *set);

set :传出的未决信号集
eg: 练习阻塞SIGNIT信号并查看

#include<stdio.h>
#include<signal.h>
void print(sigset_t set)
{
    int i=0;
    for(i=0;i<32;i++)
    {
        if(sigismember(&set,i))
        putchar('1');
        else
        putchar('0');
    }
    printf("\n");
}
int main()
{
    sigset_t set;
    sigset_t oldset;
    sigset_t pedset;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigprocmask(SIG_BLOCK,&set,&oldset);
    sigpending(&pedset);
    print(pedset);
    while(1);
    return 0;
}

信号捕捉

signal函数

注册一个信号捕捉函数

 #include <signal.h>

 typedef void (*sighandler_t)(int);  //函数指针

 sighandler_t signal(int signum, sighandler_t handler);

eg:

#include<signal.h>
#include<stdio.h>
void sig_catch(int signo)
{
    printf("catch you ! %d\n",signo);
} 
int main()
{
    signal(SIGINT,sig_catch);
    while(1);
    return 0;
}

sigaction函数

修改信号处理动作(通常在Linux用其注册一个信号的捕捉函数)

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

参数:
act : 传入参数,新的处理方式
old: 传出参数,旧的处理方式

 struct sigaction 
 {
      void     (*sa_handler)(int);  //捕捉到信号后要干的事情
      void     (*sa_sigaction)(int, siginfo_t *, void *);
      sigset_t   sa_mask;    //只作用于信号捕捉期间
      int        sa_flags;
      void     (*sa_restorer)(void);
 };

eg:捕捉信号SIGINT

#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_catch(int signo)
{
    printf("catch you ! %d\n",signo);
} 
int main()
{
    struct sigaction act,oldact;
    act.sa_handler = sig_catch;//set callback function name
    sigemptyset(&act.sa_mask);//清零set mask when sig_catch working
    act.sa_flags=0;            //usually use

    int ret=sigaction(SIGINT,&act,&oldact);
    if(ret==-1)
    {
        perror("error");
        exit(1);
    }
    while(1);
    return 0;
}

信号捕捉特性

  • 1)进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
  • 2)XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
  • 3)阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)

测试:

#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void sig_catch(int signo)
{
    printf("catch you ! %d\n",signo);
    sleep(3);

} 
int main()
{
    struct sigaction act,oldact;
    act.sa_handler = sig_catch;//set callback function name
    sigemptyset(&act.sa_mask);//清零set mask when sig_catch working
    act.sa_flags=0;            //usually use

    int ret=sigaction(SIGINT,&act,&oldact);
    if(ret==-1)
    {
        perror("error");
        exit(1);
    }
    while(1);
    return 0;
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

binary~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值