免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6182 | 回复: 7
打印 上一主题 下一主题

[函数] 用alarm函数实现sleep函数时遇到的问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-09-04 16:50 |只看该作者 |倒序浏览
今天读到APUE的第十章《信号》的时候,参照书上所讲,自己用alarm()实现了一个mysleep()函数。程序如下:
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>

typedef void Sigfunc(int);
static jmp_buf env_alarm;
volatile int rtn_val;

static void old_sig_alarm(int signo)
{
  printf("in old_sig_alarm()\n");
  // longjmp(env_alarm,1);

}

static void new_sig_alarm(int signo)
{
  printf("in new_sig_alarm()\n");
  // longjmp(env_alarm,1);

}

int mysleep(unsigned int sec)
{
  int old_alrm = -1;
  Sigfunc *p;

  if( (p = signal(SIGALRM,new_sig_alarm)) == SIG_ERR )
    return(sec);

  if(setjmp(env_alarm)==0)
  {
    old_alrm = alarm(sec); //old_alarm保存此函数之前的alarm的秒数


    if(old_alrm>0) //说明调用mysleep()之前有alarm()语句

    {
      if(old_alrm<sec) // alarm(3); mysleep(5); 的情况

      {
      signal(SIGALRM,p);
      alarm(old_alrm);
      rtn_val = 0;
      pause();
      }
      else //alarm(5); mysleep(3); 的情况,也是我所遇到问题的情况

      {
         rtn_val = old_alrm - sec;
      pause();
      }
    }
    else //调用mysleep()之前没有alarm()语句

    {
      rtn_val = 0;
      pause();
    }
  }

  signal(SIGALRM,p); //返回之前把SIGALRM的处理函数恢复为默认的

  return(alarm(rtn_val));
}

int main(void)
{
  signal(SIGALRM,old_sig_alarm); //设置SIGALRM的默认处理函数

  alarm(5);
  mysleep(3);
  pause();
  return 0;
}

程序的一些说明:
1、old_sig_alarm()函数用来模拟SIGALRM信号默认的信号处理函数,而new_sig_alarm()函数用作捕捉SIGALRM信号的处理函数。mysleep()中用函数指针p保存old_sig_alarm,以便退出函数的时候恢复为默认的信号处理函数(我的程序中用old_sig_alarm模拟默认函数)。
2、main()函数在调用mysleep(m)之前已经设置了一个alarm(n),n在mysleep(m)中用old_alrm存放。

     如果alarm的时间早于mysleep的结束时间,则直接处理alarm(),然后终止mysleep()函数。如:
int main(void)
{
   …
   alarm(3);
   mysleep(5);
   …
}

     如果alarm的时间超过mysleep的时间,则先mysleep结束以后,再等到alarm到时间并调用默认的处理函数。(根据APUE上说,对于 先alarm(5) 然后 sleep(3),是否会产生2个SIGALRM信号根据不同系统的具体实现而不同。我的程序是会产生2个SIGALRM信号的,即mysleep结束以后依然处理之前的alarm)。如:
Int main(void)
{

alarm(5);
mysleep(3);
pause();

}

3、根据APUE,如果mysleep的实现写成:
int mysleep(int sec)
{

signal(SIGALRM,new_sig_alarm);
alarm(sec);
pause();

}

当系统繁忙的时候,可能产生竞争条件。即,执行alarm(sec)以后,因为竞争,所以没有执行到pause()一句alarm()就到时间了。这时会调用new_sig_alarm()处理信号,然后再执行pause()。这样进程就会永远挂起。为了避免这种情况,程序引入了setjmp和longjmp,在new_sig_alarm() 与old_sig_alarm()中加入一句 longjmp,这样无论是否产生竞争,函数都不会永远挂起。具体实现见上面贴的程序。具体实现中,我用rtn_val保存mysleep函数返回之前需要alarm的时间,有可能是0, 也有可能是 old_alarm-sec。为了longjmp的时候rtn_val不会被恢复成为setjmp时的值,我把rtn_val申明为volatile。

