linux 异步信号处理机制

1800阅读 0评论2013-07-28 蜗niu漫步
分类:LINUX

PS: 本文很长,如果可以,请分几天学习!还附上了几个简单程序。主要给入门的孩子学习的。请高手飘过!本文主要讲解linux中的信号机制。

linux 信号是一种进程间异步通信机制。实际上是一种软中断机制。

    linux 下信号的生命周期如下:
        1)    在目的进程中安装该信号。即是设置捕获该信号时进程进程该执行的操作码。采用signal();sigaction()系统调用来实现。
        2)    信号被某个进程产生,同时设置该信号的目的进程(使用pid),之后交给操作系统进行管理。采用kill()、arise()、alarm()等系统调用来实现。
        3)    信号在目的进程被注册。信号被添加进进程的PCB(task_struct)中相关的数据结构里——未决信号的数据成员。信号在进程中注册就是把信号值加入到进程的未决信号集里。
                并且,信号携带的其他信息被保留到未决信的队列的某个sigqueue结构中。
        4)    信号在进程中注销。在执行信号处理函数前,要把信号在进程中注销。对于非实时信号(不可靠信号),其在信号未决信号信息链中最多只有一个sigqueue 结构,因此该结构被释放后,相应的信号要在未决信号集删除。而实时信号(可靠信号),如果有多个sigqueue,则不会把信号从进程的未决信 号集中删除。
        5)    信号生命的终结。进程终止当前的工作,保护上下文,执行信号处理函数,之后回复。如果内核是可抢占的,那么还需要调度。

    linux 信号的产生:
        1)    用户在终端输入,如Ctrl+C则产生终止的信号。
        2)    硬件产生的异常信号。
        3)    软件异常产生的信号。

        4)    进程终止的信号。例如某进程给另一个进程(包括自身)发送信号,如调用kill()函数可以发送信号给一进程或者进程组。
        在终端下输入命令--->ps  显示如下:
     

       之后可以输入指令:kill -SIGABRT XXXX           其中SIGABRT 为要发给指定进程的信号,这个的信号默认操作为终止进程,而XXXX很明显就是进程的PID啦。。。。。注意这里的kill不是指杀死的意思。是发送信号!
    linux 信号的发送:


        1)    进程发送
信号给自己,用raise()函数。extern int raise(int __sig)    其中__sig为信号的值。
        2)    进程发送信号给其他的进程,用 kill() 函数。 extern int kill(__pid_t __pid ,int __sig)   其中__pid 很明显就是某进程的pid,而__sig同上。我们会问怎么知道某个进程的pid啊。?哈哈这里有个函数--> getpid()。这样我们可以利用getpid() 与kill() 这两个函数完成raise() 的功能。kill( getpid(), __sig )。其实我们会注意到,如果当有两个C文件用GCC编译链接后为A , B 两个可执行文件,我们在编程时不知道其运行时的PID,即如果要A要给B发送信号,但我们在编程时A是不能知道B文件运行时的PID的。那样A在编程时,kill() 中的PID不能确定,不能给B发送信号啊!怎么办。。。? 哈哈,我这里有个思路!!在C程序文件中利用fork()函数产生一个子进程,在子进程中执行原来A程序文件的内容(又或者在子进程中调用execX系列的系统调用,执行一个新的程序,而这个新的程序就是B的可执行文件了。),而B程序文件则在父进程中执行,这样就可以知道B的PID啦,怎么知道?getpppid() 就得咯。
       3)    aralm() 定时器,在一定时间内产生SIGALRM信号。  extern unsigned int    alarm( unsigned int __sec) 其中__sec是以秒为单位的时间。默认的情况下,在进程接收alarm() 的信号后会终止的。这里要注意的是,子进程并不继承父进程的alarm 信号。在调用exec()执行新的程序的时候,原来设置的alarm 仍然有效。

    linux 信号的安装:
        1)    安装信号函数,signal()。 extern _sighandler_t signal(int __sig, _sighandler_t __handler)     其中__sig为接收到的信号,__handler 为接收信号后处理代码的入口。__handler 可以为以下的3个宏————>SIG_DFL(默认的操作), SIG_ERR(返回错误), SIG_IGN(忽略信号).
                例子:        void  my_sig_handler(ing __sig){
                                                    printf("i got a sig %d\n",__sig);
                                            }
                                  main(){
                                        signal(SIGUSR1, my_sig_handler);

                                        } 
            因为 signal() 只能安装简单的信号,在linux里其已近开始逐渐被淘汰了。取而代之的是强大的sigaction()
        2)    安装信号函数,sigaction()。 extern  int ssigaction(int __sig , struct  sigaction * __N_act ,struct sigaction * __O_act)。
               其中,__sig是要安装的信号,__N_act是描述信号要执行的操作与相关的信息。如果 __N_act 为NULL,那么信号的处理将不会改变。__O_act存储在执行这个信号之前这个信号安装的信息。     
   

               struct sigaction {

                     union {
                                      __sighandler_t _sa_handler;        //对信号处理的函数
                                      void (*_sa_sigaction)(int, struct siginfo *, void *);        //信号捕获函数
                                  } __u;
                     sigset_t sa_mask;                    //在执行信号捕获的期间要屏蔽的其他信号
                     unsigned long sa_flags;          //这是影响信号行为的标记
                     void (*sa_restorer)(void);       //这个是没有被使用的
                    };
             #define sa_handler   _u._sa_handler       // 对union中两个成员重定义
             #define sa_sigaction _u._sa_sigaction
            
                这里对各个成员进行解释,sa_handler 与sa_sigaction两者取其一即可。使用前者则与signal()函数一样,而后者则有专门的用途!sa_mask为信号集,执行信号捕获函数时要添加到进程的屏蔽信号集的信号集。要注意的是,不会将SIGSTOP与SIGKILL 添加到进程的屏蔽信号集中,  这是系统强制执行用来给超级用户终止普通进程用的。sa_flags 是影响信号行为的标记。对于sa_flags有以下几种取值,可以为其中的一个也可以为其中的几个。
               1:SA_NOCLDSTOP,在子进程退出的时候不会生成SIG_CHLD信号。
               2:SA_ONSTACK ,设置了这个标志并且使用sigaltstack()或者sigstack() 声明备份信号堆栈,信号会传递给这个堆栈中的调用进程,否则,信号在当前堆栈中传递。
               3:SA_RESETHAND ,如果设置了这个标志,sigcation()的行为如同设置了SA_NODEFER标志。而且处理后信号的处理方法将重置为SIG_DFL,并且进入信号处理程序后将清除SA_SIGINFO标志。
               4:SA_RESTART ,影响可中断函数的行为,中断函数会失败。而且会设置errno全局变量。
               5:SA_SIGINFO ,如果没有设置这个标志并且捕获了信号,信号的捕获函数会以以下的形式:void func (int __sig) ,这样必须使用sa_handler 成员来描述信号捕获函数,而且不许修改sa_sigaction的成员。
               6:SA_NOCLDWAIT.如果设置这个标志,并且sig等于SIGCHLD,那么子进程在终止时不会转化为僵死进程。如果调用的进程之后等待其子进程,并且调用进程没有非等待的子进程(已经变为僵尸进程),那么调用的进程会阻塞直到所有的子进程都终止,并且wait()系列的函数都会失败。
               7:SA_NODEFER ,设置这个标志并且捕获到信号,那么进程进入信号处理程序后,信号将不会添加到进程的屏蔽信号集中,除非,将其添加到sa_mask成员。否则,在进入信号处理程序后,sig始终会添加到进程屏蔽信号集中。
               ps:。。。。。这么多。。不要记。。用时候拿出来查下就得啦。。。。对于这两个安装函数,由于signal()有系统漏洞,一般不建议使用。
     
          
        linux 安装信号:
                1)    先来区分一下信号的屏蔽与忽略。
                               信号的屏蔽:就是给进程发送信号,可是进程不捕获信号,而是让这个信号处于未决状态。当信号不再屏蔽这个信号的时候,才去捕获该信号。
                               信号的忽略:系统仍然传递这个信号给进程,但进程不对这个信号任何处理。
                
                2)    信号集:为了方便管理进程的多个信号,linux 使用了信号集。
                                  定义在不同的版本是不同的。以下是2.6.39的。linux-2.6.39.1/include/asm-generic/signal.h
                                  typedef struct {
                                                unsigned long sig[_NSIG_WORDS];
                                        } sigset_t;

                3)    设置屏蔽信号集:extern int sigprocmask(int __how, __const sigset_t *__restrict __set, sigset_t * __restrict __oset ) 调用成功返回0,否则为-1.
                                   其中参数解释如下:
                                  __how 指示如何修改屏蔽信号
                                 __set是一个非空指针时,根据__how修改屏蔽信号
                                  __oset是一个非空指针时,存放当前屏蔽信号集
                                  若__set为NULL,不改变该进程的信号屏蔽字,__how也无意义,这是可以查看进程的屏蔽信号集!保存在__oset中。.zip
                                  __how的取值:
                                  SIG_BLOCK
                                  该进程新的信号屏蔽字是其当前信号屏蔽字和__set指向信号集的并集。__set包含了我们希望阻塞的附加信号
                                  SIG_UNBLOCK
                                  该进程新的信号屏蔽字是其当前信号屏蔽字和__set所指向信号集的交集。__set包含了我们希望解除阻塞的信号
                                  SIG_SETMASK
                                  该进程新的信号屏蔽是set指向的值
               4)    获取未决信号量:extern int sigpending(sigset_t *__set); 调用成功返回0;否则为-1。
               5)    信号量的操作:
                              1 : 清空信号集。extern int sigemptyset(sigset_t * __set); 其中,__set 为要操作的信号集。
                              2 : 将所有信号加入set集合 。extern int sigfillset(sigset_t * __set);
                              3 : 添加信号到指定的信号集中。extern int sigaddset(sigset_t * __set);
                              4 : 把信号从信号集中删除。extern int sigdelset(sigset_t * __set,int __sig); 其中__sig 为要添加的信号。
                              5 : 检测信号集是否为空。extern int sigisemptyset(__const sigset_t * __set);
                              6 : 检查信号是否在某一信号集中。extern int sigismember(__const sigset_t * __set,int __sig);
                              7 : 按逻辑与的方式把两个信号集合并。extern int sigandset(sigset_t * __set,__const sigset_t * __set_l,__const sigset_t * __set_r);     其中__set_l与__set_r是要合并的两个信号集,逻辑与后结果合并到__set中。
                              8 :  按逻辑或的方式把两个信号集合并。extern int sigorset(sigset_t * __set,__const sigset_t * __set_l,__const sigset_t * __set_r);     其中__set_l与__set_r是要合并的两个信号集,逻辑或后结果合并到__set中。  
                              以上函数如果调用成功返回0,否则为-1,而且7、8两个函数调用错误时会设置errno。


        linux等待信号    
              1) pause()函数;extern int pause(void); pause() 使当前的进程处于等待的状态,直到进程屏蔽信号外的任意一个信号出现去“拯救”他。
              2) sigsuspend(__sigset_t * __set); sigsuspend()函数将当前进程的屏蔽信号集替换为__set信号集。直到收到非__set信号集中的信号后继续执行。当进程恢复时会自动回复到原来的信号集。


         linux中常用的信号:
 *    +--------------------+------------------+
 *    |  POSIX signal      |  default action  |
 *    +--------------------+------------------+
 *    |  SIGHUP            |  terminate          |
 *    |  SIGINT             |    terminate        |
 *    |  SIGQUIT           |    coredump       |
 *    |  SIGILL              |    coredump       |
 *    |  SIGTRAP           |    coredump       |
 *    |  SIGABRT/SIGIOT    |    coredump  |
 *    |  SIGBUS            |    coredump        |
 *    |  SIGFPE             |    coredump        |
 *    |  SIGKILL            |    terminate(+)    |
 *    |  SIGUSR1          |    terminate         |
 *    |  SIGSEGV          |    coredump        |
 *    |  SIGUSR2          |    terminate         |
 *    |  SIGPIPE            |    terminate         |
 *    |  SIGALRM          |    terminate         |
 *    |  SIGTERM          |    terminate         |
 *    |  SIGCHLD          |    ignore              |
 *    |  SIGCONT          |    ignore(*)          |
 *    |  SIGSTOP           |    stop(*)(+)        |
 *    |  SIGTSTP            |    stop(*)            |
 *    |  SIGTTIN            |    stop(*)            |
 *    |  SIGTTOU          |    stop(*)             |
 *    |  SIGURG            |    ignore             |
 *    |  SIGXCPU           |    coredump       |
 *    |  SIGXFSZ           |    coredump       |
 *    |  SIGVTALRM       |    terminate        |
 *    |  SIGPROF           |    terminate         |
 *    |  SIGPOLL/SIGIO  |    terminate         |
 *    |  SIGSYS/SIGUNUSED  |    coredump  |
 *    |  SIGSTKFLT         |    terminate         |
 *    |  SIGWINCH         |    ignore               |
 *    |  SIGPWR             |    terminate          |
 *    |  SIGRTMIN-SIGRTMAX |    terminate  |
 *    +--------------------+------------------+
 *    |  non-POSIX signal  |  default action  |
 *    +--------------------+------------------+
 *    |  SIGEMT            |  coredump    |
 *    +--------------------+------------------+
                PS:终于写完了。很长!!!!!!
                注:主要参考资料《linux高级程序设计第三版》、linux内核、网上资料。



上一篇:没有了
下一篇:Linux内核中的红黑树