信号的产生以及处理

信号

kill -l :查看系统中的信号列表。 


1 -31号信号为普通信号; 34-64号信号为实时信号;
信号产生的方式:
(1)键盘产生信号:eg:ctl + c
(2)异常产生:异常产生信号导致进程终止;
当一个进程由于异常被终止时:可以选择把进程的用户空间数据保存到硬盘上,文件名通常是core;这叫做 core Dump(核心转储);
注:
a:保存这些数据可以进行事后调试,从而判断进程异常退出的原
b:由于安全问题,系统一般默认不允许产生core文件;
c:通过ulimit -a可以显示系统中的软硬件资源上限;
d:通过ulimit -c 1024:将系统中core文件的大小设置为1024;

由于0不能作为被除数,所以我写一个bug的程序,借助core文件进行事后调试: 

#include<stdio.h>
int main()
{
    int i = 0;
    int x = 23;
    int ret = x/i;
    printf("the ret is %d",ret);
    return 0;
}

调试结果:


则:很容易的就得知进程异常终止的原因;
进程发生异常导致进程终止的过程:
a:除0操作,产生错误导致内存给管理器(MMU)产生异常;
b:操作系统将此异常理解为一个信号,则发送一个信号给产生该异常的进程;
c:此进程处理信号,信号的默认处理一般都为终止进程,则:此进程被终止。
注:可以通过捕捉该信号来自定义该信号的操作,可以使该进程不退出;(某些信号不能被捕捉,例如:9号信号)
(3)系统调用产生信号:
int kill(pid-t pid,int sig); //给进程号为pid的进程发送sig信号;
int raise(int sig);        //进程给自己发送信号;
(4)软件触发产生信号: 
void abort(void);//认为进程是异常的,给进程发送6号信号,使得进程提前退出;
unsigned int alarm(unsigned int second);//间隔second秒后发送一个信号;
信号的处理动作:
(1)默认处理(大部分信号的默认处理为终止进程);
(2)忽略处理;
(3)自定义动作(信号的捕捉);
  信号捕捉函数:
sighandler_t signal(int signum, sighandler_t handler);
参数:signum:表示要捕捉的信号;
         handler:捕捉到该信号后所执行的自定义动作;
信号的表示:      
信号递达:实际执行信号的处理动作称为信号的递达;
信号未决:信号从产生到递达之间的状态称为信号未决;
阻塞信号:当进程把某个信号阻塞以后,信号产生以后就保持未决状态,直到进程解除对于此信号的阻塞;

block表和pending表的实现都是用位图来实现的;
block表:保存进程对于信号的阻塞(屏蔽)情况,下标表示信号,对应下标保存的值表示该信号是否被阻塞;如上图:对于1号信号来说,没有被进程阻塞;则:2号信号和3号信号,都被进程阻塞,即使受到1号和2号信号,也只能处于未决状态,直到进程取消对于这个信号的阻塞;
pending表:保存进程中的信号是否处于未决状态,下标表示信号,对应下表保存的值表示该信号是否处于未决状态。如上图:对于1号和3号信号来说,就是没有产生;对于2号信号来说,已经产生并且处于未决状态;
handler表:保存进程中信号处理信号的动作。如上图:对于1号信号来说:执行的是默认动作(对于大多数信号来说,默认动作就是终止进程);对于2号信号来说:执行的是忽略动作;对于3号信号来说是执行自定义动作(也就是对于信号的捕捉);
解析上表:1号信号没有产生,也没有被进程阻塞,它的处理动作为默认处理;2号信号产生了,但是该信号被该进程阻塞,所以处于未决状态,此信号一直处于未决状态,直到该信号被该进程取消阻塞。当该信号被该进程取消阻塞后:执行该信号的处理动作:忽略处理;3号信号被该进程设置为阻塞,此时并没有产生,若产生,则处于未决状态;
handler表:或许是一个指针数组吧,保存的是都是指针,这些指针分别指向该信号所对应的处理操作。当一个信号被捕捉后,进行自定义的处理时:其实就是修改这个指针的指向。
注:(1)普通信号的发送只能被记录一次,若连续发送,只能记录一次有效的,其余被丢弃(由于位图只有一个bit为来保存数据,所以只                                            能记录该信号是否产生这个状态,不能记录这个信号发送的次数);
(2)实时信号可以通过队列来对于连续发送的信号进行记录,不会被丢弃;

