栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

8.文件锁、文件锁的内核结构、访问测试、权限掩码、修改文件大小

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

8.文件锁、文件锁的内核结构、访问测试、权限掩码、修改文件大小

write写二进制比较麻烦,因为写的是字节流,不涉及二进制的处理

而fwrite(,"wb");就可以写入二进制

多个进程在同时写入一个文件,会出现写冲突,对不对?

不对,得是同一个文件得同一个区域

mode_t:权限类型 size_t:计数 off_t:偏移 ssize_t:

文件锁

读写冲突

1.如果两个或以上的进程同时向同一个文件的某个特定区域写入数据,那么最后写入文件的数据极有可能因为写操作的交错而产生混乱

2.如果一个进程写而其它进程同时在读一个文件的某个特定区域,那么读出的数据极有可能因为读写操作的交错而不完整

3.多个进程同时读一个文件的某个特定区域,不会有任何问题,它们只是各自文件中的数据拷贝到各自的缓冲区中,并不会改变文件的内容,相互之间也就不会冲突

4.由此可以得出结论,为了避免在读写同一个区域时发生冲突,进程之间应该遵循以下规则,如果一个进程正在写,那么其它进程既不能写也不能读,如果一个进程正在读,那么其它进程不能写但是可以读’

//写冲突演示
#include
#include
#include
#include

int main(int argc,char* argv[]){
    if(argc < 2){
        fprintf(stderr,"用法:./a.out <字符串>n");
        return -1;
    }
    
    //打开文件
    int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //写入数据
    // ./a.out  hello 
    // argv[1]记录着hello,argv[1]记录 ./a.out
    for(int i = 0;i < strlen(argv[1]);i++){
        if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
            perror("write");
            return -1;
        }
        sleep(1);
    }
    //关闭文件
    close(fd);
    return 0;
}

 

文件锁

1.为了避免多个进程在读写同一个文件的同一个区域时发生冲突,Unix/Linux系统引入了文件锁机制,并把文件锁分为读锁和写锁两种,它们的区别在于,对一个文件的特定区域可以加多把读锁,对一个文件的特定区域只能加一把锁

2.基于锁的操作模型是:读/写文件中的特定区域之前,先加上读/写锁,锁成功了再读/写,读/写完成以后再解锁

 

3.假设进程A期望访问某文件的A区,同时进程B期望访问该文件的B区,而A区和B区存在部分重叠,分情况讨论

第一种情况:进程A正在写,进程B也想写

第二种情况:进程A正在写,进程B却想读

第三种情况:进程A正在读,进程B却想写

第四种情况:进程A正在读,进程B也想读

#include

int fcntl(int fd,F_SETLK/F_SETLKW,struct flock* lock);

        功能:加解锁

        参数:F_SETLK 非阻塞模式加锁,,F_SETLKW 阻塞模式加锁

                    lock 文件要加的锁

        返回值:成功返回0,失败返回-1

        非阻塞模式加锁,函数返回,然后我们可以执行其它功能,过一会再调

        阻塞模式加锁,即一直停滞等到解锁再加锁

struct flock{

        short l_type; //锁类型:F_RDLCK/F_WRLCK/F_UNLCK

        short l_whence; //锁区偏移起点:SEEK_SET/SEEK_CUR/SEEK_END

        off_t l_start; //锁区偏移字节数

        off_t l_len; //锁区字节数

        pid_t l_pid; //加锁进程的PID,-1表示自动设置

};

通过对该结构体类型变量的赋值,再配合fcntl函数,以完成对文件指定区域的加解锁操作。

几点说明:

1.当通过close函数关闭文件描述符时,调用进程在该文件描述符上所加的一切锁将被自动解除

2.当进程终止时,该进程在所有文件描述符上所加的一切锁将被自动解除

3.文件锁仅在不同进程之间起作用,同一个进程的不同线程不能通过文件锁解决读写冲突问题。

4.通过fork/vfork函数创建的子进程,不继承父进程所加的任何文件锁

5.通过exec函数创建的新进程,会继承原进程所加的全部文件锁,除非某文件描述符带有FD_CLOEXEC标志。

从前述基于锁的操作系统可以看出,锁机制之所以能够避免读写冲突,关键在于参与读写的多个进程都在按照一套模式 ----先加锁,再读写,最后解锁 ------按部就班地执行。这就形成了一套协议,只要参与者无一例外地遵循这套协议,读写就是安全的。反之,如果哪个进程不遵守这套协议,完全无视锁地存在,想读就读,想写就写,即便有锁,对它也起不到任何约束作用。因此,这样地锁机制被称为劝谏锁或协议锁

//文件锁演示
#include
#include
#include
#include
#include

//加锁函数
int wlock(int fd,int wait){
    struct flock lock;
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;//0  一直加到文件尾
    lock.l_pid = -1;
    
    return fcntl(fd,wait ? F_SETLKW : F_SETLK,&lock);
}

//解锁
int unlock(int fd){
    struct flock lock;
    lock.l_type = F_UNLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;//0  一直加到文件尾
    lock.l_pid = -1;

    return fcntl(fd,F_SETLK,&lock);
}

