文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>从kernel源代码的角度分析signal的错误用法和注意..

从kernel源代码的角度分析signal的错误用法和注意..

时间:2006-06-04  来源:rwen2012


!声明: 按照Linux的习惯, 我的这篇文档也遵循GPL 协议: 你可以随意应用并修改本文档,必须发布你的修改,使其他人可以获得一份Copy,尤其是给我一份Copy! 我的mail :[email protected] | [email protected] 均可。欢迎论坛转载! 目前有些内容已经在 www.linuxforum.net中进行过讨论,可以前往:http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=607800&page=0&view=&sb=&o=&fpart=&vc=1   和  http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=607228&page=1&view=collapsed&sb=5&o=7&fpart=   欢迎大家继续讨论,以便文档更加完善! 多谢!周末愉快!                                 
--bob
读这份文档之前,建议先浏览一下 《Unix Advanced Programming》里面的signal一章和下面这份出自IBM论坛的文章:进程间通信 信号(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html  ,和 进程间通信 信号(下)http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 该作者写了一个系列的进程间通信的文章, 我只是希望对该篇作个补充!
因为它们都没有从源代码的角度分析,所以我尝试了一下把上层应用与kernel实现代码分析结合起来,这样使用者才可能真正的理解signal的用法和原理!

目前介绍signal理论和用法书不少,缺点是只介绍其用法,非常深奥拗口,不容易理解;  而介绍kernel源代码的书,侧重于代码分析,不讲实际应用!
我就想到如果把两者结合起来,对上层使用signal函数的用户必然能知起所以然了,而且只要顺着我的代码注释大概粗读一下源码就可以理解 signal的特性和用法以及你碰到的种种疑惑和不解了。
如果你对signal的特性和用法有什么疑惑的话, 如果对kernel也感兴趣的话, 就可以继续读源码 , 把这篇文章加以补充和完善!  前提是遵守上面的声明!

 

因为工作的需要,用了2天的时间详细的读了一下 linux kernel 2.4.24 版本的signal方面的源代码,收获不小, 因为以前发现看<<Unix Advanced Programming>>的时候 ,不知道是大师的话太深奥,还是中文版太烂,有的东西就是理解不了,象吃满头嗫住了,很是不爽,总觉得心里不踏实。看看源码才真正明白什么是信号,以及它的kernel流程,所以建议大家对某个系统调用,函数什么的,如果存在疑惑和不理解的,强烈建议读读源码,粗读也非常不错,关键要由参考书领着读,比如<<linux kernel源代码 情景分析>> 就非常不错。


有的时候看着一个系统调用成堆的手册页,还真不如看看它的实现来得更快, 当然两下对照着看就快了。  

另外提醒大家 <<UNIX Adanced Programming>> 可不是 《Linux Advanced Programming》啊!尽信书不如无书 ......


在此通过阅读源码,弄清楚了5个问题,每个问题我都给出了结论,当然这些结论肯定是正确的,至少《Unix Advanced Programming》是这样认为的, 我只是从kernel的角度是验证它的正确性(简单的写了几个测试程序,以验证kernel的做法),而且也归纳了 一些结论,比如如何避免 Zobie进程 等。  相信对大家会有价值,也可以mail讨论!或者上相应的论坛!


首先总结一下:在PC linux(RHT 9.0 + kernel-2.4.24) 键盘产生的信号:
Ctrl + c     SIGINT(2) terminate ,以前我总想当然以为是 SIGTERM(15)!
Ctrl + \ SIGQUIT(3) terminate
Ctrl + z SIGTSTP(20) 挂起进程

对于一般应用:
挂起一个进程: kill(pid, SIGSTOP)   或 kill(pid,SIGTSTP) , 或 SIGTTIN , SIGTTOU 信号
恢复一个进程  kill(pid,SIGCONT); 
杀死所有的符合某个名字的进程 :比如killall  curl ,发送的是SIGTERM 信号
强制杀死某个进程 kill –9 curl  ,发送的是SIGKILL 信号, 在kernel中,SIGKILL和SIGSTOP是不能被忽略的
....
剩下的大家都清楚了,这里就不罗嗦了。
子进程结束时候发给父进程的信号:   SIGCHLD ,这个比较特殊 , 且看下面3>的论述


Agenda :
1>不可靠的信号
2>Zombie进程(僵尸进程)与signal
3>特殊的SIGCHLD 信号
4>信号与进程的关系 ,进程的要求
5>pause() 与 signal
6>关于信号的技巧

1> 不可靠的信号(linux继承Unix的结果,考虑兼容性) ,  和可靠的信号(主要就是信号可以排队处理,信号不丢失,linux自己的,但大家好像用的不多)
什么是不可靠的信号:简单的说,就是当你向一个进程发送 singal( 1~31,注意这里讨论是 1~31 )的时候 , 当进程还没有处理该信号(这时候叫pending,未决信号)或者是正在调用信号处理函数的时候,  进程又收到了一个同样的信号 , kernel会把第二个信号丢弃,或者叫和一个信号合并,这样的信号就是 不可靠的信号  ,具体正方面的比较权威的解释请参考 http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html ,这篇文章对于信号理论介绍的非常详细清楚明白, 个人认为比《Unix advanced Programming》要更好!

系统实现是这样的:
==>  kernel/signal.c 
 int send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
 .............................................
 /*  
  如果当前进程的未决信号集中已经包括了这个信号,就不重新注册后来现在的同样的信号了, 
  据个例子:  给进程发了 SIGTERM 信号 , 但是kernel还没有来得及处理(进程只有在kernel空间即将返回道用户空间的时候,
  kernel才会检测pending信号 ,然后才会调用do_signal()函数去处理)
  这个时候又发了一个SIGTERM,那么第二个SIGTERM 肯定要被cut掉了。
 */
 if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig))  //SIGRTMIN 是分水岭 , 小于它的都是不可靠的信号,否则就是实时信号
  goto out;  //跳出了正常执行的范围
 ....................................................
}


!正确的: 1~31都是不可靠的信号! SIGRTMIN ~SIGRTMAX都是可靠的信号!


以前大家有个误区:

!误区1>
以为不可靠的信号,是指 给进程发了一个信号(之前没有发过),那么这个信号可能丢失,也就是进程收不到
 这样的理解是错误的, 根据上面的定义 , 应该是”一个信号发了多遍,后来的信号丢失了, 而不是第一个丢了“。
 具体的原因可以参照上面的代码分析,就一目了然,还可以看 《unix advanced programming 》,不过我觉得它讲的都是老的Unix ,对Linux只能是参考而已!
