linux c文件锁 flock()、fcntl()

2018-06-20 21:21:37

同步技术我们有讲过信号,本篇我们介绍专门为文件设计的同步技术。

由于 stdio 库会在用户空间进行缓冲,因此在混合使用 stdio 函数与本章介绍的加锁技术时需要特别小心。这里的问题是一个输入缓冲区在被加锁之前可能会被填满或者一个输出缓冲器在锁被删除之后可能会被刷新。要避免这些问题则可以采用下面这些方法。

1.使用 read() 和 write()(以及相关的系统调用)取代 stdio 库来执行文件io。
2.在对文件加锁之后立即刷新 stdio 流,并且在释放锁之前立即再次刷新这个流。
3.使用 setbuf()(或类似的函数)来禁用 stdio 缓冲,当然这可能会牺牲一些效率。


使用flock() 给文件加锁

#include <sys/file.h>

int flock(int fd, int operation);

//Returns 0 on success, or –1 on error

fd 为打开的文件描述符,operation 参数可取的值为

值  描述
 LOCK_SH 放置共享锁
 LOCK_EX 放置互斥锁
 LOCK_UN 解锁
 LOCK_NB 非阻塞锁请求

在默认情况下,如果另一进程已经持有了文件上的一个不兼容的锁,那么flock()会阻塞,如果设置成非阻塞模式,那么将返回-1,errno设置成 EWOULDBLOCK。

任意数量的进程可以持有文件的共享锁,但在同一时刻只能有一个进程拥有互斥锁,一旦一个进程拥有互斥锁,其他进程的共享锁请求也会被拒绝。

通过再次调用 flock() 并在 operation 参数中指定恰当的值可以将一个既有共享锁转换成一个互斥锁(反之亦然)。将一个共享锁转换成一个互斥锁,在另一个进程持有了文件上的共享锁时会阻塞,除非同时指定了 LOCK_NB 标记。 

锁转换的过程不一定是原子的。在转换过程中首先会删除既有的锁,然后创建一个新锁。在这两步之间另一个进程对一个不兼容锁的悬而未决(pending)请求可能会得到满足。如果发生了这种情况,那么转换过程会被阻塞,或者在指定了LOCK_NB 的情况下转换过程会失败并且进程会丢失其原先持有的锁。

例子

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <sys/mman.h>
#include <time.h>

char *currTime(const char *fmt);

void errExit(char *msg){
    perror(msg);
    exit(1);
}

#define BUF_SIZE 1000

char * currTime(const char *format){
    static char buf[BUF_SIZE];
    time_t t;
    size_t s;
    struct tm *tm;

    t = time(NULL);
    tm = localtime(&t);
    if (tm == NULL)
        return NULL;

    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

    return (s == 0) ? NULL : buf;
}

int main(int argc, char *argv[]){
    int fd, lock;
    const char *lname;

    if (argc < 3 || strcmp(argv[1], "--help") == 0 || strchr("sx", argv[2][0]) == NULL){
        printf("%s file lock [sleep-time]\n"
                 "    'lock' is 's' (shared) or 'x' (exclusive)\n"
                 "        optionally followed by 'n' (nonblocking)\n"
                 "    'sleep-time' specifies time to hold lock\n", argv[0]);
        return 0;
    }

    lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
    if (argv[2][1] == 'n')
        lock |= LOCK_NB;

    fd = open(argv[1], O_RDONLY);               /* Open file to be locked */
    if (fd == -1)
        errExit("open");

    lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";

    printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
            currTime("%T"));

    if (flock(fd, lock) == -1) {
        if (errno == EWOULDBLOCK){
            printf("PID %ld: already locked - bye!", (long) getpid());
            return 1;
        }else{
            printf("flock (PID=%ld)", (long) getpid());
            return 1;
        }
    }

    printf("PID %ld: granted    %s at %s\n", (long) getpid(), lname, currTime("%T"));

    sleep((argc > 3) ? atoi(argv[3]) : 10);

    printf("PID %ld: releasing  %s at %s\n", (long) getpid(), lname, currTime("%T"));
    if (flock(fd, LOCK_UN) == -1)
        errExit("flock");

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out tmp.txt x 10
PID 675: requesting LOCK_EX at 16:59:30
PID 675: granted    LOCK_EX at 16:59:30
PID 675: releasing  LOCK_EX at 16:59:40
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out tmp.txt s 10
PID 679: requesting LOCK_SH at 16:59:38
PID 679: granted    LOCK_SH at 16:59:40
PID 679: releasing  LOCK_SH at 16:59:50

