向其它进程传递信号

来源:互联网 发布:软件测试月薪 编辑:程序博客网 时间:2024/05/10 11:06
本文所有程序均应在UNIX系操作系统下编译执行。

第八章:向其它进程传递信号

上一章,我们简单地说了一下信号。信号不仅仅可以在本进程内使用,还可以向其它的进程传递。确切地说,一般情况下,一个进程收到的信号绝大部分是从系统的INIT传来的。而INIT也是一个进程。
用过UNIX或者LINUX的朋友大概都知道,UNIX系的操作系统里,有一个kill命令。如果你的一个进程无法中止,可以使用kill命令强行干掉它。
%kill mypro
我们自己也可以做个简单的kill命令。

/*      nkill.c */
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>

int     main( int argc , char *argv[] )
{
        pid_t   pid;
        if( argc != 2 )         //      检查命令行参数个数
        {
                fprintf( stderr , "Usage : %s PID /n" , argv[0] );
                exit(1);
        }
        pid = atol( argv[1] );
        if( kill( pid , SIGKILL )!=0 )  //      向pid这个进程传送一个SIGKILL信号
        {
                fprintf( stderr , "kill failed/n" );
        }
        return 0;
}

执行例:
%sleep 300 &            (后台睡眠300秒)
[1] 520
%nkill 520
[1]   +  Killed               sleep 300

你用自己写的程序杀死了一个sleep进程。没事。杀了就杀了吧。留着也是垃圾……
说明一下,众所周知,UNIX是非常注意用户的权限的。所以,如果你向你自己生成的进程发送SIGKILL的话,这个进程会中止。如果你是向别的(你没有写权限的)进程发送SIGKILL的话,除非那个进程允许你动它,否则你发了也是白发。打个不恰当的比较下流一些的比方:如果你要求亲一下你自己老婆的话(你有“亲”的权限),你会如愿以偿。但是如果你想亲一下别人的老婆的话(你没有“亲”的权限),除非他及她同意,否则……呵呵。你死定了。
所以,执行上面的程序的时候,你必须保证你有权限关掉那个进程。可不是说你ps -ef然后随便挑一个进程就能关的。要不那黑客也太好当了。

几乎是咱们的老规矩了:先写个程序,然后解释这个程序,说说其中新出现的函数。这回还照旧吧。
大家看到了。这里新出来一个kill()函数。解释如下:

#include <sys/types.h>
#include <signal.h>

int     kill( pid_t pid , int sig );
返回值:        成功时:0
                失败时:-1
这个函数是向进程ID(PID)为pid的进程传送一个sig信号。
这个函数有什么用呢?用处可大了。
在上一章中,我们用到了SIGKILL信号。这一节我们用的还是这个信号。实际上,信号(signal)有许多。而且根据UNIX系统的不同,这些信号也不太一样。

我们简单列举一些信号例子。其用途请恕小生才疏,解释不清。有兴趣的朋友可以自己查阅一下资料。
SIGHUP , SIGINT , SIGQUIT , SIGILL , SIGTRAP , SIGABRT , SIGEMT , SEGFPE(注意,这里是SEG),SIGKILL , SIGBUS , SIGSEGV , SIGSYS , SIGPIPE , SIGALRM , SIGTERM , SIGUSR1 , SIGUSR2 , SIGCLD , SIGPWR , SIGWINCH , SIGSTOP , SIGTSTP , SIGCONT , SIGTTIN , SIGTTOU
使用kill()函数,可以将这些信号中的一个或者多个送至另一个进程。刚才的程序中,我们就是将一个SIGKILL信号送至sleep那个进程,该进程收到信号,以为是用户按下了Ctrl+C,就中止了。

知道了这些, 我们再来做一个比较有意思的程序。我们做一个cat程序(类似于DOS下的type,查看文本文件的内容)。不过这个cat程序与UNIX的cat不太一样,我们把由参数指定的文件的内容输出到屏幕上。如果你没指定文件,那么这个程序会等你五秒,在这五秒钟里,你可以从标准输入输入一个文件。然后显示。万一这五秒钟里你也没输入什么文件名。程序会把当前目录下的所有文件名打印出来(ls),以督促你输入一个文件名。

/*      show.c  */
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>

jmp_buf env;

int     func();
int     main( int argc , char *argv[] )
{
        char    filename[256];
        signal( SIGALRM , func );       //      如果收到一个SIGALRM信号,则转至func处执行
        if( argc==1 )
        {
                fprintf( stderr , "& " );
                alarm(5);               //      5秒钟后向自己发送一个SIGALRM信号
                setjpm(env);            //      设置记号
                scanf( "%s" , filename );
                execl( "/usr/bin/more" , "more" , filename , (char *)0 );
        }
        else
        {
                execl( "/usr/bin/more" , "more" , argv[1] , (char *)0 );
        }
        return 0;
}
int     func()
{
        int     st;
        fprintf( stderr , "Which file do you want to read ?/n/n" );
        if( fork()==0 )
        {
                execl( "/bin/ls" , "ls" , "-FCa" , (char *)0 );
        }
        wait(&st);
        longjmp( env , 0 );             //      跳转至先前的setjmp()处
}       


