实现mysleep,可重入函数,SIGCHLD信号

原创 2018年04月16日 15:27:18

一,pause函数实现sleep

int pause(void);//头文件<unistd.h>

        该函数的功能是使调用进程挂起直到有信号递达。如果处理动作是终止进程,则进程终止,函数不再返回;若处理动作是忽略,则进程继续挂起;若是自定义行为,则捕捉信号后返回-1。所以该函数只有出错的返回值。

 #include <unistd.h>
 unsigned int sleep(unsigned int seconds);

        sleep函数的功能是使进程挂起seconds秒后醒来。如果在秒数为到达seconds之前进程被唤醒了,返回剩余的秒数。若是正常醒来,返回0。

        要实现sleep的功能,首先要使进程挂起,这里就要用到pause函数了。而进程seconds秒后要醒来,就要有信号递达使pause函数出错返回(否则进程会继续挂起或终止)。要实现seconds秒后发送信号的功能,就要使用到alarm函数,它可以再seconds秒后发送SIGALRM信号,并将该信号的处理动作改为用户自定义行为,进而使pause出错返回,使进程醒来。若进程在SIGALRM信号为递达之前就被唤醒了,此时就要取消闹钟来返回剩余的秒数。所以,整个函数的步骤如下:

(1)自定义SIGALRM信号的处理动作

(2)alarm(seconds)设置闹钟,在seconds秒后发送SIGALRM信号

(3)pause挂起进程

(4)alarm(0)取消闹钟(在(3)中pause可能被其他信号唤醒,所以要取消闹钟返回闹钟剩余的秒数)

(5)恢复SIGALRM信号的原有处理动作

(6)返回剩余的秒数

代码如下:

#include<stdio.h>
#include<unistd.h>
#include<signal.h>                                                                                                                    
//捕捉闹钟信号
void handler(int signo)
{
    ;   
}
int mysleep(int sec)
{
    struct sigaction act,oact;//act为要设置闹钟信号的相关信息,oact保存闹钟信号的原有相关信息
    act.sa_handler = handler;//自定义闹钟信号的处理动作
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);//在执行闹钟信号的处理动作时,不屏蔽其他信号
    sigaction(SIGALRM,&act,&oact);//捕捉闹钟信号

    alarm(sec);//sec秒后向进程发送个闹钟信号
    pause();//使进程挂起
    int ret = alarm(0);//取消闹钟,返回剩余的秒数
    sigaction(SIGALRM,&oact,NULL);//恢复闹钟信号的默认处理动作,验证有无该语句的对比
    return ret;
}
int main()
{
    while(1)
    {   
        printf("hello world\n");
        mysleep(1);                                                                                                                   
    }   
    return 0;
}

        运行结果:

[admin@localhost pause_sleep]$ gcc mysleep.c 
[admin@localhost pause_sleep]$ ./a.out 
hello world
hello world
^C

        在运行结果中,1s输出一条语句,Ctrl+C后进程结束。

        下面代码验证mysleep的返回值功能,在上述代码的基础上设置2号信号的自定义行为,并修改main函数:

//捕捉其他信号
void handler1(int signo)
{
    printf("catch signo %d\n",signo);
}
int main()
{
    signal(2,handler1);//自定义2号信号的处理动作
    printf("pid %d\n",getpid());                                                                                                    
    int ret = mysleep(20);
    printf("ret:%d\n",ret);
    return 0;
}

        运行结果:

[admin@localhost pause_sleep]$ ./a.out 
pid 12516
^Ccatch signo 2   //大约1秒后按Ctrl+C发送2号信号
ret:19

        结果显示mysleep的返回值正确。

注意:

(1)在mysleep程序中注册SIGALRM的处理函数,是为了使pause出错返回,使进程从挂起状态醒来。如果不注册该函函数,因为SIGALRM信号的默认处理动作是终止进程,所以在seconds秒后,进程会直接终止而不是醒来继续执行后面的语句。

而在处理函数中什么都没做,是因为sleep函数挂起结束后也什么都没做