锁释放

把operation 参数指定为 LOCK_UN 可以释放一个文件锁,锁也会在文件描述符关闭之后被释放。但问题其实要更加复杂。

1.通过复制出来的文件描述符会引用同一个文件锁,但是如果不显性的释放锁,那么必须把所有打开文件描述符关闭后才能自动释放。比如下面如果不显性调用 flock(newfd, LOCK_UN); 那么,必须关闭了fd和 newfd 后才能显性的释放。通过fork()出来的子进程会复制其父进程的文件描述符,效果一样。

flock(fd, LOCK_EX); 
newfd = dup(fd);        //这时其实还是共用一个锁
flock(newfd, LOCK_UN);  //fd的锁被释放

2.如果多次使用open(),那么第二个文件描述符会当成不同的描述符。

fd1 = open("a.txt", O_RDWR);
fd2 = open("a.txt", O_RDWR);
flock(fd1, LOCK_EX);
flock(fd2, LOCK_EX);   //会阻塞,等待fd1释放

3.通过flock() 创建的锁在 exec() 中会得到保留。除非设置了 close-on-exec,并且无其他文件描述符副本才会释放锁。

flock() 的限制

通过 flock() 放置的锁存在几个限制。

1.只能对整个文件加锁。这种粗粒度的加锁会限制协作进程之间的并发性。
2.通过 flock() 只能放置劝告式锁。
3.很多 NFS 实现不识别 flock() 放置的锁。

下一节中介绍的 fcntl() 加锁模型弥补了所有这些不足。


使用 fcntl() 加锁

使用fcntl() 可以在文件的任意部分上放置一把锁。这种形式的加锁通常叫做记录加锁。下图显示了如何使用记录锁来同步两个进程对文件的同一块区域的访问。fcntl()要么会锁定整个指定区域,要么不会对任何字节锁定。

用来创建或删除一个文件锁的 fcntl() 调用的常规形式如下。

struct flock flockstr;

fcntl(fd, cmd, &flockstr); 
struct flock {
    short l_type;   /* 锁类型: F_RDLCK, F_WRLCK, F_UNLCK */
    short l_whence; /* SEEK_SET(起始),SEEK_CUR(当前), SEEK_END(结尾) */
    off_t l_start;   /* 根据l_whence决定偏移量,可以为负数 */
    off_t l_len;     /* 锁定字节数,0 代表直到EOF */
    pid_t l_pid;     /* 阻止我们取得锁的pid (F_GETLK only) */
};

l_type 解释如下表

 锁的类型描述 
 F_RDLCK 放置一把读锁
 F_WRLCK 放置一把写锁
 F_UNLCK 删除一把既有锁

上面的语义分别对应 flock() 的共享锁、互斥锁、LOCK_UN。

放置读锁必须打开文件允许读,写锁要允许写,要放置两种锁就必须打开文件允许读写。试图在文件上放置不兼容的锁会导致 EBADF 错误。

cmd 参数可以取下面3个值。

F_SETLK 获取( l_type 是 F_RDLCK 或 F_WRLCK )或释放(l_type 是 F_UNLCK)由 flockstr 指定的字节上的锁。如果进程间放置不兼容的锁会失败并返回 EAGAIN 错误。在一些 UNIX 实现上返回 EACCES 错误。

F_SETLKW 跟 F_SETLK一样,除了遇到不兼容锁时会阻塞。如果信号没有指定 SA_RESTART 那么会被打断并返回 EINTR 错误。开发人员可以使用 alarm() 或 setitimer()来为一个加锁请求设置超时时间。

F_GETLK 用来检测获取flockstr 指定区域上的锁。l_type 字段的值必须为 F_RDLCK 或 F_WRLCK。如果允许加锁,l_type 字段会返回 F_UNLCK,并且剩余字段会保持不变。如果获取失败,那么flockstr 会返回其中一把阻止我们获取的锁相关信息,包括l_type,字节范围(l_start 和 l_len;l_whence 总是为 SEEK_SET)以及持有这把锁的进程id。注意:在使用F_GETLK 之后接着使用 F_SETLK 或 F_SETLKW的话可能会出现竞争关系。因为就算你检测了可以上锁,刚检测完在正式上锁之前,其他进程有可能抢先一步上锁。所以仍然需要对 F_SETLK 返回错误或 F_SETLKW 阻塞做好准备。