程序的问题
     当把old_sig_alarm() 与new_sig_alarm()中的longjmp语句注释起来,程序可以正常执行。即,运行3秒之后输出 in new_sig_alarm(),再过2秒,输出in old_sig_alarm()。可以看到,退出mysleep()之前,SIGALARM的信号处理函数被还原成了默认的old_sig_alarm()。
    但是,当把2句longjmp的注释去掉以后,运行3秒输出了in new_sig_alarm()后,程序就永远挂起了。

    请问这是怎么回事啊?(由于涉及信号,用gdb仿佛也不好调试)

论坛徽章:
0
2 [报告]
发表于 2007-09-04 18:48 |只看该作者
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>

typedef void Sigfunc(int);
static jmp_buf env_alarm;
volatile int rtn_val;

static void old_sig_alarm(int signo)
{
  printf("in old_sig_alarm()\n");
  longjmp(env_alarm,1);

}

static void new_sig_alarm(int signo)
{
  printf("in new_sig_alarm()\n");
  longjmp(env_alarm,1);

}

int mysleep(unsigned int sec)
{
  int old_alrm = -1;
  Sigfunc *p;

  if( (p = signal(SIGALRM,new_sig_alarm)) == SIG_ERR )
    return(sec);

  printf("the sig function 1 is %p\r\n", p);
  if(setjmp(env_alarm)==0)
  {
    old_alrm = alarm(sec); //old_alarm保存此函数之前的alarm的秒数


    if(old_alrm>0) //说明调用mysleep()之前有alarm()语句

    {
      if(old_alrm<sec) // alarm(3); mysleep(5); 的情况

      {
      signal(SIGALRM,p);
      alarm(old_alrm);
      rtn_val = 0;
      pause();
      }
      else //alarm(5); mysleep(3); 的情况,也是我所遇到问题的情况

      {
         rtn_val = old_alrm - sec;
      pause();
      }
    }
    else //调用mysleep()之前没有alarm()语句

    {
      rtn_val = 0;
      pause();
    }
  }

  signal(SIGALRM,p); //返回之前把SIGALRM的处理函数恢复为默认的
  printf("the sig function 2 is %p\r\n", p);
  printf("the remain time is %d\r\n", rtn_val);
  return(alarm(rtn_val));
}

int main(void)
{
  signal(SIGALRM,old_sig_alarm); //设置SIGALRM的默认处理函数

  alarm(5);
  mysleep(3);
  pause();
  return 0;
}
作上述修改后,用gcc运行的结果(在windows下运行gcc):
$ ./a.exe
the sig function 1 is 0x401074
in new_sig_alarm()
the sig function 2 is 0x401074
the remain time is 2
in old_sig_alarm()
the sig function 2 is 0x4
the remain time is 2
      7 [main] a 4064 _cygtls::handle_exceptions: Error while dumping state (probably corrupted stack)
Segmentation fault (core dumped)
错在什么地方应该已经很明显了~~~