!误区2>
 signal() 发送的是不可靠的信号 ,而 sigaction()发送的是可靠的信号
 
 
只要是1-31的信号,它就是不可靠的信号。 无论在注册信号处理函数的时候用的是sigaction() ,还是signal() ,只要你发送的信号 是  1-31,那么就是不可靠的信号。中国有句俗语叫”烂泥扶不上墙“,我看放在这里挺合适!

signal()和 sigaction()的差别到底在哪里呢?   通过对比一看便知:
   对于signal() ,它的kernel实现函数,也叫系统调用服务历程sys_signal()

==>kernel/signal.c
asmlinkage unsigned long
sys_signal(int sig, __sighandler_t handler)
{
 struct k_sigaction new_sa, old_sa;
 int ret;

 new_sa.sa.sa_handler = handler;
 new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
    //SA_ONESHOT:当执行一次信号处理程序后, 马上恢复为SIG_DFL ,
    //SA_NOMASK : 表示在信号处理函数执行期间,不屏蔽的当前正在处理的那个信号

 ret = do_sigaction(sig, &new_sa, &old_sa);   //sys_sigaction 也调用这个函数

 return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}

而sigaction()函数的kernel实现是: sys_sigaction()
==>arch/i386/kernel/signal.c
asmlinkage int
sys_sigaction(int sig, const struct old_sigaction *act,struct old_sigaction *oact)
{
 struct k_sigaction new_ka, old_ka;
 int ret;

 if (act) {
  old_sigset_t mask;
  if (verify_area(VERIFY_READ, act, sizeof(*act)) ||
      __get_user(new_ka.sa.sa_handler, &act->sa_handler) ||
      __get_user(new_ka.sa.sa_restorer, &act->sa_restorer))
   return -EFAULT;
  __get_user(new_ka.sa.sa_flags, &act->sa_flags);
  __get_user(mask, &act->sa_mask);
  siginitset(&new_ka.sa.sa_mask, mask);
 }

 ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);//都调的这个函数

 if (!ret && oact) {
  if (verify_area(VERIFY_WRITE, oact, sizeof(*oact)) ||
      __put_user(old_ka.sa.sa_handler, &oact->sa_handler) ||
      __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer))
   return -EFAULT;
  __put_user(old_ka.sa.sa_flags, &oact->sa_flags);
  __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask);
 }

 return ret;
}
signal()和sigaction() 都是用do_signaction()来包装的, 都是用 struct sigaction()这个结构体的,差别在下面标出来了

 struct sigaction {
 __sighandler_t sa_handler;  //2// typedef void (*__sighandler_t)(int);  signal()和sigaction()函数都要求要户提供信号处理函数
 unsigned long sa_flags; //signal()函数默认就用 SA_ONESHOT | SA_NOMASK;  //sigaction()要由用户自己指定!
 void (*sa_restorer)(void); //没用了
 sigset_t sa_mask;    //执行信号处理函数的时候要阻塞的信号,signal()使用默认的,就屏蔽正处理的信号,其他的不屏蔽,sigaction() 要求用户自己指定!
};


? 讨论时间: 读到这里我有个疑问:sys_signal()函数明明把 sa_flags = SA_ONESHOT | SA_NOMASK; 而且在kernel执行信号处理函数之前,它会检查SA_ONESHOT标志 ,如果有这个标志,  就把sa_handler = SIG_DFL ,如果是这样的话, 我们需要反复注册某个信号的处理函数才行啊, 但是事实上,我们并没有这样作,而且程序运行的很好!

Kernel的signal()函数实现代码如下:
 
 ==>arch/i386/kernel/signal.c
