zoukankan      html  css  js  c++  java
  • Unix进程小结(二)关于信号的一些操作

    一、基本的概念
        1、中断
        中止、暂停当前正在执行的进程,转而去执行其它的任务。
        硬中断:来自硬件设备的中断
        软中断:来自其它程序的中断
        
        2、信号
        信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。

        3、一些常见信号

            SIGINT(2)    终端中断信号Ctrl+c
            SIGQUIT(3)    终端退出信号Ctrl+/
            SIGABRT(6)    调用abort函数产生的信号
            SIGFPE(8)    算术信号
            SIGKILL(9)    死亡信号
            SIGSEGV(11) 段错误信号
            SIGALRM(14) 闹钟信号
            SIGCHLD(17) 子进程结束信号
            SIGCONT(18) 进程继续信号
            SIGSTOP(19) 进程暂停信号
            SIGTSTP(20) 终端停止信号

    4、不可靠信号(非实时)
            1、编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
            2、不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。    
            3、进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。
            
        5、可靠信号(实时)
            1、位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
            2、可靠信号支持排除,不会丢失。
            3、无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction进行处理。

    二、信号的捕获和处理
        #include <signal.h>

        typedef void (*sighandler_t)(int);

        sighandler_t  signal(int  signum, sighan‐
           dler_t handler);
        功能:向注册一个信号处理函数
        signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。
        handler:函数指针
            SIG_IGN 忽略信号
            SIG_DFL 恢复信号默认的处理方式
        返回值:是之前信号处理方式
            函数指针、SIG_IGN、SIG_DFL、SIG_ERR

    (1)eg: signal(SIGINT ,SIG_ING );

    //SIG_ING 代表忽略SIGINT信号,SIGINT信号代表由InterruptKey产生,通常是CTRL +C 或者是DELETE 。发送给所有ForeGround Group的进程。

    下面我们写个死循环:

    #include

    #include

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

    {

    signal(SIGINT,SIG_IGN);

    for(;;);

    return 0;

    }

    这时我们保存执行。

    按下CTRL _C程序没有反应。这就对了

    如果我们想结束该程序可以按下CTRL +来结束

    其实当我们按下CTRL +组合键时,是产生了SIGQUIT信号

    (2)eg: signal(SIGINT ,SIG_DFL );

    //SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作时终止该进程。这与不写此处理函数是一样的。

    我们将上面的程序改成:

    #include

    #include

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

    {

    //signal(SIGINT,SIG_IGN);

    signal(SIGINT,SIG_DFL)

    for(;;);

    return 0;

    }

    这时就可以按下CTRL +C 来终止该进程了。把signal(SIGINT,SIG_DFL);这句去掉,效果是一样的。

    (3) void ( *signal( int sig, void (* handler)( int )))( int );

    int (*p)();

    这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.

    int (*fun())();

    这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.

    void (*signal(int signo, void (*handler)(int)))(int);就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.

    在写信号处理函数时对于信号处理的函数也是void sig_fun(int signo);这种类型,恰好与上面signal()函数所返回的函数指针所指向的函数是一样的.void ( *signal() )( int );

    signal是一个函数, 它返回一个函数指针, 后者所指向的函数接受一个整型参数 且没有返回值, 仔细看, 是不是siganal( int signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是 signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时, signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针, 并且又指向信号处理函数, 就开始执行它)

    三、子进程的信号处理
        1、通过fork创建的子进程会继承父进程的信号处理方式。
        2、通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。
        练习:测试通过vfork+exec创建的子进程是否会继承父进程的信号处理方式。

    四、信号的发送与kill命令以及kill函数的一些操作

    kill命令是使跟在它后面的进程结束,学习了信号我们知道,它应该是给进程发送了让进程结束的信号才能完成这一功能。

    如何给进程发送信号呢。这里就要用到kill函数;

    它的原型如下;

    int  kill(pid_ pid,int signum);   返回值含义:成功0 失败-1

                      接受者  信号类型

    kill命令发送的是什么信号呢?我们知道让进程结束的信号有SIGINT。

    可是kill其实还有kill和kill -9之分。kill不一定能杀死进程,kill -9却能100%杀死进程。这是为什么呢?

    难道还有其他信号能使进程结束?

    答案是肯定的。

    下面用一个简单的程序来实现简单的kill命令

    1. #include<stdio.h>
    2. #include<signal.h>
    3. #include<unistd.h>
    4. #include<stdlib.h>
    5.  
    6.  
    7.  
    8. int main(int argc,char* argv[])
    9. {
    10.  
    11. if(argc==1)
    12. {
    13. printf("error cmd! ");
    14. return -1;
    15. }
    16. int pid=atoi(argv[2]);
    17. int sig=atoi(argv[1]);
    18. kill(pid,sig);
    19. }

    atoi函数将命令行中的字符串参数转换成整型,这样才能传递给kill函数!

    这个程序最简单的操作是通过进程号杀死一个进程!

    四、pause/sleep/alarm函数的一些操作


        #include <unistd.h>
        int pause(void);
        功能:休眠
        
        1、进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。
        2、当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。
        3、pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
        4、从功能上来讲它相当于没有时间限制的sleep函数。
        

        #include <unistd.h>
        unsigned int sleep(unsigned int seconds);
        功能:使用调用的进程睡眠seconds秒
        
        1、调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
        2、sleep的返回值是0,或剩余的睡眠秒数。
        3、相当于有时间限制的pause
        
        int usleep(useconds_t usec);v
        功能:睡眠usec微秒
        
        1秒=1000毫秒=1000000微秒。
        它是一种更精确的睡眠函数。
        
       
        #include <unistd.h>
        unsigned int alarm(unsigned int seconds);
        功能:定时一个闹钟信号
        
        1、让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号。
        2、SIGALRM信号的默认处理方式是直接退出。

    下面通过一个简单的定时操作来让大家感受一下这几给函数

    1. #include<stdlib.h>
    2. #include<signal.h>
    3.  
    4. void sigalrm(int signum)
    5. {
    6. printf("我接受到了%d ",signum);
    7. }
    8.  
    9. int main(int argc,char* argv[])
    10. {
    11. signal(SIGALRM,sigalrm);
    12. if(argc<1)
    13. {
    14. printf("error cmd! ");
    15. return -1;
    16. }
    17. int sec=atoi(argv[1]);
    18. alarm(sec);
    19. pause();
    20. printf("定时%d秒! ",sec);
    21.  
    22. }

    此程序通过alarm和pause的配合简单实现了一个定时功能!先让程序休眠,在规定时间后alarm发送一个信号,pause也就结束了

    达到了定时的目的!

    五、信号集和信号屏蔽
        1、信号集:
            多个信号的集合,sigset_t
            由128个二进制位组成,每个二进制位表示一个信号
            
            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,失败返回-1
        2、屏蔽信号集中的信号
            每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。
            
            int sigprocmask(int how, const  sigset_t
           *set, sigset_t *oldset);
           功能:设置进程的信号掩码(信号屏蔽码)
           how:修改信号掩码的方式
                   SIG_BLOCK:向信号掩码中添加信号
                   SIG_UNBLOCK:从信号掩码中删除信号
                   SIG_SETMASK:用新的信号集替换旧的信号掩码
           newset:新添加、删除、替换的信号集,也可以为空
           oldset:获取旧的信号掩码
           当newset为空时,就是在备份信号掩码

        当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。
        屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。
        当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
        不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
        可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
        在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
        
        int sigpending(sigset_t *set);
        功能:获取末决状态的信号
       

    1. #include <stdio.h>
    2. #include <unistd.h>
    3. #include <signal.h>
    4.  
    5. // 更新数据
    6. void updata(void)
    7. {
    8. for(int i=0; i<10; i++)
    9. {
    10. printf("更新第%d条数据... ",i);
    11. sleep(1);
    12. }
    13. }
    14.  
    15. void sigint(int signum)
    16. {
    17. printf("收到信号%d,正在处理... ",signum);
    18. }
    19.  
    20. int main()
    21. {
    22. signal(34,sigint);
    23. printf("我是进程%d ",getpid());
    24. // 定义信号集
    25. sigset_t set,oldset;
    26. // 初始化信号集
    27. sigemptyset(&set);
    28. // 向信号集中添加信号
    29. printf("向信号集中添加%d %s ",34,sigaddset(&set,34)?"失败":"成功");
    30. // 设置信号掩码
    31. printf("设置信号掩码%s ",sigprocmask(SIG_SETMASK,&set,&oldset)?"失败":"成功");
    32.  
    33. updata();
    34. sigpending(&set);
    35. for(int signum=1; signum<65; signum++)
    36. {
    37. if(sigismember(&oldset,signum))
    38. {
    39. printf("默认屏蔽的的信号%d ",signum);
    40. }
    41. }
    42. printf("还原信号掩码%s ",sigprocmask(SIG_SETMASK,&oldset,NULL)?"失败":"成功");
    43. pause();
    44. }

    这个代码就是一个简单的屏蔽信号的实例,这里就不过多赘述!

    总之:信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。

    UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。

  • 相关阅读:
    Extjs 4 生成饼状图的例子
    cocos2d-x 3.0rc2 对于每个包执行情况的重要平台 (超级方便)
    HDOJ 1495 非常可乐 【BFS】
    Android采用HttpClient下载图片
    解决本地访问Android文档是非常慢的问题
    潜在语义分析Latent semantic analysis note(LSA)原理及代码
    你奋斗这么辛苦,这辈子要证明什么?
    Objective-C时间戳转换的转换和时间
    Delphi 线程resume 不能调用Execute
    Delphi 多线程 “尚未调用CoInitialize错误”的解决方法
  • 原文地址:https://www.cnblogs.com/dachao0426/p/9367811.html
Copyright © 2011-2022 走看看