信号集操作函数
 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);  //判断该信号集中是否包含signum这个有效信号;
读取或者更改进程的信号屏蔽字(阻塞信号集) 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:how   (1)how =  SIG_BLOCK;               //对于信号设置屏蔽;
                    (2)how =  SIG_UNBLOCK;         //对于被屏蔽的信号取消屏蔽;
                    (3)how =  SIG_SETMASK;         //设置当前信号屏蔽字为set的值;;
          set:信号集
          oldset:表示未操作之前的信号集,便于恢复;

读取当前进程的未决信号集
int sigpending(sigset_t *set);
参数:set:作为输出型参数,保存的是当前进程的未决信号集;
 
信号捕捉:
信号捕捉是发生在进程从内核态切换到用户态的时候;

(1)当代码在主函数中执行遇到中断,异常或者系统调用时,需要进去内核态进行处理;
(2)在内核态处理完中断要返回用户态之时,系统会自动检查是否有可以递达的信号;
(3)若有可以递达的信号:
  a:当信号的处理动作为默认的时候:(4)系统修改进程的信号的pending字段,并且返回用户态的主函数上次执行的地方;
  b:当信号的处理动作为默认时:(4)大多数信号的默认动作为终止进程)系统会直接将该进程终止;
  c:当信号的处理动作为自定义时:(4)函数进入用户态,返回到信号的处理动作函数,执行次函数;
                                                   (5)信号的自定义处理动作函数执行结束,返回到内核态;
                                                   (6)从内核态返回到 用户态主函数被中断的地方,继续执行。
读取和修改与指定信号相关联的动作:
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
参数:
signo:要操作的信号量;
act:本次操作之后信号量的状态;
oact:信号量被操作之前的状态;
返回值:
返回0表示操作成功;返回-1表示操作失败;

挂起进程,直到有信号递达
int pause(void)
(1)当信号递达的处理动作为默认,那么系统会终止这个进程,pause函数没有机会返回;
(2)当信号递达的处理动作为忽略,那么,此进程继续挂起,不会返回;
(3)当进程的处理动作为自定义时:调用完信号处理函数之后,pause返回-1,pause只有出错的返回值;
使用alarm和pause实现了一个sleep函数:
#include<unistd.h>
#include<stdio.h>
#include<signal.h>
void myhandler()
{}
void mysleep(int timeout)
{
    struct sigaction act ,oact;
    act.sa_handler = myhandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM,&act,&oact);
    alarm(timeout);                     //set a alarm;
    pause();                                  // hung the process;
    int ret = alarm(0);
    sigaction(SIGALRM,&oact,NULL);
}
int main()
{
    while(1)
    {
        mysleep(1);
    }
    return 0;
}
可重入函数
可重入函数也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境,这样的函数就是可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问和静态变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量或静态变量付出一些性能代价

线程安全:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的;否则:线程是不安全的。
注:线程安全都是由全局变量和静态变量引起的

可重入函数和线程安全的联系:
(1)线程安全的函数不一定是 可重入的,但是可冲入的一定是线程安全的;
(2)如果使用互斥锁控制对于临界资源的访问,对于线程安全来讲是安全的,但是对于可重入函数来讲不是可重入的;
(3)对于使用静态变量或者全局变量的,并且没有对其实行保护措施的,既不是线程安全,也不是可重入的,它会造成数据的二义性。


作者水平有限,若有问题,请留言,谢谢!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值