此前的笔记《Linux程序已运行实体的检测以及其PID的获得》通过Linux里面的bash命令来获取正在运行的同名进程的pid,正如网友AzureSky提醒说实际上平常此类程序所用的方法都是文件锁,比如dhclient、apt-get系列、pacman诸如此类,都是通过往/var/run里面写入一个包含自身pid的xx.pid文件,同时加上排他锁,只要当前进程还在,新的进程就不可能再加锁,也就能检测出是否单实例了。(AzureSky博客另外整理出APUE的一个实现)
至于后台运行,都是通过fork来生成新进程,虽然还需要setsid等的调用以确保子进程正常运行,不过这里暂不详细讨论,重点关注文件排他锁和生成的后台进程的“规范方法“(参考《Unix环境高级编程 APUE 第二版》)。
整个过程是这样的:
- 打开文件;
- 获得该文件的锁的状态;
- 如果没有锁,则给该文件加上自己的锁并写入自己的pid*;否则获得锁着该文件的进程的pid
- 如果能够顺利加上锁,则生成子进程;子进程等待父进程退出后马上给文件加上自己的锁;
*其实写不写入pid对整个过程影响不大,只是惯例如此。
过程并不复杂,纠结的地方就是,文件锁是针对进程而言的,在生成后台进程的过程中,父进程和子进程虽然说拥有共同的文件描述字之类的资源,但由于是两个完全不同的进程,文件锁并不会继承过去。只有当父进程完全退出,失去对文件的锁操作后,子进程才能重新获得锁。
文件锁的调用fcntl函数的cmd参数里面有F_SETLK和F_SETLKW两个,后者是阻塞式的调用,就非常适合上述子进程等待父进程退出以获得锁的情形。
PT根据《APUE 2nd》F14.5、F14.6 整理出来的一份示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <string.h> #define LOCKFILE "/tmp/run/test.pid" #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) int lockfile; /* 锁文件的描述字 */ void flock_reg (); /* 注册文件锁 */ int program_running_check(); /* 锁控制函数 */ void daemon_init(); /* 转换到后台进程 */ int main() { //打开锁文件 lockfile = open (LOCKFILE, O_RDWR | O_CREAT , LOCKMODE); if (lockfile < 0){ perror ("Lockfile"); exit(1); } // 检测可否获得锁 int mun; if ( (mun = program_running_check())){ printf("Instance with pid %d running, just exitn", mun); exit(1); } //转换到后台式运行 daemon_init(); printf("sleepingn"); while (1); return 0; } void flock_reg () { char buf[16]; struct flock fl; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; fl.l_type = F_WRLCK; fl.l_pid = getpid(); //阻塞式的加锁 if (fcntl (lockfile, F_SETLKW, &fl) < 0){ perror ("fcntl_reg"); exit(1); } //把pid写入锁文件 ftruncate (lockfile, 0); sprintf (buf, "%ld", (long)getpid()); write (lockfile, buf, strlen(buf) + 1); } void daemon_init() { pid_t pid; if ( (pid = fork()) < 0) { perror ("Fork"); exit(1); } //父进程,打印该信息后马上退出 if (pid) { fprintf(stdout, "&&Info: Forked background with PID: [%d]n", pid); exit(0); } //子进程,设置相关环境 setsid(); /* become session leader */ chdir("/"); /* change working directory */ umask(0); /* clear our file mode creation mask */ //子进程重新加锁 flock_reg (); } int program_running_check() { struct flock fl; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; fl.l_type = F_WRLCK; //尝试获得文件锁 if (fcntl (lockfile, F_GETLK, &fl) < 0){ perror ("fcntl_get"); exit(1); } //没有锁,则给文件加锁,否则返回锁着文件的进程pid if (fl.l_type == F_UNLCK) { flock_reg (); return 0; } return fl.l_pid; } |
发表评论