第3章 文件I/O(6)_高级文件操作:文件锁

7. 高级文件操作:文件锁

(1)文件锁分类

分类依据

类型

说明

按功能分

共享读锁

文件描述符必须读打开

一个进程上了读锁,共它进程也可以上读锁进行读取

独占写锁

文件描述符必须写打开

一个进程上了写锁,其它进程就不能上写锁和读锁进行读写操作

按类型分

建议锁

要求上锁文件的进程都要检测是否有锁的存在,并尊重巳有的锁。这也是Linux默认的锁类型。

强制锁

由内核和系统执行的锁。

(2)fcntl函数

 头文件

#include<unistd.h>

函数

int fcntl(int fd, int cmd, struct flock* lock);

返回值

若成功返回新文件描述符,出错返回-1

功能

若成功则依赖于cmd,出错为-1。

参数

cmd:F_SETLK、F_GETLK和F_SETLKW(阻塞版的fcntl函数)

struct flock{
    short  l_type;   //F_RDLCK,F_WRLCK,or F_UNLCK
    off_t  l_start;  //相对于l_whence的偏移量
    short  l_whence; //SEEK_SET,SEEK_CUR,or SEEK_END
    off_t  l_len;    //长度(单位字节),0表示锁到文件尾
    pid_t  l_pid;    //使用F_GETLK时有效,返回持有锁的进程
}

备注

①l_type:表示锁的类型,F_RDLCK为共享读锁,F_WRLCK为独占写锁,F_UNLCK解锁。

②l_whence、l_start:要加锁或解锁的区域起始地址,则这两者共同决定。注意,区域的起始地址可以在文件尾端或越过尾端开始,但不能从文件起始位置之前开始。

③l_len:表示区域的长度。如果为0,则表示锁的区域从其起点(由l_start和l_whence共同决定)开始,直到最大可能位置为止。也就是不管整个文件为了锁整个文件,通常的作法是l_whence为SEEK_SET,l_start和l_len都设为0

fcntl函数可以作用于建议锁也就可以作用于强制锁。

(3)建议锁和强制锁

  ①建议性锁:要求每个使用文件的进程都要主动检查该文件是否有锁存在(可以通过fcntl函数通过F_GETLK来检查)。如果有,则合作的进程必须主动加锁,以防止对文件的破坏。如果一个进程加锁,而其它的进程不加锁则这个锁无法约束不加锁进程对文件的进行读写操作的行为。因为锁只是建议性存在,并不强制执行为了让这个锁起作用,要求其它进程要尊重这个锁的存在,主动去加锁Linux默认是采用建议性锁,它们依靠程序员遵守这个约定。(要么合作的进程都加锁,要么都不加锁,否则锁的作用无法发挥出来!)

  ②强制性锁:当文件被上锁来进行读写操作时,在锁定该文件的进程释放该锁之前,内核会强制阻止任何对该文件的读或写违规访问,每次读或写访问都得检查锁是否存在。也就是强制性锁机制,让锁变得名副其实,真正达到了锁的效果。而不是像建议性锁机制,当其它进程不加锁时,它只是个一只纸老虎!

(4)开启强制性锁

  ①重新挂载文件系统所在的分区:mount -o remount,mand /dev/sda5 /

  ②设置文件的组ID位,关闭组执行位:chmod g+s,g-x file.txt

【编程实验】多进程同时写同一文件

//io.h

#ifndef __IO_H__
#define __IO_H__
#include <sys/types.h>

extern void copy(int fdin, int fdout);//文件复制
extern void set_fl(int fd, int flag); //设置文件状态标志
extern void clr_fl(int fd, int flag); //取消文件状态标志

//文件锁
extern int lock_reg(int fd, int cmd, short type, 
                    off_t offset, short whence, off_t length);
#define READ_LOCKW(fd, offset, whence, length)   \
        lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, length)
#define READ_LOCK(fd, offset, whence, length)    \
        lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, length)

#define WRITE_LOCKW(fd, offset, whence, length)  \
        lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, length)
#define WRITE_LOCK(fd, offset, whence, length)   \
        lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, length)

#define UNLOCKW(fd, offset, whence, length)       \
        lock_reg(fd, F_SETLKW, F_UNLCK, offset, whence, length)
#define UNLOCK(fd, offset, whence, length)       \
        lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, length)
#endif

//io.c

#include "io.h"
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

//编译命令:gcc -o obj/io.o -Iinclude -c src/io.c

#define BUFFER_LEN 1024 //与分区文件块大小一致。可以通过
                        //tune2fs -l /dev/sda1命令查看


void copy(int fdin, int fdout)
{
    char buffer[BUFFER_LEN];

    ssize_t size;

    //保证从文件开始处复制
    lseek(fdin, 0L, SEEK_SET); 
    lseek(fdout, 0L, SEEK_SET); 

    while((size = read(fdin, buffer, BUFFER_LEN)) > 0){
        if(write(fdout, buffer, size) != size)
        {
           fprintf(stderr, "write error: %s \n", strerror(errno));
           exit(1);
        }
    }

    if (size < 0 )
    {
        fprintf(stderr, "read error: %s\n",strerror(errno));
        exit(1);  //return 1;
    }   
}

void set_fl(int fd, int flag) //设置文件状态标志
{
    //获取原来的文件状态标志
    int val = fcntl(fd, F_GETFL);

    //增加新的文件状态标志
    val |= flag;

    //重新设置文件状态标志
    if(fcntl(fd, F_SETFL, val) < 0)
    {
        perror("fcntl error");
    }
}

void clr_fl(int fd, int flag) //取消文件状态标志
{

    //获取原来的文件状态标志
    int val = fcntl(fd, F_GETFL, val);

    //清除指定的文件状态标志(置0)
    val &= ~flag;

    //重新设置文件状态标志
    if(fcntl(fd, F_SETFL, val) < 0 )
    {
        perror("fcntl error");
    }
}

//文件锁
int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length)
{
    struct flock flock;

    flock.l_type = type;
    flock.l_start = offset;
    flock.l_whence = whence;
    flock.l_len = length;
    //flock.l_pid 加锁的进程号,会由操作系统自己填入,当F_GETLK时可以获取到

    if(fcntl(fd, cmd, &flock) < 0){
        perror("fcntl error");
        return 0;
    }

    return 1;
}

//lock_write.c

#include "io.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

//演示多进程对加独占写锁的文件同时写入的问题。

int main(int argc, char* argv[])
{
    if(argc < 4){
        printf("usage: %s content file locktype\n", argv[0]);
        exit(1);
    }

    ssize_t size = strlen(argv[1]) * sizeof(char);
    int fd = open(argv[2], O_WRONLY | O_CREAT, 0777);
    if(fd < 0){
        perror("open error");
        exit(1);
    }

    //查看文件被哪个进程锁住:
    struct flock flock;
    flock.l_start=0;
    flock.l_type=F_RDLCK;
    flock.l_len=0;
    flock.l_whence=SEEK_SET;
    if(fcntl(fd, F_GETLK, &flock) < 0 )
    {
        perror("fcntl error");
        exit(1);
    }

    printf("current pid: %d,is locked by another process[pid:%d]\n",getpid(),flock.l_pid);

    //sleep(5);

    //加文件锁(注意Linux默认是建议锁,为了防止同多进程同时写文件(O_WRONLY),要求其它
    //合作的进程也要同时加O_WRONLY或O_RDONLY。如果其它进程如果不加锁,也是可以直接读
    //或写该文件的。因为它是建议锁,为体现锁的功能,其他进程必须遵守加锁的约定,即
    //在遵守加锁的约定后,第二个进程想要对文件加锁必须要等到第一个进程释放文件锁后,
    //才可以获取锁并进行加锁操作。
    if(!strcmp("lock", argv[3])){
        WRITE_LOCKW(fd, 0, SEEK_SET, 0); //独占写锁
        printf("lock success\n");
    }

    //写入content的内容,为了演示交替写入,将字符一个个写入
    char* p = argv[1];
    int i = 0;
    for(i=0; i<size; i++){
        if(write(fd, p+i, 1) != 1){
            perror("write error");
            exit(1);
        }

        printf("%d success write one character[-%c-]!\n",
                                       getpid(), *(p+i));

        sleep(1);
    }

    //释放锁
    if(!strcmp("lock", argv[3])){
        UNLOCKW(fd, 0, SEEK_SET,0);
        printf("unlock success\n");
        printf("unlock pid: %d\n", getpid());
    }

    close(fd);

    return 0;
}

//start.sh

#因为是建议锁,所以两个进程必须同时加锁,锁的功能才能起作用
bin/lock_write aaaaaa lock.txt lock &  #加共享读写锁
bin/lock_write AAAAAA lock.txt lock &  #加共享读写锁

#如果进程1加锁,进程2不加锁。则锁的作用将不起作用,这就是
#建议性锁的问题,即要求如果锁要起作用,则大家都要加锁。
#bin/lock_write aaaaaa lock.txt lock &    #加共享读写锁
#bin/lock_write AAAAAA lock.txt nolock &  #注意,这里不加锁,上一个进程的锁将无法约束这里写的行为。

原文链接: https://www.cnblogs.com/5iedu/p/6344626.html

欢迎关注

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

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

    第3章 文件I/O(6)_高级文件操作:文件锁

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

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

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

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

(0)
上一篇 2023年4月3日 下午3:18
下一篇 2023年4月3日 下午3:18

相关推荐