(2)mysleep中再返回前恢复SIGALRM信号的原有处理动作,是因为在mysleep结束后,进程结束前,如果再次发送SIGALRM信号本意是想终止进程,但因为SIGALRM信号是自定义处理动作,所以不会终止进程。

(3)mysleep函数的返回值与sleep函数的返回值作用相同。

二,可重入函数

        上面有提及,main函数和handler函数是不同的执行流。当main函数在调用一个函数如insert函数还未返回时,由于信号中断在执行信号的自定义行为时,再次调用insert函数,这就叫重入。如果insert函数访问的是一个全局链表,有可能因为重入导致结果错乱,这就叫不可重入函数。反之,如果insert函数只访问自己的局部变量或参数,则成为可重入函数。因为局部变量会保存在各自的栈帧结构中,而不会相互影响。

如果一个函数符合下述条件之一则是不可重入的:

(1)调用了malloc或free,因为malloc是用全局链表来管理堆的

(2)调用了I/O库函数,标准I/O库的很多实现都是以不可重入的方式使用全局数据结构的。

(3)函数中有很多的全局变量

三,SIGCHLD信号

        在前面的进程控制中有提到子进程退出时,如果父进程不回收子进程的资源,就会造成内存泄漏。所以父进程通过等待的方式来回收子进程资源。父进程若以阻塞的方式等待,子进程不结束,父进程除了等待也不能干其他事。父进程以非阻塞的方式等待,就需要在做其他事的同时一直询问子进程是否结束。这两种方式都是父进程的工作效率变得很低。

        实际上,子进程在结束的时候会给父进程发送一个SIGCHLD信号,通知父进程它现在要退出了。所以,父进程可以一直做自己的事情直到收到子进程发送的信号SIGCHLD,在以等待的方式回收子进程资源。因此,可以父进程可以将SIGCHLD信号进行捕捉,在自定义函数中以等待的方式回收子进程资源。这样做便可以提高父进程的工作效率。

        如果在同一时刻有多个子进程退出,即收到多个SIGCHLD信号。如果父进程不及时处理,可能会使一些SIGCHLD信号被忽略,从而无法回收子进程资源造成内存泄漏。所以,在自定义函数中,要以循环的方式进行等待,如果有子进程结束,就立即等待回收资源,如果没有则返回父进程的执行流中继续执行父进程的工作。所以,这里父进程还需要以非阻塞的方式进行等待,否则会一直阻塞在自定义函数中,同样会使父进程效率低下。handler函数代码如下:

void handler(int signo)
{
    pid_t pid;
    while(pid = waitpid(-1,NULL,WNOHANG) > 0)
    {
        printf("child exit. child pid is %d\n",ret);
    }
}          

        在linux中,如果将SIGCHLD的处理动作设置为SIG_IGN,此时fork出来的子进程在退出时会自己清理掉,而不会通知父进程也不会产生僵尸进程。用以下代码来演示:

int main()
{
    struct sigaction act,oact;
    act.sa_handler = SIG_IGN;//将SIGCHLD信号的处理动作设置为默认
    act.sa_flags = 0;
    sigaction(SIGCHLD,&act,&oact);//对比该语句设置前后子进程退出后的状态变化
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("i am child pid is %d\n",getpid());
        int count = 3;
        while(count--)
        {
            printf("hello world\n");
            sleep(1);
        }
        exit(2);
    }
    while(1)  //父进程没有等待
    {
        printf("i am father\n");
        sleep(1);
    }
    return 0;
}           

        运行结果:

[admin@localhost SIGCHLD]$ while :; do ps axj | grep a.out | grep -v grep;sleep 1;echo "################";done
10912 11922 11922 10912 pts/0    13395 T      500   0:00 ./a.out
10912 13395 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
13395 13396 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13395 T      500   0:00 ./a.out
10912 13395 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
13395 13396 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13395 T      500   0:00 ./a.out
10912 13395 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
13395 13396 13395 10912 pts/0    13395 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13395 T      500   0:00 ./a.out
10912 13395 13395 10912 pts/0    13395 S+     500   0:00 ./a.out

        在代码中父进程并没有等待子进程,但子进程退出时也没有产生僵尸进程。

        当取消上述代码中的sigaction语句时,结果如下:

[admin@localhost SIGCHLD]$ while :; do ps axj | grep a.out | grep -v grep;sleep 1;echo "################";done
10912 11922 11922 10912 pts/0    13463 T      500   0:00 ./a.out
10912 13463 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
13463 13464 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13463 T      500   0:00 ./a.out
10912 13463 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
13463 13464 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13463 T      500   0:00 ./a.out
10912 13463 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
13463 13464 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
################
10912 11922 11922 10912 pts/0    13463 T      500   0:00 ./a.out
10912 13463 13463 10912 pts/0    13463 S+     500   0:00 ./a.out
13463 13464 13463 10912 pts/0    13463 Z+     500   0:00 [a.out] <defunct>
        此时,父进程也没有等待子进程,但子进程在退出后变成僵尸进程了。


        

        










SIGCHLD信号处理

在apue这本书中,介绍了早期System V不可靠信号中SIGCLD的经典语义。如在RH7.2上编译并运行该程序则一切正常(不会出现重复打印"SIGCLD received"), 因为:  ...
  • stonesharp
  • stonesharp
  • 2016-10-15 15:10:32
  • 690

linux 信号 可重入函数

一、可重入函数 1)什么是可重入性? 可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能...
  • u013378057
  • u013378057
  • 2016-01-28 20:40:48
  • 390

UNIX网络编程笔记(5):处理SIGCHLD信号

在上一讲中,我们使用fork函数得到了一个简单的并发服务器。然而,这样的程序有一个问题,就是当子进程终止时,会向父进程发送一个SIGCHLD信号,父进程默认忽略,导致子进程变成一个僵尸进程。僵尸进程一...
  • u012877472
  • u012877472
  • 2015-12-03 19:16:16
  • 742

linux的SIGCHLD信号

SIGCHLD信号 SIGCHLD的产生条件 子进程终止时 子进程接收到SIGSTOP信号停止时 子进程处在停止态,接受到SIGCONT后唤醒时 借助SIGCHLD信号回收子进程 #inc...
  • oguro
  • oguro
  • 2016-12-24 11:26:32
  • 2178

SIGCHLD 信号

SIGCHID:子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时...
  • youngyoungla
  • youngyoungla
  • 2016-06-12 12:47:53
  • 809

关于linux环境下信号SIGCHLD的排队机制

2006-05-31 11:38:35 分类: LINUX 一直对这个问题没有深入的思考过。最近由于项目的需要终于弄清了这个问题。 以下文字是抄袭+理解+估计: 在linux系统中,...
  • pzqingchong
  • pzqingchong
  • 2016-10-18 21:35:22
  • 416

linux下同时使用wait和SIGCHLD的信号处理函数

前一段时间写代码用到了子进程,设置了SIGCHLD的信号处理函数,并且父进程使用wait等待子进程结束,运行的结果和我想象的不大一样。 原型抽取如下: /* * SigChld.cpp *...
  • worshiper
  • worshiper
  • 2012-11-17 10:54:43
  • 3025

信号处理函数遇上不可重入函数

123
  • qq_18973645
  • qq_18973645
  • 2017-01-23 19:10:28
  • 228

SIGCHLD

在Linux系统下,子进程状态改变后产生此信号,如子进程停止(STOP)、子进程终止(terminate)等。在Linux系统下,如果进程明确地将该信号的配置设置为SIG_IGN,则调用进程的子进程将...
  • tengyft
  • tengyft
  • 2015-05-27 20:56:53
  • 2148

信号--SIGCLD语义

信号--SIGCLD语义 两个不停产生混淆的信号的SIGCLD和SIGCHLD。首先,SIGCLD(没有H)是系统V的名字,这个信号有着和名为SIGCHLD的BSD信号不同的语义。POSIX.1...
  • u012570105
  • u012570105
  • 2015-08-25 15:20:04
  • 549
收藏助手
不良信息举报
您举报文章:实现mysleep,可重入函数,SIGCHLD信号
举报原因:
原因补充:

(最多只允许输入30个字)