linux / fcntl 函数详解

零、作用

fcntl() 针对(文件)描述符提供控制。

  1. 复制一个现有的描述符(cmd = F_DUPFD)。 
  2. 获得/设置文件描述符标记(cmd = F_GETFDF_SETFD)。 
  3. 获得/设置文件状态标记(cmd = F_GETFLF_SETFL)。 
  4. 获得/设置异步I/O所有权(cmd = F_GETOWNF_SETOWN)。
  5. 获得/设置记录锁(cmd = F_GETLKF_SETLKF_SETLKW)。

一、原型

#include 
#include  
int fcntl(int fd, int cmd); 
int fcntl(int fd, int cmd, long arg); 
int fcntl(int fd, int cmd, struct flock *lock);

参数 fd 是被参数 cmd 操作的描述符。针对 cmd 的值,fcntl 能够接受第三个参数 int arg。

返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD、F_GETFD、F_GETFL 以及 F_GETOWN。
F_DUPFD        返回新的文件描述符
F_GETFD        返回相应标志
F_GETFL、F_GETOWN        返回一个正的进程 ID 或负的进程组 ID 。

二、命令说明

1、cmd 值的 F_DUPFD

F_DUPFD:返回一个如下描述的(文件)描述符:

  • 最小的大于或等于arg的一个可用的描述符。
  • 与原始操作符一样的某对象的引用。
  • 如果对象是文件(file)的话,则返回一个新的描述符,这个描述符与 arg 共享相同的偏移量(offset)。
  • 相同的访问模式(读,写或读 / 写)。
  • 相同的文件状态标志(如:两个文件描述符共享相同的状态标志)。
  • 与新的文件描述符结合在一起的 close-on-exec 标志被设置成交叉式访问 execve(2) 的系统调用。

实际上调用 dup(oldfd),等效于

fcntl(oldfd, F_DUPFD, 0);

而调用 dup2(oldfd, newfd),等效于

close(oldfd);
fcntl(oldfd, F_DUPFD, newfd);

2、cmd 值的 F_GETLK、F_SETLK 或 F_SETLKW

(1)F_GETLK

从内核获取文件锁的信息,将其保存到第三个参数,此时第三个参数为 struct flock *flockptr。我们这里是要设置文件锁,而不是获取已有文件锁的信息,我们这里用不到这个宏。

(2)F_SETLK

设置第三个参数所代表的文件锁,而且设置的是非阻塞文件锁,也就是如果加锁失败不会阻塞。也就是说加锁失败后如果不想阻塞的话,就是由 F_SETLK 宏来决定的。

此时需要用到第三个参数,struct flock *flockptr。

使用举例:

第一步:定义一个 struct flock flockptr 结构体变量(这个结构体变量就是文件锁)。

第二步:设置 flockptr 的成员,表示你想设置什么样的文件锁。

第三步:通过第三个参数,将设置好的 flockptr 的地址传递给 fcntl,设置你要的文件锁

(3)F_SETLKW

与 F_SETLK 一样,只不过设置的是阻塞文件锁,也就说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。

第三个参数

第三个参数设置为什么视情况而定,如果 fcntl 用于实现文件锁的话,第三个参数为 struct flock *flockptr,flockptr 代表的就是文件锁。对 flockptr 的成员设置为特定的值,就可以将文件锁设置为你想要的锁。

struct flock结构体如下:

struct flock
{
    short l_type;   // Type of lock: F_RDLCK,F_WRLCK, F_UNLCK 
    short l_whence; //How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END
    off_t l_start;   // Starting offset for lock 
    off_t l_len;    //Number of bytes to lock 
    pid_t l_pid;    //PID of process blocking our lock(F_GETLK only) 
}

成员说明

l_type:锁类型

  • F_RDLCK:读锁(或称共享锁)
  • F_WRLCK:写锁
  • F_UNLCK:解锁 

l_whence:加锁位置粗定位,设置同 lseek 的 whence

  • SEEK_SET:文件开始处
  • SEEK_CUR:文件当前位置处
  • SEEK_END:文件末尾位置处

l_whence 这个与 lseek 函数的 whence 是一个含义,off_t lseek(int fd, off_t offset, int whence);

l_start:精定位,相对 l_whence 的偏移,与 lseek 的 offset 的含义完全一致