这个程序看明白了吗?可能有些不太好懂。我们从头跑一遍试试。
首先是装入头文件等等。没关系了。然后signal( SIGALRM , func ),即,当收到一个SIGALRM信号的时候,中断现在所做的工作。转而执行func()函数。
不过可惜得很,现在暂时还没人给咱们发送SIGALRM信号,我们继续往下看。下面是判断命令行参数了。如果只有这个程序的可执行文件一个参数的话(也就是说,没有指明要显示哪个文件的内容),那就打印出一个&号来。然后设置alarm(5),也就是说,在5秒后给自己发一个SIGALRM信号。下一句setjpm(env)是设置一个记号,以后当执行至longjmp()函数的时候,就会跳转到这里来。然后等待你输入文件名。
如果在这五秒种里,你输入了一个文件名的话,那么就向下执行——通过execl()函数调用系统的more命令,显示该文件的内容。然后退出。这只需要要很少的一段时间。绝对用不上一秒(看你当前目录下有多少文件了。没人会在一个目录下装上1G吧?)。最后结束程序。alarm()在5秒后发出的信号乐意咋地咋地去吧。咱不用管了。
问题是:如果在这五秒钟里,你没有输入文件名的话,事情就变得非常微妙了。这种情况下,程序将停留在
scanf( "%s" , filename );
这一行。等待你输入文件名。然后五秒种的期限到了。alarm()函数向这个进程发了一个SIGALRM信号。signal()函数检测到这个信号,转向func()函数处执行。
func()函数先是输出了一个提示信息 Which file do you want to read ? 。然后fork()出一个子进程。这个子进程通过execl()函数调用/bin/ls命令,显示出当前目录下的所有文件。execl()函数生成的进程覆盖掉了子进程。显示完所有文件名后退出。父进程等待子进程结束(wait())后,执行longjmp()函数,返回到刚才的setjmp那行程序结尾处。继续执行下一步,也就是再执行一次 scanf( "%s" , filename )。当然,上一次的scanf()已经中止了。剩下的就简单地一路到底。明白了?
为什么要fork()出一个子进程,而不用原来的进程呢?是因为execl()函数会覆盖掉当前进程。如果直接使用execl()的话,咱们这个宝贝进程就被execl()覆盖,调用完 /bin/ls 后直接结束退出了。还怎么跳来跳去的呀。呵呵。

好。又多了三个函数:

unsigned        alarm( unsigned sec );
在sec指定的秒数之后,向本进程发送一个SIGALRM信号。
返回值是以前的alarm()函数执行后所剩余的时间。

还记不记得第一章中,咱们做过的闹钟程序了?那个时候是让进程休眠一段时间,然后再报时。现在我们有了另一个法宝——alarm()。这回也别让进程浪费大好时光睡大觉去了。让它做点别的什么事吧。写写试试?

#include <setjmp.h>
int     setjmp( jmp_buf env );                  //      设置跳转记号env
void    longjmp( jmp_buf env , int val );       //      跳转至记号env处。

jmp_buf env就是跳转记号了。那么longjmp第二个参数 int val 是干什么用的呢?当你用longjmp()函数跳转至setjmp()处的时候,setjmp()函数应该会返回一个值吧?这个值就是int val,也就是longjmp()第二个参数。
setjmp()的返回值,在第一次使用的时候是0。在由longjmp()跳转过来的时候,返回值是longjmp()的第二个参数。
但是有一个特例:像刚才程序中所写的,longjmp( env , 0 ),longjmp()第二个参数是 0 的时候,setjmp()的返回值是 1 。

有很多界面比较友好的程序中,当等待用户输入数据的时候,如果用户过了一段时间还没有输入的话,程序会输入一些提示信息,或者是可供选择的选项来供用户参考。在Windows编程中,例如VC,VB等等,大概可以用定时器 timer 或者其它的什么办法来解决(我粗通VB。VC一点都不会)。在UNIX下,上面那样便是一种解决方法。当然。编程习惯不同,解决方法也不尽相同。

信号(signal)这个东西,在UNIX编程中占很重要的地位。笔者一直想用通俗的语言来解释,可是限于笔者的语文水平,所以做到的仅此而已。总的来说,signal可以看成是一种软中断。当一个signal被发送到程序的时候,会引发程序的默认过程(比方说SIGKILL的场合是强制结束)或者程序的作者自定义的过程(用signal()函数指定)。
另外。longjmp()和setjmp()两个函数由于使用了跳转(类似于goto语句,但是longjmp()能实现函数外跳转,就是说能跳到另一个函数里去。goto办不到吧),这不符合结构化程序设计的要求,所以如果不是万不得以,不推荐使用。

下一章准备说说利用共享内存实现两个进程间通信。

本章中要感谢Steven.C老兄。这一系列的贴子发了七章了,访问量加在一起好像刚过百。而回贴的人好像只有老兄你一位。看到有一个人回了我的贴子,我当时真热泪盈眶。多谢支持啦。


wait(等待子进程中断或结束)   
  相关函数   
  waitpid,fork   
  表头文件   
  #include<sys/types.h>   
  #include<sys/wait.h>   
  定义函数   
  pid_t   wait   (int   *   status);   
  函数说明   
  wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status   返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则   
  参数   
  status可以设成NULL。子进程的结束状态值请参考waitpid()。   
  返回值   
  如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。 
  转载自 http://club.it.sohu.com/read_art_sub.new.php?b=program&a=21573&NoCache=1