锁获取和释放细节

1.解锁一块文件区域总是会立即成功。即使当前并不持有一块区域上的锁,对这块区域解锁也不是一个错误。

2.在任何一个时刻,一个进程只能持有一个文件的某个特定区域的一种锁。在之前己经锁住的区域上放置一把新锁会导致:1.不发生任何事情(新锁的类型与既有锁的类型是一样的)。2.原子地将既有锁转换成新模式。在后一种情况中,当将一个读锁转换成写锁时需要为调用返回一个错误( F_SETLK )或阻塞( F_SETLKW )做好准备。(这与 flock() 是不同的,它的锁转换不是原子的。)

3.一个进程永远都无法将自己锁在一个文件区域之外,即使通过多个引用同一文件的文件描述符放置锁也是如此。(这与 flock()是不同的)

4.在已经持有的锁中间放置一把模式不同的锁会产生三把锁:在新锁的两端会创建两个模式为之前模式的更小一点的锁(如下图)。与此相反的是,获取与模式相同的一把既有锁相邻或重叠的第二把锁会产生单个覆盖两把锁的合并区域的聚合锁。除此之外,还存在其他的组合情况。如对一个大型既有锁的中间的一个区域进行解锁会在已解锁区域的两端产生两个更小一点的已锁住区域。如果一个新锁与一个模式不同的既有锁重叠了,那么既有锁就会收缩,因为重叠的字节会合并进新锁中。

例子-交互式加锁

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <sys/mman.h>
#include <time.h>

#define MAX_LINE 100

void errExit(char *msg){
    perror(msg);
    exit(1);
}

static void displayCmdFmt(void){
    printf("\n    Format: cmd lock start length [whence]\n\n");
    printf("    'cmd' is 'g' (GETLK), 's' (SETLK), or 'w' (SETLKW)\n");
    printf("    'lock' is 'r' (READ), 'w' (WRITE), or 'u' (UNLOCK)\n");
    printf("    'start' and 'length' specify byte range to lock\n");
    printf("    'whence' is 's' (SEEK_SET, default), 'c' (SEEK_CUR), "
           "or 'e' (SEEK_END)\n\n");
}