通过 l_whence 和 l_start 的值,就可以用来指定从文件的什么位置开始加锁,不过一般来说,我们会将 l_whence 指定为SEEK_SET,l_start 指定为 0,表示从整个文件头上开始加锁。

l_len:从 l_whence 和 l_start 所指定的起始地点算起,对文件多长的内容加锁。

如果 l_len 被设置 0,表示一直加锁到文件的末尾,如果文件长度是变化的,将自动调整加锁的末尾位置。

将 l_whence 和 l_start 设置为 SEEK_SET 和 0,然后再将 l_len 设置为 0,就表示从文件头加锁到文件末尾,其实就是对整个文件加锁。

flockptr.l_whence=SEEK_SET;                                            
flockptr.l_start=0;                                            
flockptr.l_len=0;

就就表示对整个文件加锁。

如果只是对文件中间的某段加锁,这只是区域加锁,加区域锁时可以给文件 n 多个的独立区域加锁。

l_pid:当前正加着锁的那个进程的 PID 。

只有当我们获取一个已存在锁的信息时,才会使用这个成员,这个成员的值不是我们设置的,是由文件锁自己设置的,我们只是获取以查看当前那个进程正加着锁。对于我们目前设置文件锁来说,这个成员用不到。

栗子

使用文件锁的互斥操作,解决父子进程向同一文件写“hello ”,“world\n”时,hello hello world相连的问题。

【file_lock.h】

#ifndef H_FILELOCK_H
#define H_FILELOCK_H

#include 
#include 

//非阻塞设置写锁
#define SET_WRFLCK(fd, l_whence, l_offset, l_len) \
    set_filelock(fd, F_SETLK, F_WRLCK, l_whence, l_offset, l_len)
//阻塞设置写锁
#define SET_WRFLCK_W(fd, l_whence, l_offset, l_len) \
    set_filelock(fd, F_SETLKW, F_WRLCK, l_whence, l_offset, l_len)

//非阻塞设置读锁
#define SET_RDFLCK(fd, l_whence, l_offset, l_len) \
    set_filelock(fd, F_SETLK, F_RDLCK, l_whence, l_offset, l_len)
//阻塞设置读锁
#define SET_RDFLCK_W(fd, l_whence, l_offset, l_len) \
    set_filelock(fd, F_SETLKW, F_RDLCK, l_whence, l_offset, l_len)

//解锁
#define UNLCK(fd, l_whence, l_offset, l_len) \
    set_filelock(fd, F_SETLK, F_UNLCK, l_whence, l_offset, l_len)

/* 调用这个函数,即可实现阻塞加读锁/阻塞加写锁, 非阻塞加读锁/非阻塞加写锁/解锁 */
static void set_filelock(int fd, int ifwait, int l_type, int l_whence,
    int l_offset, int l_len)
{
    int ret = 0;
    struct flock flck;

    flck.l_type = l_type;
    flck.l_whence = l_whence;
    flck.l_start = l_offset;
    flck.l_len = l_len;

    ret = fcntl(fd, ifwait, &flck);
    if (ret == -1) {
        perror("fcntl fail");
        exit(-1);
    }
}

#endif

【main.c】 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "file_lock.h"

#define PARENT_CONTENT "parent process\n"
#define SUN_CONTENT "sun process\n"

void print_err(char *str, int line, int err_no)
{
    printf("%d, %s: %s\n", line, str, strerror(err_no));
    exit(-1);
}

int main(void)
{
    int fd = 0;
    int ret = 0;

    fd = open("./file", O_RDWR | O_CREAT | O_TRUNC, 0664);
    if (fd == -1)
        print_err("./file", __LINE__, errno);

    ret = fork();
    if (ret > 0) {
        while (1) {
            SET_WRFLCK_W(fd, SEEK_SET, 0, 0);
            write(fd, PARENT_CONTENT, strlen(PARENT_CONTENT));
            UNLCK(fd, SEEK_SET, 0, 0);
            sleep(1);
        }
    } else if (ret == 0) {
        while (1) {
            SET_WRFLCK_W(fd, SEEK_SET, 0, 0);
            write(fd, SUN_CONTENT, strlen(SUN_CONTENT));
            UNLCK(fd, SEEK_SET, 0, 0);
            sleep(1);
        }
    }

    return 0;
}
gcc -o main main.c

 结果:

linux / fcntl 函数详解_第1张图片

(SAW:Game Over!) 

你可能感兴趣的:(OS,/,Linux,gnu,服务器)