信号(SIGNAL)

信号是Linux unix系统下经典的消息机制,系统利用信号完成处置进程(杀死与挂起)

kill -l 查看系统下支持的信号

1~31Unix经典信号(软件开发) 34~64自定义信号(驱动层开发) 32 33系统隐藏信号(NPTL)系统预留给线程使用。

Linux系统下发送信号的几种方式

1.终端组合按键触发信号

ctl+c(SIGINT(2))

ctl+\(SIGQUIT(3))

ctl+z(SIGTSTP(20))(jobs查看挂起进程,fg 序号 运行到前台 bg 序号 运行到后台)

这三个信号的目标都是唯一的终端前台进程。

核心已转储:

段错误(核心已转储):当进程异常退出,系统可以将异常信息存储在core文件中 后序使用gdb快速定位异常。

默认情况下系统不允许生成core文件,可以使用ulimit 命令允许生成。

调试:gcc 名 -g -o 名     gdb 名 

改成4096或者4096的整数倍。

这样就会有core文件了 然后gdb 名 core就ok了,简单的能用,特别复杂的错误不行。

2.命令触发信号

kill -signo pid//向任意进程发送任意信号

3.函数触发信号

kill(pid_t pid int signal);//向任意进程发送任意信号

raise(int signo);//向调用进程发送任意信号

abort();//向调用进程发送SIGABRT(6)信号

用kill函数实现kill命令。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
int main(int argc,char** argv){
if(argc<3){
    printf("try agin\n");
    exit(0);
}
kill(atoi(argv[2]),atoi(argv[1]));
        return 0;
}

4.硬件异常触发信号

系统向用户进程发送信号。

段错误(核心已转储)用户违法访问内存引发异常,系统会向用户发送SIGSEGV(11)杀死目标。

总线错误(核心已转储)内存访问越界,系统发送SIGBUS(7)信号杀死目标。

浮点数例外(核心已转储)如果cpu出现计算异常,系统发送SIGFPE(8)杀死目标进程。

5.软条件触发信号

(1).alarm(20)定时器,定时到时系统向定时进程发送SIGALRM(14)通知其到时。但是这个信号默认杀死进程。

(2)管道读端关闭,写端向管道写数据,系统会发送SIGPIPE(13),杀死写端进程。

unsigned int alarm(unsigned int seconds)//定时函数,返回值是之前设置的闹钟的剩余秒数。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int main()
{

    int a = alarm(5);
    printf("%d\n",a);
    sleep(2);
    a=alarm(2);
    printf("%d\n",a);
    while(1){

    }
    return 0;
}

信号的三种行为和五种默认处理动作

默认行为SIG_DFL,忽略行为SIG_IGN捕捉行为SIG_ACTION

五种处理动作:TREM(杀死进程)CORE(杀死进程核心转储)STOP(挂起进程)CONT(继续进程)IGN(忽略信号)

每个信号都被设置了自身的行为与动作,后续可以通过修改改变信号行为。

行为的优先级大于动作。

如果将某个信号的行为改为忽略,此信号失效(无法处置进程)

捕捉函数,用户自定义行为,可以对某个信号设置与函数的绑定,信号触发调用对应函数。捕捉也可以让信号失去原有效果,无法杀死或挂起进程。

struct sigaction act;//信号行为结构体

act.sa_handler=sig;//用户设置信号行为(void sig(int)用户自定义行为)

act.sa_flags=0;//默认选项

sigemptyset(&act.sa_mask);//初始化临时屏蔽字:如果不同信号绑定一个捕捉函数,捕捉函数中有对全局资源的访问,那么两个信号同时递达,引发冲突异常。可以采用临时屏蔽字避免此情况的发生,在某个信号执行捕捉函数时临时屏蔽其他信号。

sigaction(SIGINT,&act,&oldact|NULL);//修改信号行为,传入新结构体,传出原有行为结构体。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void sig(n)
{
    printf("你好 %d\n",n);
}
int main()
{
struct sigaction act,oct;
act.sa_handler=sig;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,&oct);
while(1) sleep(1);
return 0;
}

信号的传递过程

如果信号无法通过未决信号集,信号直接丢弃。

信号通过未决信号集,系统会将对应位翻转为1,表示待处理。

信号从未决态转换为递达态,系统会将未决信号集对应位设置为0.

用户可以自行设置屏蔽字,实现阻塞信号的目的。

经典信号不支持排队。

要想查看进程中信号的实时屏蔽情况,应该查看哪个信号集?

查看未决信号集,用户可以读取未决信号集。