int main(int argc, char *argv[]){
    int fd, numRead, cmd, status;
    char lock, cmdCh, whence, line[MAX_LINE];
    struct flock fl;
    long long len, st;

    if (argc != 2 || strcmp(argv[1], "--help") == 0){
        printf("%s file\n", argv[0]);
        return 0;
    }
        

    fd = open(argv[1], O_RDWR);
    if (fd == -1)
        errExit("open");

    printf("Enter ? for help\n");

    for (;;) {          /* Prompt for locking command and carry it out */
        printf("PID=%ld> ", (long) getpid());
        fflush(stdout);

        if (fgets(line, MAX_LINE, stdin) == NULL)       /* EOF */
            exit(EXIT_SUCCESS);
        line[strlen(line) - 1] = '\0';

        if (*line == '\0')
            continue;

        if (line[0] == '?') {
            displayCmdFmt();
            continue;
        }

        whence = 's';                   /* In case not otherwise filled in */

        numRead = sscanf(line, "%c %c %lld %lld %c", &cmdCh, &lock,
                        &st, &len, &whence);
        fl.l_start = st;
        fl.l_len = len;

        if (numRead < 4 || strchr("gsw", cmdCh) == NULL ||
                strchr("rwu", lock) == NULL || strchr("sce", whence) == NULL) {
            printf("Invalid command!\n");
            continue;
        }

        cmd = (cmdCh == 'g') ? F_GETLK : (cmdCh == 's') ? F_SETLK : F_SETLKW;
        fl.l_type = (lock == 'r') ? F_RDLCK : (lock == 'w') ? F_WRLCK : F_UNLCK;
        fl.l_whence = (whence == 'c') ? SEEK_CUR :
                      (whence == 'e') ? SEEK_END : SEEK_SET;

        status = fcntl(fd, cmd, &fl);           /* Perform request... */

        if (cmd == F_GETLK) {                   /* ... and see what happened */
            if (status == -1) {
                printf("fcntl - F_GETLK\n");
                return 1;
            } else {
                if (fl.l_type == F_UNLCK)
                    printf("[PID=%ld] Lock can be placed\n", (long) getpid());
                else                            /* Locked out by someone else */
                    printf("[PID=%ld] Denied by %s lock on %lld:%lld "
                            "(held by PID %ld)\n", (long) getpid(),
                            (fl.l_type == F_RDLCK) ? "READ" : "WRITE",
                            (long long) fl.l_start,
                            (long long) fl.l_len, (long) fl.l_pid);
            }
        } else {                /* F_SETLK, F_SETLKW */
            if (status == 0)
                printf("[PID=%ld] %s\n", (long) getpid(),
                        (lock == 'u') ? "unlocked" : "got lock");
            else if (errno == EAGAIN || errno == EACCES)        /* F_SETLK */
                printf("[PID=%ld] failed (incompatible lock)\n",
                        (long) getpid());
            else if (errno == EDEADLK)                          /* F_SETLKW */
                printf("[PID=%ld] failed (deadlock)\n", (long) getpid());
            else
                errExit("fcntl");
        }
    }
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out tmp.txt 
Enter ? for help
PID=2056> ?

    Format: cmd lock start length [whence]

    'cmd' is 'g' (GETLK), 's' (SETLK), or 'w' (SETLKW)
    'lock' is 'r' (READ), 'w' (WRITE), or 'u' (UNLOCK)
    'start' and 'length' specify byte range to lock
    'whence' is 's' (SEEK_SET, default), 'c' (SEEK_CUR), or 'e' (SEEK_END)

#0-9这十个字节设置写锁
PID=2056> s w 0 10 s        ---1
[PID=2056] got lock

#解锁
PID=2056> s u 0 10 s        ---5
[PID=2056] unlocked
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out tmp.txt 
Enter ? for help

#尝试在第1个字节获取读锁,失败
PID=2058> s r 0 1 s       ---2
[PID=2058] failed (incompatible lock)

#尝试获取第10个字节的写锁,失败
PID=2058> s w 9 1 s       ---3
[PID=2058] failed (incompatible lock)

#尝试获取第11个字节的写锁,成功
PID=2058> s w 10 1 s      ---4
[PID=2058] got lock

#因为在第5步已经解锁,成功
PID=2058> s w 0 1 s      ---6
[PID=2058] got lock


文件锁的性能

内核会为文件锁维护一个链表,如下图。

这个链表会按照进程id和偏移量来排序。每次需要在这个数据结构中添加一把新锁时,都会从头到尾遍历来检查冲突。假设有大量的锁随机的分布在很多进程中,那么可以说,添加或删除一个锁所需的时间和文件上已有的锁的数量之间是一个线性关系。


继承和释放锁的语义

fcntl() 记录锁继承和释放的语义与使用 flock() 创建的锁的继承和释放的语义是不同的,以下几点需要注意。

1.由 fork() 创建的子进程不会继承记录锁。这与 flock() 是不同的,在使用 flock() 创建的锁时,子进程会继承一个引用同一把锁的引用并且能够释放这把锁,从而导致父进程也会失去这把锁。

2.记录锁在 exec() 中会得到保留。(但需要注意下面描述的。close-on-exec 标记的作用。)。

3. 一个进程中的所有线程会共享同一组记录锁。记录锁同时与一个进程和一个 i-node关联。从这种关联关系可以得出当一个进程终止之后,其所有记录锁会被释放。另一个稍微有点出乎意料的结果是当一个进程关闭了一个文件描述符之后,进程持有的对应文件的所有锁会被释放,不管这些锁是通过哪个文件描述符获得的。例如在下面的代码中, dose(fd2) 调用会释放调用进程持有的 testfile 文件之上的锁,尽管这把锁是通过文件描述符 fd1 获得的。

struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;

fd1 = open("testfile", O_RDWR);
fd2 = open("testfile", O_RDWR);

if (fcntl(fd1, cmd, &fl) == -1)
    errExit("fcntl");
close(fd2);

以上讲的都是劝告式加锁,也就是即使该文件上有独占写锁,你也可以忽略它并写入数据。


强制加锁

想启用强制锁,要经历两个步骤:

1.文件系统上开启,shell上挂载的时候加上 -o mand,也可以在程序上调用mount() 时指定 MS_MANDLOCK 标记

mount -o mand /dev/sda10 /testfs

#查看
mount | grep sda10
#/dev/sda10 on /testfs type ext4 (rw,mand)

2.文件上开启打开强制加锁,开启 set-group-id 权限位和关闭 group-execute 权限位。

chmod g+s,g-x /testfs/file

强制加锁警告

强制式锁所起的作用其实没有其一开始看起来那么大,它存在一些潜在的缺陷和问题。

1.在一个文件上持有一把强制式锁并不能阻止其他进程删除这个文件,因为只要在父目录上拥有合适的权限就能够与一个文件断开链接。

2.在一个可公开访问的文件上启用强制式锁之前需要经过深思熟虑,因为即使是特权进程也无法覆盖一个强制式锁。恶意用户可能会持续地持有该文件上的锁以制造拒绝服务的攻击。(在大多数情况下可以通过关闭 set-group-ID 位来使得该文件再次可访问,但当强制式文件锁造成系统挂起时就无法这样做了。)

3.使用强制式加锁存在性能开销。在启用了强制式加锁的文件上执行的每个 IO 系统调用中,内核都必须要检查在文件上是否存在冲突的锁。如果文件上存在大量的锁,那么这种检查工作会极大地降低 IO 系统调用的效率。

4.强制式加锁还会在应用程序设计阶段造成额外的开销,因为需要处理每个 IO 系统调用返回 EAGAIN(非阻塞 I/O )或 EDEADLK (阻塞 I/O )错误的情况。

5.因为在当前的 Linux 实现中存在一些内核竟争条件,因此在有些情况下执行 IO 操作的系统调用在文件上存在本应该拒绝这些操作的强制式锁时也能成功。

最后一句话,能避免强制加锁的就避免。


/proc/locks

通过查看Linux 特有的/proc/locks 文件的内容能够查看系统中当前存在的锁。

[root@izj6cfw9yi1iqoik31tqbgz c]# cat /proc/locks 
1: POSIX  ADVISORY  WRITE 1248 fd:01:524784 0 EOF
2: POSIX  ADVISORY  WRITE 1248 fd:01:527043 0 EOF
...
180: FLOCK  ADVISORY  WRITE 1195 fd:01:1065623 0 EOF
181: FLOCK  ADVISORY  WRITE 878 fd:01:1058410 0 EOF
182: POSIX  ADVISORY  WRITE 498 00:13:12246 0 EOF
183: FLOCK  ADVISORY  WRITE 495 00:13:12202 0 EOF

从左到右8个字段分别是

1.锁序号
2.FLOCK 表示 flock() 创建的锁,POSIX 表示 fcntl() 创建的锁
3.ADVISORY 代表劝告式锁,MANDATORY 代表强制锁
4.READ 或 WRITE (对应于 fcntl() 的共享锁和互斥锁)
5.持有锁的进程id
6.主设备号:次设备号:inode
7.锁的起始字节。对于flock()锁来说,永远为0
8.锁的结尾字节。EOF 代表延伸到结尾


仅运行一个实例

一些程序 -- 特别是很多 daemon -- 需要确保同一时刻只有一个程序实例在系统中运行。完成这项任务的一个常见方法是让 daemon 在一个标准目录中创建一个文件并在该文件上放置一把写锁。 daemon 在其执行期间一直持有这个文件锁并在即将终止之前删除这个文件。如果启动了 daemon 的另一个实例,那么它在获取该文件上的写锁时就会失败,其结果是它会意识到 daemon 的另一个实例肯定正在运行,然后终止。

/var/run 目录通常是存放此类锁文件的位置。daemon会将其进程id写入到锁文件,因此这个文件在命名时通常将 .pid 作为扩展名。还可以通过kill(pid, 0) 来检查进程id是否存在。

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <sys/mman.h>
#include <time.h>

#include <sys/stat.h>
#include <fcntl.h>

#define CPF_CLOEXEC 1
#define BUF_SIZE 100

#define PID_F "/tmp/freecls.pid"

int lockRegion(int fd, int type, int whence, int start, int len);
static int lockReg(int fd, int cmd, int type, int whence, int start, off_t len);

void errExit(char *msg){
    perror(msg);
    exit(1);
}

static int lockReg(int fd, int cmd, int type, int whence, int start, off_t len){
    struct flock fl;

    fl.l_type = type;
    fl.l_whence = whence;
    fl.l_start = start;
    fl.l_len = len;

    return fcntl(fd, cmd, &fl);
}

int lockRegion(int fd, int type, int whence, int start, int len){
    return lockReg(fd, F_SETLK, type, whence, start, len);
}

int createPidFile(const char *progName, const char *pidFile, int flags){
    int fd;
    char buf[BUF_SIZE];

    fd = open(pidFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1){
        printf("Could not open PID file %s", pidFile);
        return 1;
    }

    if (flags & CPF_CLOEXEC) {

        /* Set the close-on-exec file descriptor flag */

        flags = fcntl(fd, F_GETFD);                     /* Fetch flags */
        if (flags == -1){
            printf("Could not get flags for PID file %s", pidFile);
            exit(1);
        }

        flags |= FD_CLOEXEC;

        if (fcntl(fd, F_SETFD, flags) == -1){
            printf("Could not set flags for PID file %s", pidFile);
            exit(1);
        }
    }

    if (lockRegion(fd, F_WRLCK, SEEK_SET, 0, 0) == -1) {
        if (errno  == EAGAIN || errno == EACCES){
            printf("PID file '%s' is locked; probably "
                     "'%s' is already running\n", pidFile, progName);
            exit(1);
        }else{
            printf("Unable to lock PID file '%s'", pidFile);
            exit(1);
        }
            
    }

    if (ftruncate(fd, 0) == -1){
        printf("Could not truncate PID file '%s'", pidFile);
        exit(1);
    }
        
    snprintf(buf, BUF_SIZE, "%ld\n", (long) getpid());
    if (write(fd, buf, strlen(buf)) != strlen(buf)){
        printf("Writing to PID file '%s'", pidFile);
        exit(1);
    }
        

    return fd;
}

int main(){
    createPidFile("test",PID_F, 0);
    
    sleep(10);
    
    unlink(PID_F);
    
    return 0;
}

createPidFile()  函数中的一个精妙之处是使用 ftruncate() 来清除锁文件中之前存在的所有字符串。之所以要这样做是因为 daemon 的上一个实例在删除文件时可能因系统崩溃而失败。在这种情况下,如果新 daemon 实例的进程 ID 较小,那么可能就无法完全覆盖之前文件中的内容。例如,如果进程 ID 是 789 ,那么就只会向文件写入 789\n,但之前的 daemon 实例可能己经向文件写入了 12345\n ,这时如果不截断文件的话得到的内容就会是 789\n5\n。从严格意义上来讲,清除所有既有字符串并不是必需的,但这样做显得更加简洁并且能排除产生混淆的可能。

#运行并锁住文件
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out
[root@izj6cfw9yi1iqoik31tqbgz c]# ll /tmp
-rw------- 1 root root    5 Jun 21 14:10 freecls.pid

#在睡眠10s结束之前
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
PID file '/tmp/freecls.pid' is locked; probably 'test' is already running


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/5f

 

©著作权归作者所有
收藏
推荐阅读
  • linux c虚拟内存操作-mprotect()、mlock()、mlockatt()

    改变内存保护:mprotect()mprotect()系统调用修改起始位置为addr,长度为length字节的虚拟内存区域中分页上的保护。addr取值必须为分页大小的整数倍,length会被向上舍入到...

  • err
    linux c内存映射

    内存映射一般是用来做ipc进程间通信的,类似于一种共享内存技术。内存映射分为4种(假设有进程A和进程B):1.私有文件映射:2个进程都以一个文件中的内容来初始化内存内容,...

  • err
    linux c管道和FIFO

    每个 shell 用户都对命令中使用管道比价熟悉,如下面这个统计一个目录中文件的数目的命令所示。ls | wc -l为了执行上面的命令,shell 创建了2个进程分别执行...

  • linux c共享库(动态库)高级特性(2)

    动态加载库当一个可执行文件开始运行之后,动态链接器会加载程序的动态依赖列表中的所有共享库,但有些时候延迟加载库是比较有用的,如只在需要的时候再加载一个插件。动态链接器的这项功能是通过一组 API 来实...

  • err
    linux c共享库(动态库)基础(1)

    在很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源代码文件只编译一次,然后在需要的时候将它们链接进不同的可执行文件中。虽然这项技术能够竹省...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。