linux下单例进程的一个实现方式_linux实现单例进程

今天在论坛上有朋友问如果截获SIGKILL信号,然后删除锁文件,他主要想要做一个单例进程,进程开始时判断一个文件config的存在性,如果不存在,则创建之然后运行,如果已经存在那么进程退出,进程在退出的时候必须把这个config文件删除以确保不能影响下一次进程的运行,但是问题出来了,如果有人用SIGKILL把他的进程杀死怎么办?于是我的回答就是再建一个进程守护着它的运行,比如用心跳检测就可以,一旦检测不到这个进程的心跳了,那么就帮忙删除掉这个config文件,另一种做法就是将这个单例的进程作为一个父进程的子进程运行,然后在父进程中设置SIGCHLD信号处理器,一旦子进程被杀,那么就会发送信号给父进程,然后父进程在信号处理中可以删除config文件,这样做的话仅仅是多了一层保护,试想如果守护此进程运行的进程或者这个进程的父进程被杀怎么办?改内核是一种方式,比如钩住do_exit内核函数,然后判断结束的是哪个进程后做善后处理,但是如果谁都要通过改内核行为来解决问题的话,那么操作系统的意义何在,内核是为系统服务的,而不是为用户服务的,因此不要指望内核行为完成用户策略,因此这些方式都还不是十全十美的方式。十全十美的方式就是自洽的,不需要别的东西帮忙的方式,不用别的进程,不用什么锁文件,而且这种方式还不能影响别的进程,不能给系统带来不稳定性,因此linux的“小即是好”的思想派上了用场,linux总是可以用很多很小的东西组合成一个很猛的功能,比如今天我想到的一个办法就是用管道实现的。在windows上也可以这么实现但是windows上的管道要复杂些,而且windows中还可以有别的很多方式,比如windows中可以设置全局的信号量和互斥体,它们都可以实现,windows的粒度某种意义上更小,比如windows可以基于窗口发送消息,而linux在系统范围内发送消息的最小单位就是进程,比如基于进程发送信号,虽然如此,windows总是带来很多混乱因为它没有处理好大和小的边界,粒度小是好的,比如像linux哲学所述的那样,但是过于小的话就会相互杂糅不稳定,过大又会臃肿不灵活。因此设计粒度必须是可控范围内的最小值,这个可控范围就是进程,而且必须有一个统一的实体来控制粒度,比如unix/linux中将很多东西往进程的意义上靠拢,因此进程就是unix/linux的中心。下面看看我实现单例的方式吧:
#include

int check( char * name )

{

    int len = strlen(name),i = 0;

        for(;name[len-1-i]!='/'&&i
        name = name+len-i;

    char cmd[64];

    sprintf(cmd,"ps -e|grep %s|awk '{print $4}'",name); //拼串,这就是创举的证明,ps,grep,awk都是用管道结合的,而且代价很小,并且ps,grep,awk都不大

        FILE *fp;

        char pro[64];  //64个字节足够存放进程名字了

        fp = popen(cmd, "r");

        if(fp != 0)

        {

                int count = 0; //计数器,记录一共有多少个该进程在运行

                while(fgets(pro, 64, fp) > 0)

                {

                        pro[strlen(pro)-1]=0; //去掉后面的换行

                        if(!strcmp(pro,name))

                                count++;

                }

                if( count > 1 )    //超过一个就退出

            return 1; 

                pclose(fp);

        }

    return 0;

}

int main(int argc, char* argv[])

{

    if(check(argv[0]))

    {

        printf("This programe is already runing/n");

        exit(1);

    }

...//做真正的事

        return 0;

}

注释中提到管道的代价不大,事实上很小,看看内核的sys_pipe就知道了,这里就不说了,但是还是有必要说一下用户空间的popen,它是管道创建和执行shell的封装:

FILE * popen(const char *cmdstring, const char *type)

{

    int        i, pfd[2];

    pid_t    pid;

    FILE    *fp;

...   //判断参数,并且验证其正确性

    pipe(pfd)  //创建一对管道

    if ( (pid = fork()) == 0)  //准备在子进程里面执行新的任务cmdstring

    {                               

        if (*type == 'r') {  //如果父进程是用来read的,那么子进程就是write的,因此关闭掉子进程一对管道的read端

            close(pfd[0]);

            if (pfd[1] != STDOUT_FILENO) {

                dup2(pfd[1], STDOUT_FILENO); //因为子进程是write端,因此将子进程的标准输出重新定向到新创建管道的写端,也就是exec出来的进程的输出全部写到管道里面了,然后父进程将之读出。

                close(pfd[1]); //关闭掉管道描述符,它已经没有用了,因为标准输出已经dup到它的内核实体了

            }

        } else {  //对于父进程是写端的情况和上述情况相反,故而不再赘述。

...//关闭文件描述符

        execl(SHELL, "sh", "-c", cmdstring, (char *) 0);//真正执行命令

...

    }

    if (*type == 'r') {  //如果父进程为read端,那么要将新创建管道的write端返回给调用的用户

        close(pfd[1]);

        if ( (fp = fdopen(pfd[0], type)) == NULL)

            return(NULL);

    } else {//如果父进程为write端则相反

...

    return(fp);  //返回文件描述符

}

那位朋友问是否不应该用文件锁实现单例,我的回答是:

你的办法是标准的做法,别人用SIGKILL杀你的进程才是不好的做法,任何事情都不能做那么绝,冲动是魔鬼。如果你再怕别人用SIGKILL杀你的进 程,那么我刚才给你的程序会有用,不过最好在linux和unix上都测试一下,我怕ps和awk的输出语义在不同系统上会有不同。

他问:除了再设置一个守护进程看护这个单例进程外是否还有别的办法。我的回答:

写内核模块吧,钩住do_exit内核函数,在里面判断是否退出的是你的那个进程,如果是的话就删除掉那么config文件,然后继续正常的流程。不过我 觉得没有必要这么做,就好像人死亡一样,如果是得病死去或者自杀,那么有足够的时间交待后事,当然猝死病除外,然而意外死亡的话,比如车祸,误杀等等几乎 没有任何机会交待后事的。

他最开始问:能否捕捉到SIGKILL信号,然后做善后工作。我的回答:

对于SIGKILL,被杀进程本身没有机会做任何事情,但是并不是说就没有办法做善后工作了,你可以把你的进程作为一个进程的子进程,然后在父进程里面设 置SIGCHLD处理器,一旦子进程被杀或者退出,那么父进程可以帮忙善后;另外你还可以写任何一个进程来看护你的这个需要善后的进程,办法多的是,比如 可以用心跳

原文链接: https://blog.csdn.net/dog250/article/details/5303274

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    linux下单例进程的一个实现方式_linux实现单例进程

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410441

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 下午12:01
下一篇 2023年4月26日 下午12:01

相关推荐