int main(int argc,char* argv[]){
    if(argc < 2){
        fprintf(stderr,"用法:./a.out <字符串>n");
        return -1;
    }
    
    //打开文件
    int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //阻塞加写锁

    

    //非阻塞加锁
    while(wlock(fd,0) == -1){
        if(errno == EACCES || errno == EAGAIN){//出错是因为文件被锁定,加锁失败
            printf("文件被锁定,干点别的去...n");
            sleep(1);
        }else{//其他原因造成的失败,正常错误处理
            perror("wlock");
            return -1;
        }
    }

    //写入数据
    // ./a.out  hello 
    // argv[1]  
    for(int i = 0;i < strlen(argv[1]);i++){
        if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
            perror("write");
            return -1;
        }
        sleep(1);
    }

    //解锁
    if(unlock(fd) == -1){
        perror("unlock");
        return -1;
    }

    //关闭文件
    close(fd);
    return 0;
}

文件阻塞加写锁:

文件非阻塞写锁:

当判断while(wlock(fd,0) == -1)时,wlock其实有很多可能失败返回-1,1.加锁失败 2.环境出问题。 3参数传的不对。 都会返回 -1,那么如何判断是加锁失败导致 -1呢?

通过man手册看一下, man 2 fcntl

意思是,如果锁因为其它进程有锁而没加上的话,错误码会设置成EACCES或者EAGAIN,这也是要在头文件加一个#include 的原因

文件锁地内核结构

1.每次对给定文件地特定区域加锁,都会通过fcntl函数向系统内核传递flock结构体,该结构体包含了有关锁的一切细节,诸如锁地类型(读锁/写锁),锁区地起始位置和大小,甚至加锁进程地PID(填-1由系统自动设置),即构成锁节点

2.系统内核会收集所有进程对该文件所加地各种锁,并把这些flock结构体中地信息,以链表地形式组织成一张锁表,而锁表地起始地址就保存在该文件的v节点中

3.任何一个进程通过fcntl函数对该文件加锁,系统内核都要遍历这张锁表,一旦发现有与欲加之锁构成冲突的锁即阻塞或报错,否则即将欲加之锁插入锁表,即加一个节点,而解锁的过程实际上就是调整或删除锁表中的相应节点

 

访问测试

#include

int access(char const* pathname,int mode);

        功能:判断当前进程是否可以对某个给定的文件执行某种访问

        参数:pathname 文件路径

                mode 被测试权限,可以取以下值

                R_OK -可读否

                W_OK -可写否

                X_OK -可执行否

                F_OK -存在否

        返回值:成功返回0,失败返回-1

//访问测试
#include
#include

int main(int argc,char* argv[]){
    // ./a.out write.c
    // write.c 不存在
    // write.c 存在,则判断可读,可写,不可执行
    if(argc < 2){
        fprintf(stderr,"用法:./a.out <文件名>n");
        return -1;
    }
    
    printf("文件:%s ",argv[1]);
    if(access(argv[1],F_OK) == -1){//不存在
        printf("不存在n");
    }else{//存在
        if(access(argv[1],R_OK) == -1){
            printf("不可读,");
        }else{
            printf("可读,");
        }

        if(access(argv[1],W_OK) == -1){
            printf("不可写,");
        }else{
            printf("可写,");
        }

        if(access(argv[1],X_OK) == -1){
            printf("不可执行n");
        }else{
            printf("可执行n");
        }
    }
    return 0;
}

 

  权限掩码

#include

mode_t umask(mode_t cmask);

>功能︰设置调用进程的权限掩码>参数:cmask 新权限掩码

>返回值:永远成功,返回原来的权限掩码。

umask函数所影响的仅仅是调用进程自身的权限掩码,对其父进程和兄弟进程没有任何影响,但是对子进程是有影响的。

umsk 0002是属于bash的,所以a.out默认0002,通过函数修改a.out,那么也只是a.out的权限掩码被设置成0333,

当然也可以命令行直接输入umask可以看权限掩码

//权限掩码

//权限掩码
#include
#include
#include
#include

int main(void){
    // old ——》以前的掩码  0002
    mode_t old = umask(0333);

    int fd = open("./umask.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
    if(fd == -1){
        perror("open");
        return -1;
    }

    close(fd);

    //恢复以前的掩码
    umask(old);
    return 0;
}

修改文件大小

#include

int truncate(char const* path,off_t length);

int ftruncate(int fd,off_t length);

        功能:修改指定文件大小

        参数:path 文件路径

                    length 文件大小

                    fd 文件描述符

        返回值:成功返回0,失败返回-1

该函数既可以把文件截短,也可以把文件加长,所有的改变均发生在文件的尾部,新增的部分用数字0填充

 

//修改文件大小
#include
#include
#include
#include

int main(void){
    //打开文件
    int fd = open("./trunc.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //写入ABCDE  --》 5字节
    char* buf = "ABCDE";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //改小
    if(ftruncate(fd,3) == -1){
        perror("ftruncate");
        return -1;
    }

    //改大
    if(truncate("./trunc.txt",5) == -1){
        perror("truncate");
        return -1;
    }

    close(fd);
    return 0;
}

改小: 

 

改大

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/947386.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号