相关函数

sigset_t set//信号集类型

sigemptyset(&set)//初始化函数,将所有位初始化0

sigfillset(&set)//初始化函数,将所有位初始化1

sigaddset(&set,int signo)//将set信号集中signo对应的信号位设置为1。

sigdelset(&set,int signo)//将set信号集中signo对应的信号位设置0。

bitcode=sigismember(&set,int signo);//查看信号集中的某一位是0或1直接返回。

sigprocmask(SIG_SETMASK(直接覆盖原有屏蔽字), &newset,&oldset);//使用自定义信号集替换进程原有屏蔽字,实现阻塞信号,传入替换的屏蔽字传出原有的屏i蔽字

SIG_BLOCK默认和自定义两个信号集进行位或运算得出新信号集合

SIG_UNBLOCK默认和自定义两个信号集进行取反求与运算得出新信号集合

sigpending(&pset)//可以将进程的未决信号集传出到pset变量中。

sigpending+sigismember可以遍历查看未决信号集。

屏蔽信号2:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int get_sig(sigset_t sig)
{
    int i;
    for(i=1;i<=31;i++){
    if(sigismember(&sig,i))
        putchar('1');
    else putchar('0');
    }
    putchar('\n');
    return 0;
}
int main()
{
    sigset_t sig1,sig2,sig3;
    sigemptyset(&sig1);
    sigaddset(&sig1,SIGINT);
    sigaddset(&sig1,SIGQUIT);
    sigprocmask(SIG_SETMASK,&sig1,&sig2);
   while(1){
   sigpending(&sig3);
   get_sig(sig3);
   sleep(1);
   }
   return 0
}

屏蔽和忽略很像但是不一样,忽略是已经递达了,屏蔽是没递达。

输出未决信号集:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int get_sig(sigset_t sig)
{
    int i;
    for(i=1;i<=31;i++){
    if(sigismember(&sig,i))
        putchar('1');
    else putchar('0');
    }
    putchar('\n');
    return 0;
}
int main()
{
    sigset_t sig1,sig2,sig3;
    sigemptyset(&sig1);
    sigaddset(&sig1,SIGINT);
    sigaddset(&sig1,SIGQUIT);
    sigprocmask(SIG_SETMASK,&sig1,&sig2);
   while(1){
   sigpending(&sig3);
   get_sig(sig3);
   sleep(1);
   }
   return 0
}

高优先级信号

如果所有的信号都失效,那么系统安全收到威胁,需要一些高级信号为系统提供保障。

sigkill(9),sigstop(19),只要发出必然递达,无法被屏蔽捕捉忽略。

临时屏蔽

如果一个信号在执行捕捉函数,系统会临时屏蔽此信号,避免相同信号递达调用捕捉函数引发冲突。

也就是说如果一个信号递达了然后系统会把屏蔽字临时设为1,下一个信号在未决态,所以第三个第四个后续的信号都被丢弃了。当第一个信号处理完毕,临时屏蔽解除,第二个信号可以递达处理。

经典信号不支持排队,但是因为临时屏蔽的关系可以排队一个。

后面的自定义信号支持排队处理。

信号处理优先级

操作系统发信号给内核层。

(1)进程在用户空间串行执行主函数代码

(2)信号抵达进程等待处理

(3)进程需要通过系统调用,中断,异常方式进行层级转换

(4)完成首要任务,完成调用,处置中断或处置异常

(5)上述任务完毕,在返回用户空间前检测是否有未处理信号,有则处理

(6)cpu保留内核级权限到用户空间执行捕捉函数

(7)执行完捕捉函数后,执行一条1SIG_RETURN返回内核

(8)返回用户空间

(9)从main被暂停的位置继续执行代码

信号的优先级比较低,信号一定会被处理。

一般情况下永远是主函数先执行,在执行过程中触发信号,系统调用捕捉函数,捕捉函数会比主函数先执行完。

捕捉函数执行时,占用进程本身的时间片资源,不会额外分配资源,捕捉函数执行时,主函数暂停。

捕捉与主函数使用全局资源,引发异常。

可重入与不可重入函数

函数的参数有全局或静态资源,这类函数称为不可重入函数,主函数与捕捉函数同时调用时引发冲突。

可重入函数,只要使用局部数据,不与其他人共享资源,这类函数都是安全的,信号就可以放心使用。

系统函数也开发了对应的可重入版本,例如readdir_r,strtok_r等,信号可以放心使用。

主动回收 阻塞/非阻塞

