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

一,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>
        此时,父进程也没有等待子进程,但子进程在退出后变成僵尸进程了。


        

        










阅读更多
个人分类: Linux
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