static void
handle_signal(unsigned long sig, struct k_sigaction *ka,
       siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)
{
 ...........................................................
 /* Set up the stack frame */
 if (ka->sa.sa_flags & SA_SIGINFO)
  setup_rt_frame(sig, ka, info, oldset, regs);
 else
  setup_frame(sig, ka, oldset, regs);
//here , 我加了debug信息, 确实执行到这里了,
 if (ka->sa.sa_flags & SA_ONESHOT){  //sys_signal()函数明明设置了这个标志
  //通过debug ,知道居然没有到这里,就说明, sa_flags 根本就没有SA_ONESHOT标志了 ,可是sys_signal() 却又明明设置了这个标志, 而且我搜索过, 根本没有地方,取消了 SA_ONESHOT 标志
  printk("<0> the signal (%d) handler will reset to SIG_DFL\n",sig);
  ka->sa.sa_handler = SIG_DFL;  //这难道还不明确吗?

 if (!(ka->sa.sa_flags & SA_NODEFER)) {
  spin_lock_irq(&current->sigmask_lock);
  sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
  sigaddset(&current->blocked,sig);
  recalc_sigpending(current);
  spin_unlock_irq(&current->sigmask_lock);
 }
}

既然这样的话  ,如果我们调用signal()就应该在信号处理函数中反复注册自己的信号处理函数才对 , 否则无法处理下一个同样的信号了。
比如 void signal_catch(int signo)
{
 //信号处理函数细节
 //最后一行
 signal(signo, signal_catch); //再注册一遍, 否则就变成  SIG_DFL 了 。
}
对于这个问题 《Unix Advanced Programming》 也提到过,说早期的Unix 也存在这个问题, 是信号不可靠的一个原因 (见 P206)

但是实际上我们在用signal()函数的时候 , 我们好像并不需要这么作  ,比如一个简单的测试程序。

为了测试, 我写了一个最简单的例子:
void sigterm_handler(int signo)
{
          printf("Have caught sig N.O. %d\n",signo);
          //按照kernel代码,应该还要有signal(signo,sigterm_handler);   才对呀 ,但事实上,我们大家都知道没有必要这样用 ,为什么呢? 请前往论坛讨论: http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=607961&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1&PHPSESSID=
}

int main(void)
{
       printf("-------------111111111111111-------------\n");
        signal(SIGTERM,sigterm_handler);
     pause();
       printf("----------222222222222222----------------\n");
      

        pause();//如果按照kernel代码里面写的, 当再发一个SIGTERM信号的时候 , sa_handler 就编程SIG_DFL 了,那默认就是 //terminate ,所以不会打出来 333333333333333333  了, 
       printf("-------------3333333333333333----------\n");
      
        return 0;
}

但是执行结果确实: 

333333333333333333333333 也打出来了, 这就又说明signal函数 ,不需要反复注册信号处理函数 ,  这不就矛盾吗? 

所以现在问题就是
if (ka->sa.sa_flags & SA_ONESHOT){ 
  ka->sa.sa_handler = SIG_DFL;
 是在什么情况下 改变了 sigaction->sa_flags (去掉了 SA_ONESHOT 标志呢?)我在代码里面搜索不到啊!
 如果感兴趣的朋友可以前往论坛讨论:http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1


2> 僵尸进程:也叫Zombie进程: 

僵尸进程定义:进程结束后,该进程的父进程没有调用wait或waitpid()对子进程进行回收 , 子进程一直是Zombie状态。
关于kernel如何杀死Zombie 请看 kernel/exit.c ==>sys_wait4() 函数 , waitpid 就是sys_wait4()实现的。
 
 首先看看正确的编程方法:
当一个进程fork()出一个子进程的时候 ,正确的情况下,父进程应该回收进程的资源:通过下面两个办法中的一个即可避免Zombie(僵尸进程):

 父进程显式的忽略SIGCHLD 信号
只要在fork一个子进程之前加上这么 一行:   signal(SIGCHLD, SIG_IGN);  //这样肯定不会出现僵尸进程,

 为什么呢?  看kernel的代码吧:
==>asm/i386/signal.c  ==>do_signal()
  ka = &current->sig->action[signr-1];//&current->sig : signal_struct
  if (ka->sa.sa_handler == SIG_IGN) {
   if (signr != SIGCHLD)
    continue;  //对于信号处理方式是 SIG_IGN , 非SIGCHLD的信号 ,kernel什么也不作! SIGCHLD 比较特殊啊!
   /* Check for SIGCHLD: it's special. 
    类似调用waitpid()来回收child process的进程表项
   */
  //SIG_CHLD 信号的行为设置为SIG_IGN  , 由内核来处理僵死进程。
  //如果你的程序中没有特别的要求需要处理SIGCHLD , 为了避免僵尸进程(Zombie进程),你可以显式的忽略它,kernel会调用sys_wait4()来处理僵尸进程的),它执行一个while() loop , 来处理系统中所有的僵尸进程,老黄牛精神啊!  
   while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)   // 看看是不是和waitpid的用法一样啊! 
    /* nothing */;
   continue;
  }
 如果 SIGCHLD 是默认的  SIG_DFL 的话:kernel就不管了,所以肯定会有僵尸进程的!
==>asm/i386/signal.c  ==>do_signal()
  if (ka->sa.sa_handler == SIG_DFL) {
   int exit_code = signr;

   /* Init gets no signals it doesn't want.  */
   if (current->pid == 1)  //谁都不可以给init(1) 进程发信号, 这样说比较准确: 发了也白发,kernel不认可
    continue;

   switch (signr) { 
   case SIGCONT: case SIGCHLD: case SIGWINCH: case SIGURG:
    continue;  //对于SIGCHLD 信号,kernel对它默认是忽略的, (请不要和SIG_IGN 混淆了)
      //所以很明显, kernel并没有调用sys_wait4() 来处理僵尸进程 ,你要自己处理了,^_^
  ..............
  }

 父进程给SIGCHLD信号注册handler(里面调用waitpid()回收child Zombie process)
比如:这样写:
while(waitpid(-1,NULL,WNOHANG) > 0)  {   //自动处理所有的僵尸进程,当然你可以不用while,只调用一次,看需要 : 比如父进程是个http server,就会fork()出很多子进程 , 所以while()是有必要的。
//WNOHANG 很关键,如果没有僵死进程,就马上返回 ,这样while()才可以结束啊 , 可是wait()就没有这个参数,  所以wait就阻塞了。 所以一般情况下,我们用waitpid还是最好的了!
 ;//什么也不必作了, 可以打印看看到底回收了哪些进程pid
}

!如果你没有用上面任何一个办法,就会出现僵尸进程。 
ps ax 命令可能会显示: 
 22149 tty8  S  0:00   test_pro
 22150  ?    Z  0:00    [test_pro <defunct>]   //这就是僵尸进程  Z 就是Zombie的意思 , 你用kill -9 也无法杀掉它 。
怎么杀掉Zombie进程呢?  你可以kill它的父进程就可以杀掉Zombie进程。
kill -SIGTERM 22149 ,  你在ps ax 看看  ,两个进程都没有了。

 

避免僵尸进程的第三种办法


 个人不推荐! 因为上面两种方法已经够用了, 除非你还有其他的要求,比如 使子进程无法获得控制终端,这种情况下, 就必须fork()两次了 。 否则一般情况下,我们需要父子进程同步和通信的, 父亲和儿子交流尚且比较方便(用pipe最好,配合使用select()) , 你让爷爷和孙子通信不是比较困难吗?  两代人的代沟呢。。。。

当       你也可以fork()两次,  父亲(比如http server,循环处理) ->  儿子进程(exit) -> 孙子进程 (处理每次的任务,正常结束,就不会成为Zombie) 
 
 下面是事例代码:  
pid_t pid = 0;
pid = fork();
if(pid < 0)
 //error
 exit( -1);
else if(pid > 0)
 //这里可能是个Server一类的, 父亲进程永远不会结束的,是while() 循环
else {

 //现在儿子 process 了,
 if(pid = fork() < 0)
 //error
  exit(-1);
 else if(pid > 0) //儿进程也结束了
  exit(0);//立刻杀死儿子进程 ,这样孙子就成孤儿了,孙子进程会被init(1)领养的。
 else { //到孙子进程了。
  /*  some code …………..


  */
  exit(0);
   }
}

对于 原理其实很简单: 儿子死了, 只有孙子了, 孙子是孤儿了, 那么init(1)进程就会领养这个 孤儿,  同时孤儿就认为init(1)就是它的父进程,由init进程负责收尸!  


3> 特殊的 SIGCHLD 信号

SIGCHLD 特殊在哪里呢?? 一般情况下, 子进程结束后 ,都会给父进程发送 SIGCHLD 信号 ,但是这不是绝对的 。
• 当一个父进程fork()一个子进程后,  当父进程没有为SIGCHLD 注册新的处理函数,处理方式为SIG_DFL  ,那么当子进程结束的时候, 就不会给父进程发送SIGCHLD 信号 。
   从代码的角度: 执行到send_sig_info(),会在isgnore_signal() 函数里面做是否要发信号的判断,结果 SIGCHLD被忽略了!
 
• 就是普通的进程,在某个地方pause(),也不是随便发一个信号就可以唤醒它, 比如 发 SIGCONT 信号(在kernel中当SIGCONT 的处理方式为SIG_DFL的时候, 它要被ignore的) ,就不可以!
例子:  int main(void)
 {
  pause();
  printf(“I am waken up\n”);
  return 0;
 }
如果你在外部随便发下列信号:SIGCONT , SIGWINCH , SIGCHLD SIGURG ,肯定是要被进程忽略的,并不能唤醒该进程! 
如果你发SIGTERM,SIGQUIT,SIGINT等信号,没有注册handler , 那么默认是中止它;如果注册了handler , 则可以唤醒该进程。


且看下面的代码分析:
/*
 * Determine whether a signal should be posted or not.
 *
 * Signals with SIG_IGN can be ignored, except for the
 * special case of a SIGCHLD.
 *
 * Some signals with SIG_DFL default to a non-action.
 */
 //定义了那些信号要被忽略!
 
static int ignored_signal(int sig, struct task_struct *t)
{
 /* Don't ignore traced or blocked signals */
 if ((t->ptrace & PT_PTRACED) || sigismember(&t->blocked, sig))
  return 0;

 return signal_type(sig, t->sig) == 0; 
}

/*
 * Signal type:
 *    < 0 : global action (kill - spread to all non-blocked threads)
 *    = 0 : ignored
 *    > 0 : wake up.
 */
//
 #define SIG_DFL ((__sighandler_t)0) /* default signal handling */
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */
#define SIG_ERR ((__sighandler_t)-1) /* error return from signal */
//
static signal_type(int sig, struct signal_struct *signals)
{
 unsigned long handler;

//-----------------------------空信号 ignore  -----------------------------
 if (!signals)
  return 0;   //
 
 handler = (unsigned long) signals->action[sig-1].sa.sa_handler;
 if (handler > 1)   //该信号有特定的信号处理函数不能ignore ,必须wake_up ()
  return 1; //can't ignore

// -----父进程设置SIGCHLD 的处理方式为 SIG_IGN : 子进程结束的时候不会给父进程发信号,也就无法唤醒了。
 /* "Ignore" handler.. Illogical, but that has an implicit handler for SIGCHLD */
 if (handler == 1)  
  return sig == SIGCHLD;//当信号是 SIGCHLD的时候,信号不能被忽略,其他的要被活略


// --------------------------当把信号设置为SIG_DFL 时的情况---------------------
 /* Default handler. Normally lethal, but.. */
 switch (sig) {

 /* Ignored */
 case SIGCONT: case SIGWINCH:
 case SIGCHLD: case SIGURG:
  return 0; //这些信号忽略干脆就忽略了 ,那你可能奇怪了?那SIGCONT 信号如何唤醒 TASK_STOPPED状态的进程呢?  如果你有这个疑问 ,请看 5>的讨论!
 /* Implicit behaviour */    //can't ignore
 case SIGTSTP: case SIGTTIN: case SIGTTOU: //这些信号就时要暂停进程的
  return 1; //这些信号会唤醒该进程的, 程序会接着望下跑的,  最后 把进程的状态置为 TASK_STOPPED 的。

 /* Implicit actions (kill or do special stuff) */
 default:  //对于象SIGKILL , SIGTERM ,SIGQUIT 这样的信号直接就默认操作, 一般就是terminate 该进程
  return -1;
 }

?怎么在应用程序验证上述kernel的代码呢?

既然提到了”唤醒“ ,肯定要用上  pause(2)函数了, 且看pause(2)的manunal :
DESCRIPTION
       The  pause  library function causes the invoking process (or thread) to
       sleep until a signal is received that either terminates it or causes it
       to call a signal-catching function.  (也就是发的信号有对应的信号处理函数,或者是强行中止的哪些信号)

RETURN VALUE
       The  pause  function only returns when a signal was caught and the sig-
       nal-catching function returned. In this  case  pause  returns  -1,  and
       errno is set to EINTR.

上面的手册说得很清楚了, 对于pause过的进程,  只有发送类似 SIGKILL , SIGQUIT, SIGTERM 的信号或者是 注册了新的处理函数,才可以唤醒它!

另外再看一下:pause()的系统实现:
asmlinkage int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE;  //设置成INTERRUPTABLE 状态,就不在CPU调度的队列里面了
schedule();//重新调度,使当前进程立刻让出CPU,  kernel从TASK_RUNNING队列中选择合适的进程重新运行
return -ERESTARTNOHAND;
}

下面是测试的例子: 
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>


void sig_handler(int signo)
{
        printf("signo = %d\n",signo);
        if(signo == SIGCHLD) {
                pid_t child_pid = 0;
                int status = 0;

                printf("into singal handler\n");

                //wait() fault : it will blocked if no defunced  process ,so I will use waitpid(.. WNOHANG) ,it will return immidietly

              while( (child_pid = waitpid(-1,&status,WNOHANG))> 0) //循环回收所有的Zombie进程
                      printf("child_pid = %d  ,  status = %d\n",child_pid,status);
        }

}

int main(void)
{
        pid_t pid = 0;

/* 感兴趣的读者可以试试!你可以试着注释掉下面的两个signal()函数, 用这个,试着回答下面的两个问题
        struct sigaction sa,old_sa;
        sigaction(SIGCHLD,&sa,&old_sa);
        sa = old_sa;
        sa.sa_flags |= SA_NOCLDSTOP; //当子进程结束的时候,阻止子进程向其父进程发SIGCHLD
        sa.sa_handler = SIG_IGN;
        sigaction(SIGCHLD,&sa,NULL);
*/
 
  signal(SIGCHLD,sig_handler);  //避免僵尸进程
  //signal(SIGCHLD,SIG_IGN);   //注释上面那行,用这行 , 再试着重新回答下面的两个问题

  pid = fork();
        if(pid < 0) {
                perror("create child process failure \n");
                exit(-1);
        }
        else if(pid == 0)
  {
                setsid();
                umask(0);
                close(0);
                //close(1);
                close(2);
                chdir("/");
       
   sleep(5); //为了确保testing的正确性, 需要确保子进程结束之前 ,父进程已经pause 了,所以子进程 sleep(5)

                printf("child ................... \n");
                exit(0);
        }
        else {
                printf("parent pause() ..............\n");
                pause();   //父进程会被唤醒吗???????????????????
                fflush(stdout);
                printf("parent process has been waken up \n");
                return 0;
        }
}

大家可以思考一下?
(1)  子进程会成为孤儿进程吗?     
(2)  父进程会被唤醒吗?


如果你看明白了上面的kernel代码 ,你就很快明白了答案了:(上面没有被注释的代码的运行结果 ,注释的部分,读者自己可以验证试着读源代码解释程序行为!

(1)答:不会,正常结束 ,因为有声明 :signal(SIGCHLD,SIG_IGN);   //避免僵尸进程
(2)答:(至少在linux-2.4.24上会,我在linux-2.4.20-8上试了一下,就不会唤醒,代码肯定不同了), 因为子进程结束后, 会给parent进程发送一个SIGCHLD 信号, 此信号会唤醒 parent 进程! 有关这方面的讨论可以访问论坛页:http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1


  
4> 在给一个进程发送信号的过程中, 只要目标进程(迟早要成为当前running的进程)没有block该信号, kernel都会调用 wake_up_process() 函数来唤醒它 , 为什么呢?   因为 只有当前活动进程才会handle signal ,过程是这样的:  当一个进程被唤醒后, 它肯定处于kernel空间 , 在它即将返回道用户空间的时候, 开始检测 task_struct->sigpending ,如果为1   就说明该进程收到了信号(现在这个信号叫pending信号,只要有pending 信号, sigpending 就是等于1 ) ,开始调用do_signal() 函数来处理 , 也就是重要的一点 , 只有当前活动进程才可以处理信号(类似中断 , 当一个进程收到一个信号后, 就active了, 至于该信号怎么处理,是 "kernel处理信号的任务“ 。

具体说明如下:
关于这个函数我觉得也也值得注意!

1> 当向一个进程发送 SIGCONT信号时候,
如果进程本身还有一些类似SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等会使进程停止的信号,
就要把他们删掉

2>如果想要停止某个进程的话,
就一定要删除SIGCONT信号(这个肯定,否则kernel在处理的时候,
进程优先处理SIGCONT信号,然后再处理这4个 ,那就多余了,没有必要。既然要停止,
就直接停止,忽略之前的SIGCONT操作)

static void handle_stop_signal(int sig, struct task_struct *t)
{
 switch (sig) {
 case SIGKILL: case SIGCONT:
  /* Wake up the process if stopped.  */
  if (t->state == TASK_STOPPED)
   wake_up_process(t);
  t->exit_code = 0;
  rm_sig_from_queue(SIGSTOP, t); //删除这些未决信号
  rm_sig_from_queue(SIGTSTP, t);
  rm_sig_from_queue(SIGTTOU, t);
  rm_sig_from_queue(SIGTTIN, t);
  break;

 case SIGSTOP: case SIGTSTP://因为这些信号排在SIGCONT信号的后面,如果不删除队列中的SIGCONT信号, 在do_singal()会先执行SIGCONT的操作的,这样就多次一举了。Note:我看代码里面是 kernel在检测信号的时候, 先处理sigset_t类型值中前面的bit对应的信号
 case SIGTTIN: case SIGTTOU:
  /* If we're stopping again, cancel SIGCONT */
  rm_sig_from_queue(SIGCONT, t);
  break;
 }
}

  
5> ? 对于3>提到的特殊的SIGCHLD 信号, 我们提到了到底哪些信号要被忽略。那么对于善于思考您, 不知道你是否有此疑问:
  (1)对于一个普通的进程发SIGCONT 信号肯定是要被kernel忽略的;
  (2)但是一般的上层熟悉signal用法的R&D都知道 SIGCONT信号是SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU的后继信号, 是专门用来把挂起的进程恢复running的,根据上面的结论SIGCONT不是也要被忽略的吗???? 那进程又怎么可能恢复执行呢?
  
 答案:要回答这个问题就要弄清楚kernel在发一个signal的流程: 
1. 判断信号是否是bad的,  参考 kernel/signal.c ==>bad_signal()
2. 处理状态是TASK_STOPPED的进程, 如果是 , 就调用wake_up_process() , 参考 kernel/signal.c==>handle_stop_signal() 函数
3. 判断哪些信号该忽略,参考 kernel/signal.c==>ignore_signal()
4. 最后 调用 deliver_signal() 正是发送信号(其实发送信号,说白了,就是修改task_struct 相应的数据成员),发送完信号成功后,如果进程是处于TASK_INTERRUPTABLE 状态的(且信号没有被阻塞), 就唤醒它。
到此为止,信号就算正式发送完毕了。

所以,你现在你就知道答案了:
虽然SIGCONT 信号要在 3.被忽略,可是2. 却可以被执行, 进程被变为TASK_RUNNING 状态了(!TASK_RUNNING状态的进程有很多,但是同一个时间,占用CPU的就只有一个)
那为什么要必须把 TASK_STOPPED 状态的进程 变为 TASK_RUNNING 呢?  也许你可以从 4>中得到答案!


6> !关于阻塞信号注意事项
1. 阻塞信号很有用, 比如你运行某个程序, 比如upgrade 程序, 再比如更新BIOS , 都是不允许突然断电和突然停止的,否则会发生灾难性的后果!至少在代码某个部分,是不能停止的,这个时候 必须阻塞一些信号(比如SIGTERM,SIGQUIT,SIGINT等等)。阻塞信号后, 在适当的时机要对该信号解除阻塞!以处理被阻塞信号!


利用信号阻塞可以实现上面的功能,你可以在关键的不可停止的代码上面加 sigprocmask(&block_set)函数阻塞一个信号集,这样当这部分关键代码执行的时候,阻塞block_set集合里面的信号, 这些信号只有被解除阻塞的时候,才可以被处理! 当关键代码执行完毕后, 你再 调用suspend(&zero_set) 来允许所有的信号, 为的就是处理刚才被阻塞的信号,比如如果刚才发了SIGQUIT信号,现在就可以处理SIGQUIT信号了,最后(假如你的程序还没有中止,要继续执行其他操作的话)调用sigprocmask(SIG_SETMASK,&old_set,NULL); 来恢复原来的信号掩码集合,使你的程序继续running下去!

下面是例子程序:例子程序我写了详细的操作说明,读者可以清楚了解sigsuspend()的用法
程序说明: 在执行critical code的过程中,不允许SIGQUIT信号,你可以随意copy使用!

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void sig_int(int signo)
{ printf("int:signo %d\n",signo);
}
void sig_quit(int signo)
{ printf("quit:signo %d\n",signo);
}
int main(void)
{
 sigset_t newmask,oldmask,zeromask;
 signal(SIGINT,sig_int);  //only test
 signal(SIGQUIT,sig_quit);//only test

 sigemptyset(&zeromask);
 
 sigemptyset(&newmask);
 sigaddset(&newmask,SIGQUIT);

 printf("just sigprocmask , blocked newmask \n");
 sigprocmask(SIG_BLOCK,&newmask,&oldmask );
 
/*critical code begin */ 
 printf("only testing ,please send SIGQUIT signal to test,it will be blocked \n");
 pause();//在这里停住仅仅用于测试, 否则程序马上掠过这里, 你将没有机会测试 SIGQUIT信号了!
 //实际使用的时候,你不能用pause()
/*critical code over */

 //deal these blocked signal
 printf("allow all signals, and deal all blocked signals \n");
 sigsuspend(&zeromask);
 printf("haha , SIGQUIT has been deal \n");

 printf("will restore the signal mask \n");
 sigprocmask(SIG_SETMASK,&oldmask,NULL);
 printf("has restored ,please testing with SIGQUIT \n");//这个时候,没有阻塞SIGQUIT信号!
 pause();   //这里也仅仅用于测试 ,你可以发SIGQUIT信号试试!

 printf("haha , testing over ,this is suspend () usage \n"); 
 exit(0);
}


上面演示了sigsuspend()与sigprocmask的用法, 通常情况下,这两个函数都是配合使用的,其他的例子可以参考《Unix Advanced Programming 》P229

2.?如果我阻塞了一个信号, 但是当我恢复信号阻塞掩码的时候, 我并不想处理该信号我该怎么办呢?比如SIGQUIT信号,如果我不另加处理, 它会中止程序的,天哪?那怎么行?

这个时候需要用到sigpending()这个函数了, 当你的关键的code 段,结束后, 你可以利用sigpending()来检查某个信号是否在pending中,然后设置这个信号的处理方式, 比如,如果你想删除这个信号, 直接忽略它 。 等恢复了原来的信号阻塞掩码后,再恢复该信号的处理方式!

还是据个例子吧:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(void)
{
 sigset_t block_set,save_set;
 sigset_t pending_set;
 
 sigemptyset(&pending_set);
 
 sigemptyset(&block_set);
 sigaddset(&block_set,SIGQUIT);
 sigprocmask(SIG_BLOCK,&block_set,&save_set);
 
 /* your key code segment ,can't be interrupt by SIGQUIT
   code
  */
  printf("Please send SIGQUIT signal , by  Ctrl+ \\ \n");
  sleep(6);  //仅仅是为了测试,这个时候 ,你可以发SIGQUIT信号,实际应用中,这个肯定不要的!
 printf("key code has been end !\n");
 printf("you can unblock those signals\n");
  //如果你发了SIGQUIT 信号, 这时候, 肯定在pending里面了
 
  sigpending(&pending_set);
  if(sigismember(&pending_set,SIGQUIT)) //测试SIGQUIT是否在pending队列中
  { printf("yes , SIGQUIT is pending signal\n");
   signal(SIGQUIT,SIG_IGN);  //暂时改变一下SIGQUIT的行为,稍后再改回去!
  }
 // sigsuspend(&zero_set);  //来处理其他的刚才被阻塞的信号,但是不处理SIGQUIT信号
  sigprocmask(SIG_SETMASK,&save_set,NULL);
  signal(SIGQUIT,SIG_DFL);  //复原SIGQUIT的信号部署方式
  printf("yet pause() , please ctrl+\\ to test SIGQUIT's handler \n");
  pause(); //仅仅测试,SIGQUIT 是否还被阻塞
 
  return 0;
 
}
 

3.另外两个比较有用的函数:sigwaitinfo(), sigtimedwait(),   专门等待某种信号的到来,sigtimedwait()可以在有限的时间内等待某个信号集!而且sigwaitinfo() | sigtimedwait()也是经常与sigprocmask配合使用, 当然只用sigwaitinfo()系列也可以!而且功能也一样!为什么这样?请man 2 sigwaitinfo,你一定可以找到答案!,但是如果配合sigprocmask功能会更多一些!比如你想要哪些信号到来(sigwaitinfo()),但又不想要哪些信号(sigpromask)
具体的复杂的可应用的例子可以参考8>中的父子进程通信的例子。

使用注意事项:
 sigwaitinfo(wait_set)等待set信号集中的信号的到来, 如果在没有等到信号集中的信号,或者收到了一个不在set集合中的信号,该函数就会返回-1,在返回-1之前会处理这个不速之客(或默认处理,或调用信号处理函数)。

这里举个简单的例子;也加进了sigsuspend()函数,来处理增经阻塞过的信号
请注意block_set与wait_set的差别

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

/*
 * 该程序主要是说明并演示 sigwaitinfo()和sigsuspend()和sigprocmask()的用法
 * 用法与说明
  <1>sigwaitinfo仅仅等待set里面的信号, 当收到这个信号后, 仅仅返回signal值和信号的信息siginfo_t, 不执行信号处理函数(如果注册了信号处理函数的话)
  <2>sigsuspend()相当于 先sigprocmask() ,然后再pause() , 但是sigsupend()是个原子操作

• 程序功能:
程序运行过程中wait SIGINT信号, 在调用sigwaitinfo之前阻塞了 SIGINT 和SIGQUIT信号,
如果sigwaitinfo()阻塞的时候, 发SIGQUIT信号,必然要被阻塞, 程序依旧睡眠, 当发送SIGINT信号的时候, 也要被阻塞, 但是sigwaitinfo一旦检测到pending 信号中有SIGINT,就立即返回 。
程序继续执行, 但是我们要处理刚才发送的被阻塞的SIGQUIT信号, 所以调用suspend(&zero_set)来处理所有被阻塞的信号 , 所以调用SIGQUIT的信号处理函数, 然后从suspend()返回, 程序继续执行!调用sigprocmask()来恢复原来的信号掩码集。

           --bob
 */
void sig_quit(int signo)
{
 printf("======================SIGQUIT=======================\n");
}

int main(void)
{
 sigset_t wait_set,block_set,old_set,zero_set;
 siginfo_t info;
 int recv_signo = 0;

 signal(SIGQUIT,sig_quit); //你可以捕获SIGQUIT信号,随你的便,如果你不想被SIGQUIT退出的话。
 sigemptyset(&zero_set);
 sigemptyset(&wait_set);
 sigaddset(&wait_set,SIGINT);
 
 
 block_set = wait_set;
 sigaddset(&block_set,SIGQUIT);  //阻塞了block_set集合里面的信号, 比wait_set多阻塞了一个SIGQUIT信号!,读者可以在这里加上你想阻塞的信号,这些信号可能会对正常的流程不利!
 
 sigprocmask(SIG_BLOCK,&block_set,&old_set);
 recv_signo = sigwaitinfo(&wait_set,&info);
 printf("--debug recv signo = %d\n",info.si_signo);
 printf(“recv signo = %d\n”,recv_signo);  //确实收到了想要的信号时,结果与上面相同, 收到了 wait_set意外的信号时, 就返回 -1  ,所以用这个判断是否出错,比较好!

/*----this is error ,为什么错,please see 《unix advanced programming》P229-230 */
 //sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask
 //pause();
/* ----------------------------------------------------------------------------*/
 sigsuspend(&zero_set);//临时允许所有的信号,为的处理刚才有可能被阻塞的信号!
 
/* ---------------现在信号掩码集合已经被恢复设置了, 你可以再试试 SIGQUIT 信号了。这个时候就可以处理 SIGQUIT信号了。
 * ---------------虽然下面两行看起来和上面注释的认为的错误行一样,可是它的用途确实孑然不同 ,你看出来了吗?
 */
 sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask
 pause();
 
 return 0;
}

?对于上面的程序读者不知道发现一个bug没有?  就是如果这个时候在等SIGINT的时候, 发生了别的信号比如SIGQUIT,SIGTERM什么的,程序该怎么运行呢?  答案: 不妙, 本来时要等待SIGINT 信号的,但是没有等到,由于收到其他的信号可能会异常退出, 这怎么能行呢? 所以当收到了其他的信号的时候, 一定要判断errno == EINTR

改进如下:
……………………………………………………………..
again:
 sigprocmask(SIG_BLOCK,&block_set,&old_set);
 recv_signo = sigwaitinfo(&wait_set,&info);
 
 If(recv_signo == -1) {  //那就出错了,但是 -1 ,包含了两种情况 EINTR 和 EAGAIN
  If(errno == EINTR)  //表示被别的信号给中断了
  {
   goto again;  //重新调用sigwaitinfo() 继续等待
  else if(errno == EAGAIN);   //如果你用的时sigtimedwait()函数,这个也许有用,表示在规定的timeout时间无法获得资源,  errno== EAGAIN
   ;  //do nothing ,直接退出即可(当你用sigtimedwait()的时候)
 }
 else {  //收到我们想要的信号
  printf("--debug recv signo = %d\n",info.si_signo);
  printf(“recv signo = %d\n”,recv_signo);  //确实收到了想要的信号时,结果与上面相同, 收到了 wait_set意外的信号时, 就返回 -1  ,所以用这个判断是否出错,比较好!
 }
!我们通过一个循环来判断该sigwaitinfo()系统调用是否是被信号中断的, 如果是就继续循环重新调用sigwaitinfo()来阻塞并等待期待的信号的到来!

 


7>!不可再入函数

比如malloc(), printf(), 这些都是不可再入的函数!,使用不可再入的函数使危险的,不要用他们, 具体哪些是不可再入的,哪些使可再入的, 要查看《unix advanced programming》P209

这里指出最常见的错误:
在信号处理函数中:
• 不能调用malloc()函数来动态分配内存,因为主程序在被信号中断的时候, 有可能正在调用malloc()函数,也就是主程序调用malloc的时候被中断了。
• 不能调用printf()来打印信息!当然偶尔调试也问题不大, 但是调试成功后, 要去掉printf()函数!

8> 关于信号的技巧:
 Q :如何判断一个进程是否还活着 ?
A: 发一个空信号就好了, 什么?什么是空信号?  就是  0
 Q:如何暂时挂起某个进程?
  A: 如果你想暂时挂起某个进程的运行,以后要恢复,请用SIGSTOP信号 , 想重新执行该进程的时候,就发SIGCONT信号。 记住当你对一个进程发SIGSTOP信号的时候,  子进程会给父进程发SIGCHLD 信号,这样父进程会被唤醒。具体为什么请看 3> ,kernel 代码里面已经很清楚了。
 Q:我想父子进程间通信而且同步,比如: 父进程需要等待子进程执行的结果,然后最会退出, 当然不能无限期等待,比如等 5秒钟 , 用信号行吗? 
A:你以为信号是万能的? 不过这个功能用信号确实可以实现,而且还非常简单。
具体的我们来分析一下:
1. 同步:这是信号的最基本的功能了, 无论你用signal()/kill() 系列还是  sigaction()/sigqueue() 系列肯定能满足你的要求 。
2. 通信:如果你用kill(2)函数发信号, 通信肯定不可能了, 传递不了信息啊,接收进程只知道收到收到这个信号,但是到底发生了什么事情一概不知!这也是kill(2)的局限了!
   你可以用sigqueue()函数 ,里面有项参数就是用来传递数据的 ,内核里面有个结构叫 siginfo_t ,就是干这个用的。具体请看http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html (进程间通信 信号(下),里面举了个例子)
3. 父进程有限时间等待子进程,用sigtimedwait(const sigset_t, siginfo_t *info, const struct timespec timeout) ,但是仅限于等信号,可不是等别的什么!

举个很有实用价值的例子:比如有个downloader (libcurl),我想在主程序中调用curl执行下载, 然后主程序等待curl的下载结果,可以只等5秒钟, 如果5秒钟还是没有收到信号, 说明curl一直在执行!

主进程fork()出一个子进程 ,子进程去作真正的事情。
为了方便理解,用子进程运行curl下载文件 ,根据文件的大小和网速,下载时间会有很大不同。
下面分三种情况:
 file比较小, 子进程(curl进程)很快返回,下载完毕
 file比较大, 子进程(curl进程)过20分钟返回,下载才完毕。父进程刚开始timeout时间就结束了。
 URL出错, 子进程马上返回一个出错码!
父进程可以根据这三种情况update某个 database,来记录这次的下载状态!并及时的反馈给前端的UI
下面是我写的程序, 很高兴被您随意copy!

思路1: 用signal来实现, 非常简单和直观!(对比下面的pipe做法)

父进程需要在有限的时间内等待子进程发送SIGUSR2信号, 而子进程结束时候,也要报告自己的exit code ,但是这个时候父进程已经结束了,子进程被init(1)领养 ,所以就不用报告状态了,直接被init(1)回收!

下面是代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void sig_usr2(int signo,siginfo_t *info,void *myact)
{
 printf("signo = %d\n",signo);
}
int main(void)
{
 sigset_t wait_set;
 int sig_no;
 const struct timespec tv = {5,0}; //timeout
 siginfo_t sig_info ;//传递的信息结构
 pid_t pid = 0;

 sigemptyset(&wait_set);
 sigaddset(&wait_set,SIGUSR2);
 
 signal(SIGCHLD,SIG_IGN); //父进程显式的忽略子进程发来的SIGCHLD信号 ,防止出现Zombie 进程,如果忘记了,复习一下上面!

 pid = fork();
 if(pid < 0) {
  printf("fork failure \n");
  exit(-1);
 }
 if(pid == 0) {
  int rc = 0;
  union sigval rc_val;  //子进程要传递的信息
  setsid();
  umask(0);
  chdir("/");
  rc = system("curl -O  http://www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.24.tar.gz &>/dev/null");
  printf("rc = %d\n",rc);
  rc_val.sival_int = rc/255;  //传递整型值
  if(getppid() > 1) { //因为如果过了timeout , 父进程就退出了, 该子进程被init(1)领养, 所以千万不要向init(1)发信号!否则整个系统都要reboot 了!
   sigqueue(getppid(),SIGUSR2,rc_val);//给父进程发信号!,getppid()可以获得父进程的pid
  } 
  exit(0);
 } 
  
 //parent process
 sigprocmask(&wait_set);
 sig_no = sigtimedwait(&wait_set,&sig_info,&tv); //父进程会一直阻塞tv时间,然后就返回
 if(sig_no == -1) {   //说明超时
  printf("child process timeout \n");
  printf(“curl 一直没有返回,这样下载看起来没有问题, 正在下载\n”);
 }
 else {
  printf("child process return value = %d\n",sig_info.si_int);
  if(sig_info.si_int == 0)   //信号传递的信息! 我们在这里传递integer值!
   printf(“下载完毕\n”);
  else
   printf(“下载出错,错误代码 %d\n”, sig_info.si_int);
 }
 return 0;
}
 
为了比较, 我原来写过一个用 无名管道/select 来实现 上述功能的, 不过你需要了解pipe和select的用法,需要的知识点比较多。

你可以随意copy我的代码,

思路2:用pipe实现父子进程通信,再配合select()在timeout时间内监视管道读管道)
 父进程创建了一个无名管道, 子进程在管道写端写入value , 父进程通过select()函数检测管道的读端,如果5秒钟内读端无反应,说明超时,否则就可以读value! 这样就是简单的实现父子进程同步,通信,且有限时间等待的要求!

 pid_t pid = 0;
 int fd[2];    //pipe operation :
 unsigned char share_buffer[3]; //share info between parent process and child process
  
 fd_set read_fds;
 int fd_max ;     /* for select */
 struct timeval tv;
 int select_rc = 0;


 if(pipe(fd) < 0)
 {
  perror("create pipe");
  return SERVER_PIPE_FAIL;
 }
 
 signal(SIGCHLD,SIG_IGN); //防止出现Zombie 进程,如果忘记了,复习一下上面!
 
 pid = fork();
 if(pid < 0)  {
  perror("fork a child to download file failure");
  return SERVER_FORK_FAIL;
 }
 else if(pid == 0) {
  unsigned char buffer[3]; //子进程往里面写数据
  int rc = -1;

  setsid();
  /* generate a daemon process
    * setsid()是创建daemon的关键函数,(1)成为session的leader process ,
    *(2)成为进程组的leader process ,(3)没有终端
    */
   umask(0);
   /* 当创建文件的时候和目录的时候 默认是 umask(022) ,
    * umask()函数可以改便创建文件时候的默认许可权位 , 据个例子,当你用root权限
    * 创建一个文件 , > bob.txt   ,你会发现: ll bob.txt ,  显示 -rw-r--r-- ,
    * 这就是umask(022)的作用 , 022 对应的二进制: 000     010 010 ,表示 对于
    * 组内用户和其他的用户 不可有w的权限。   w位置1 就表示不可以w !以此类推!
   close(0);   //关闭标准输入
  close(1); //关闭标准输出
  close(2); //关闭标准错误输出
  chdir(“/”);

  close(fd[0]); //把管道的读 一端 关闭 ,只留写 一端 即可

//执行你的程序 ,你的code
。。。
//在exit(0)之前,通知你的父进程你的执行结果, rc就是执行结果

buffer[0] = rc; 
buffer[1] = '\0';
buffer[2] = '\0';
write(fd[1],buffer,sizeof(buffer));
close(fd[1]); //end of write to the "write pipe" ,must close it
exit(0);
 }  //子进程结束!
 //父进程内
 close(fd[1]); //关闭写端 ,只要留着读端即可!
  
 FD_ZERO(&read_fds); //clear the read_fds
        FD_SET(fd[0], &read_fds);
        
        tv.tv_sec = 5  //假设父进程就等待5s
        fd_max = fd[0]+1;
               
        //select 不熟悉select()的朋友可以到google搜索它的用法,一定要掌握!
        select_rc = select(fd_max,&read_fds,NULL,NULL,&tv);
 
 if(!select_rc) //wait超时 ,
  //你的处理code , 也许这正是你期待的呢!
 else {
  read(fd[0],share_buffer,sizeof(share_buffer));
  jprintf("read successfully\n");
  jprintf("in father :buffer[0] = %d\n",share_buffer[0]);
  jprintf("in father :buffer[1] = %d\n",share_buffer[1]);
  jprintf("in father :buffer[2] = %d\n",share_buffer[2]);
  close(fd[0]); //close read pipe , 读完毕后记着关闭它!

  //你可以根据buffer读出的内容作进一步的处理
  //你的code
 }
 ………………………………..


附录:

参考资料:
1. 《linux内核源代码情景分析》上册 ,对kernel代码分析的详细彻底,不回避难点,很多kernel代码分析的书没有讲到的知识,它基本上都讲到了,而且详细透彻,值得一读!
2. 《深入理解linux 内核》,虽然没有《情景分析》那么详尽细节, 但是总是有画龙点睛之笔,让人惊叹作者的功力之深看问题的锐利!建议先看它,看代码的时候,再看情景分析!
3. 《Unix环境高级编程》,是每个linux编程者必备的手册。
4. 进程间通信 信号(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html 绝对的好文章
5. 进程间通信 信号(下) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 同一个人写的
6. signal(),signaction(),sigwaitinfo(),sigtimedwait()等其他函数的manual

文档完

作者:英文名bob  ,职业:Embeded Linux程序员,开发方向NAS和linux 客人呢了 , birth : 1980/3/28
mail : [email protected]    或 [email protected]  均可

 

相关阅读 更多 +
排行榜 更多 +
空中跑酷汉化版

空中跑酷汉化版

赛车竞速 下载
修仙传说

修仙传说

角色扮演 下载
魔界零之迷宫

魔界零之迷宫

冒险解谜 下载