Unix单实例后台守护进程的“规范方法”

此前的笔记《Linux程序已运行实体的检测以及其PID的获得》通过Linux里面的bash命令来获取正在运行的同名进程的pid,正如网友AzureSky提醒说实际上平常此类程序所用的方法都是文件锁,比如dhclient、apt-get系列、pacman诸如此类,都是通过往/var/run里面写入一个包含自身pid的xx.pid文件,同时加上排他锁,只要当前进程还在,新的进程就不可能再加锁,也就能检测出是否单实例了。(AzureSky博客另外整理出APUE的一个实现

至于后台运行,都是通过fork来生成新进程,虽然还需要setsid等的调用以确保子进程正常运行,不过这里暂不详细讨论,重点关注文件排他锁和生成的后台进程的“规范方法“(参考《Unix环境高级编程 APUE 第二版》)。

整个过程是这样的:

  1. 打开文件;
  2. 获得该文件的锁的状态;
  3. 如果没有锁,则给该文件加上自己的锁并写入自己的pid*;否则获得锁着该文件的进程的pid
  4. 如果能够顺利加上锁,则生成子进程;子进程等待父进程退出后马上给文件加上自己的锁;
*其实写不写入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;
}
文章分类 Unix/Linux 标签: , , ,

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*