主动回收是不合理的,应该采用被动回收方案,如果子进程结束,系统给父进程发送通知,父进程完成回收。

子进程变僵尸进程,父进程用wait()等待系统发信号SIGCHLD(处理动作为IGN忽略)通知父进程回收。阻塞,父进程自己业务一点不跑。

非阻塞跑50%,所以我们想被动回收希望父进程更自由点跑80%业务。

我们想被动回收通知回收,父进程要有信号捕捉设定,和信号捕捉函数,在子进程创建之前完成捕捉设定,捕捉函数回收次数,如果按信号数量决定回收次数那么信号不支持排队会被丢弃,会产生漏回收,导致大量僵尸,一个信号回收多次,将当前可回收的全部回收即可。

#include<signal.h>
#include<sys/wait.h>
void jobs()
{
    while(1)
    {

        printf("I am running\n");
        sleep(1);
    }
}
void sig()
{
pid_t pi;
while((pi=waitpid(-1,NULL,WNOHANG))>0){
    printf("waitpid sucess,pid %d\n",pi);
}
}
int main()
{
    pid_t pid;
    int i;
    for(i=0;i<10;i++){
        pid=fork();
        if(pid==0)break;
    }
    struct sigaction act,oldact;
    act.sa_handler=sig;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGCHLD,&act,&oldact);
    if(pid>0)
    {
     jobs();
 }
    else if(pid==0)
    {
        printf("son %d\n",getpid());
        exit(i);
    }
    else exit(0);
    return 0;
}

信号实现外部控制

我们想实现暂停和继续,首先我们得有一个脚本程序,它想获得调用的目标进程就得有进程的pid,进程的pid可以存个配置文件里对应上,然后脚本程序发信号,不希望用信号SIGSTOP(19),SIGCONT(18),因为挂起会将数据交换到外存,我们用开发可用的信号SIGUSER1,SIGUSER2,目标进程捕捉SIGUSER1,调用Pause()暂停,捕捉SIGUSER2,不编码产生空调用,因为默认情况下这两个信号是杀死进程,Pause()是接收到信号就继续了。

使用信号实现进程间通信

父子进程交叉报数,数据为通信数据,相互传递,处理和打印。

信号选择:SIGUSER1,SIGUSER2

sigqueue(pid_t pid,int signo,union sigval);//给目标发信号发联合体

联合体的两个成员:union sigval val; val.sival_int//整型, val.sival_ptr;//void*指针

act.sa_sigaction(int n,siginfo_t* info,void*arg)

info->si_int=1//接收整型数据

info->si_ptr=0x010//接收指针数据

act.sa_flags=SA_SIGINFO;

捕捉设定做到:报数,自增,回传

父进程屏蔽SIGUSER1,而后创建子进程,子进程继承屏蔽字,出生即屏蔽SIGUSR1信号,而后子进程完成捕捉设定,解除屏蔽即可。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>
pid_t sonpid;
void parent_sig(int n,siginfo_t* info,void* arg)
{
    printf("parent %d %d\n",getpid(),info->si_int);
    union sigval val;
    val.sival_int=++(info->si_int);
    sigqueue(sonpid,SIGUSR1,val);
    usleep(100000);
}
void son_sig(int n,siginfo_t* info,void*arg)
{
    printf("son %d %d\n",getpid(),info->si_int);
    union sigval val;
    val.sival_int=++(info->si_int);
    sigqueue(getppid(),SIGUSR2,val);
    usleep(100000);
}
int main()
{
    sigset_t psig;
    sigemptyset(&psig);
    sigaddset(&psig,SIGUSR1);
    sigprocmask(SIG_SETMASK,&psig,NULL);
    pid_t pid;
    pid=fork();
    if(pid>0)
    {
    sonpid=pid;
    struct sigaction act;
    act.sa_sigaction=parent_sig;
    act.sa_flags=SA_SIGINFO;
    sigemptyset(&act.sa_mask);
 sigaction(SIGUSR2,&act,NULL);
    union sigval val;
    val.sival_int=1;
    sigqueue(sonpid,SIGUSR1,val);
    while(1)
        sleep(1);
    }
    else if(pid==0)
    {
    struct sigaction act;
    act.sa_sigaction=son_sig;
    act.sa_flags=SA_SIGINFO;
    sigemptyset(&act.sa_mask);
    sigaction(SIGUSR1,&act,NULL);
    sigprocmask(SIG_SETMASK,&act.sa_mask,NULL);
    while(1)sleep(1);
    }
    else{
        perror("fork error\n");
        exit(0);
    }
}

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值