[ 本帖最后由 goodluckwhh 于 2007-9-4 18:54 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2007-09-05 10:46 |只看该作者
goodluckwhh兄:
    我现在大概知道了是因为longjmp把栈给弄乱,所以出现错误。但是我去掉2句注释以后在linux环境下面运行程序,不会出现 in old_sig_alarm() 的打印信息,我的程序在输出:

        in new_sig_alarm()
        the remain time is 2

之后就挂起了。 (我的程序中,指针p就是用来存old_sig_alarm()的地址的,两句输出"the sig function 1/2 0x401074" 地址肯定是一样的。所以我把输出p指针地址的2句去掉了。)

    我想是不是在 new_sig_alarm()里面longjmp了以后在old_sig_alarm()里面又longjmp一次就把栈弄乱了,所以我把old_sig_alarm()里面的longjmp注释起来。但是这样还是会被挂起。

    请问能否使用longjmp 让程序按照我原本的意图正确运行啊? (即先 mysleep 3秒 ,再过2秒之后捕捉到原本的alarm(5)并用old_sig_alarm() 处理 )

    万分感谢!  

论坛徽章:
0
4 [报告]
发表于 2007-09-05 11:27 |只看该作者
我对程序作了一些改进。
用 sigsetjmp/siglongjmp 替换了 setjmp/longjmp, 这样可以在输出 in new_sig_alarm() 之后输出 in old_sig_alarm(),但是之后还会出现一句 Segmentation fault.
于是我把 old_sig_alarm()里面的 siglongjmp 注释起来,于是程序可以完全正确的运行。

但是,如果还是用 setjmp/longjmp , 只是把old_sig_alarm()里面的 siglongjmp 注释起来,程序依然会在输出 in new_sig_alarm()之后挂起。

看来还和信号屏蔽字有关系。 我继续想想。

[ 本帖最后由 sanbodhi 于 2007-9-5 11:29 编辑 ]

论坛徽章:
0
5 [报告]
发表于 2007-09-05 18:48 |只看该作者
你猜测到了是和信号有关,我帮你证实了。
同样运行该程序,但是让它在后台运行,随后用kill给它送各种信号,发现信号可以使挂起的进程结束,但是需要注意的是当我试图给它送SIGALRM信号时,shell返回了进程不存在的错误,但是进程随后结束了。

同时在进程挂起时通过 ps -l看到的该进程的WCHAN状态为pause(使用的是red hat )。

至于你该用sigsetjmp/siglongjmp之后出现的段错误,原因应该是你从信号处理函数jump回sleep函数后都有重新设置信号处理函数和调用pause,而且pause的时间总为2

论坛徽章:
0
6 [报告]
发表于 2007-09-06 10:46 |只看该作者
我终于搞清楚了。程序有2个错误,一个是Segmentation fault,一个是永远挂起。

1、Segmentation fault错误是因为 在 new_sig_alarm() 和  old_sig_alarm()里面都用了 longjmp/siglongjmp。不完美的解决方案是把old_sig_alarm()里面的注释起来,但是有一个问题就是“我的old_sig_alarm()是用来模拟SIGALRM信号的默认处理函数的,万一linux自己的处理函数里面带有类似的 longjmp, 那我这个mysleep函数还是不能够很好的运作”。

2、永远挂起错误是因为, linux中,进入消息处理函数的时候,会把这个消息加入信号屏蔽字里面。在new_sig_alarm()里面,用longjmp 直接跳转以后,并没有恢复信号屏蔽字,所以mysleep结束以后,对后来的alarm信号没有响应。
   用siglongjmp以后,会自动在跳转之前恢复信号屏蔽字,所以程序就执行正确了。
   或者,使用longjmp,在跳转之前手动恢复,如下:
static void new_sig_alarm(int signo)
{
  sigset_t delset,curset;
  printf("in new_sig_alarm()\n";

  sigemptyset(&delset);  sigaddsets(&delset,SIGALRM);
  sigpending(&curset);
  sigprocmask(SIG_UNBLOCK,&delset,&curset);
  longjmp(env_alarm,1);
}

这样,程序也可以正确执行了。  

论坛徽章:
0
7 [报告]
发表于 2007-09-06 13:21 |只看该作者
关于Segmentation fault的错误你分析的有点简单,第二个错误你已经分析出来了。
Segmentation fault的真正原因在于:
当你从new_sig_alarm调用longjmp/siglongjmo跳回sleep操作后你作了如下操作:
signal(SIGALRM,p);
return(alarm(rtn_val));
这里p就是你的old_sig_alarm函数,rtn_val的值为2,做完这个操作之后sleep函数返回了,注意此时该函数的堆栈也无效了,但是2秒钟后当alarm到期时,信号处理函数old_sig_alarm又试图跳回sleep函数。这才是造成问题的真正原因,并不是因为你在两个地方都调用了longjmp/siglongjmp。

顺便罗嗦一句:alarm信号的默认行为是结束该进程。

论坛徽章:
0
8 [报告]
发表于 2007-09-06 14:06 |只看该作者
哦! 原来是这样! 看来我还是想错了   
非常感谢 goodluckwhh兄  
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP