Linux&C语言文件学习笔记(四):文件I/O与系统API续

一、sync()和fsync()函数:

1、sync作用简述:

由于不同设备文件读写速度的差异(或者速度不匹配),而为解决过大速度差异带来的一系列问题,我们在不同速度的设备之间加入缓冲区(buffer),缓冲区的加入,使得这一种或者这一类问题得到了有效的解决。在UNIX/Linux内核中有这样一个系统调用:sync()(sync是同步之意,即缓冲区内容与磁盘数据同步),其作用就是将缓冲区中的内容刷新冲洗(flush)到指定对象中,以使得内容同步且腾出缓冲区以供后期继续使用而不至于丢失原有数据。因为缓冲区无法自行将数据写入目标文件中,必须使用sync()达到冲洗缓冲区的目的,如果不人为或人为设定系统去进行缓冲区的刷新冲洗操作,之前的数据将会丢失(被覆盖掉)。(通常,update 的系统守护进程会周期性(一般30秒为一周期)调用sync(),以定期刷新内核缓冲区。而系统命令sync也是调用sync()系统函数实现)。

对于系统级别文件操作函数来说,一般情况下带“f”前缀的函数,文件表示的参数为fd描述符,而不带前缀的函数,通常都需要采用字符串的形式作为参数对一个文件操作。sync()和fsync()函数稍微有点不一样:fsync()函数参数依旧是文件描述符,而sync()没有参数,有前面分析可知,这也是取决于其功能:sync()不针对某一个具体文件,只针对缓冲区的冲洗刷新,而fsync()则负责具体文件的同步实现

2、函数原型与解析:

#include<unistd.h>
int fsync(int fd);
void sync(void);
/*成功返回0,出错返回-1*/

sync()与fsync()的区别:
sync()只是将所有修改过的块缓冲区排入写队列,然后就返回,而并不等待实际写入磁盘的操作(从缓冲区到磁盘的刷新过程)完成。fsync()只对打开的文件起作用,即参数只能是打开返回的文件描述符,且fsync()等代写入磁盘操作结束后才会返回。

二、功能强大的fcntl()函数:

1、函数原型与参数简介:

#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd,int cmd,...);

参数简析:

第一个参数为打开的文件对应的文件描述符;
第三个参数为int arg,或者struct flock * flockptr;
第二个参数决定fcntl选择的功能:
①复制描述符:F_DUPFD、F_DUPFD_CLOEXEC;
②获取/设置文件描述符:F_GETFD、F_SETFD;
③获取/设置文件状态标识:F_GETFL、 F_SETFL;
④获取/设置异步I/O所有权:F_GETOWN、F_SETOWN;
⑤获取/设置记录锁:F_GETLK、F_SETLK、F_SETLKW;
我们主要对文件加锁进行总结分析,故文件加锁的参数在单独分支中进行说明,其它cmd简要说明如下(可以直接跳过,毕竟没有举例也晦涩难懂):

F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述符,并且复制参数fd的文件描述符。执行成功则返回新复制的文件描述符。其作用于dup2()函数类似。新描述符与fd共享一套文件表项,但是新的文件描述符有自己的一套文件描述符标志。其FD_CLOSEEXEC 文件描述符标志被清除,表示该描述负载exec时仍有效;
F_DUPFD_CLOEXEC复制文件描述符,设置于新描述符关联的FD_CLOSEEXEC文件描述符标志的值,返回新文件描述符。

F_GETFD取得close-on-exec标志。若此标志的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
F_SETFD 设置close-on-exec 标志。该标志以参数arg 的FD_CLOEXEC位决定。

F_GETFL 取得文件描述符状态标志,此标志为open()的参数flags。
F_SETFL 设置文件描述符状态标志,参数arg为新标志,但只允许O_APPENDO_NONBLOCKO_ASYNC 等位的改变,其他位(O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH、O_TRUNC等)将不受影响。

F_GETOWN获取当前接收SIGIO和SIGURG信号(两种异步I/O信号)的进程ID或进程组ID。
F_SETOWN设置接收SIGIO和SIGURG信号的进程ID或进程组ID,正的arg表示进程ID,负的arg表示绝对值等于arg的进程组ID。

返回值:

若成功,根据cmd的不同而不同(有四个cmd有其对应的返回值)
F_DUPFD返回新文件描述符;
F_GETFD和F_GETFL返回相应的标志;
F_GETOWN返回一个正的进程id或负的进程组id。
若出错返回-1

2、获取/设置记录锁:

(1)文件锁(读写锁)简述:

读锁与写锁:
读锁(锁定其它进程的“上写锁”操作):共享锁,对其他进程上读锁(为读文件操作上锁)不加限制,只针对上写锁;写锁(锁定其它进程的上读锁及写锁操作):独占锁,无论其它进程是上读锁还是上写锁都不被允许。

文件锁对应一个结构体:

struct flock{
    short l_type;/*锁的类型*/
    short l_whence;/*上锁的参照位置*/
    int l_start;/*锁的开始位置相对于参照位置的偏移量*/
    int l_len;/*锁的长度,字节为单位*/
    pid_t l_pid;/*加锁的Id*/
}

结构体成员:

①l_type:F_RDLCK读锁、F_WRLCK写锁、F_UNLCK释放锁
②l_whence与l_start联合决定锁的起始位置:l_whence与lseek函数的whence参数可选项一样。SEEK_SET、SEEK_SUR、SEEK_END
③l_pid只在F_GETLK(获取锁)时才有用(只要是struct flock 变量初始化均设置为-1即可)。
eg:

/*
功能:对于fd所指向的文件第50~100个字节加写锁。不允许在其加锁器件其他文件再加读写锁。
*/
struct flock wflock;/*定义写锁*/
/*写锁设置*/
rflock.l_type = F_WRLCK;/*锁类型为写锁*/
rflock.l_whence = SEEK_SET;
rflock.l_start = 50;/*以文件开头(SEEK_SET)为参考,向后偏移50个字节*/
rflock.l_len = 50;/*锁定50个字节*/
rflock.l_pid = -1;
fcntl(fd,F_SETLK,&rflock);/*加锁*/

一个进程对文件上锁,对其他进程上锁的影响
当一个进程对文件上了读锁,其它进程可以上读锁但不可以上写锁。
当一个进程对文件上了写锁,其它进程对该文件读锁写锁都不能上。

注意:文件锁不能锁定硬盘上的文件,也不能锁定read/write函数,只能阻止其它进程的上锁行为(也就是说,文件即使通过程序调用fcntl函数上锁了,也仍然可以通过vim对其进行修改并保存(vim相当于其他进程),因为vim的读写操作并不是上读写锁操作,所以可以成功)。文件锁的正确用法:在调用函数read()之前应该上读锁,在调用write()函数之前上写锁。锁的机制是人指定的,所以正确遵循该机制来使用锁才能发挥锁的作用。

(2)、参数测试与总结

测试代码:

/*fcntl.c*/
/**
*2017年1月22日21:20:32
*功能:测试F_SETLK与F_SETLKW参数
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

void error_sc(const char * ptr_fun)/*error System Call */
{
    perror(ptr_fun);
    exit(EXIT_FAILURE);
}
int main(void)
{
    int fd = open("fcntl.txt",O_RDWR|O_TRUNC);/*fcntl.txt已经存在*/
    if(fd == -1)    error_sc("open fcntl.txt");

    char * buf = "hello,i'am function of fcntl1.c!\n";

    struct flock wflock = {F_WRLCK,SEEK_SET,0,30,-1};/*初始化写锁内容*/
    int ret_fcntl  = fcntl(fd, F_SETLK, &wflock);/*上锁*/
    if(ret_fcntl == -1)
        error_sc("fcntl");
    else{/*注意:else是上锁成功才进行写操作,是遵循锁的机制来使用的*/
        printf("Lock of write is success!\n");
        int ret_write = write(fd,buf,strlen(buf));
        if(ret_write == -1) error_sc("write");
    }
    sleep(30);/*模拟对文件的其它操作*/
    printf("fcntl flock is over!\n");
    wflock.l_type = F_UNLCK;/*修改写锁为解除锁定*/
    fcntl(fd,F_SETLCK,&wflock);/*文件操作完毕,重新设置为解除锁定*/
    sleep(20);
    printf("process of fcntl1 is over!\n");

    return 0;
}
/*fcntl2.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

void error_sc(const char * ptr_fun)/*error System Call */
{
    perror(ptr_fun);
    exit(EXIT_FAILURE);
}
int main(void)
{
    int fd = open("fcntl.txt",O_RDWR|O_TRUNC);
    if(fd == -1)    error_sc("open fcntl.txt");

    char * buf = "HELLO,I'AM FUNTION OF FCNTL2!\n";

    struct flock wflock = {F_WRLCK,SEEK_SET,0,30,-1};
    int ret_fcntl  = fcntl(fd, F_SETLK, &wflock);
    if(ret_fcntl == -1)
        error_sc("fcntl");
    else{
        printf("Lock of write is success!\n");
        int ret_write = write(fd,buf,strlen(buf));
        if(ret_write == -1) error_sc("write");
    }
    wflock.l_type = F_UNLCK;/*修改写锁为解除锁定*/
    fcntl(fd,F_SETLCK,&wflock);/*文件操作完毕,重新设置为解除锁定*/

    return 0;
}

先运行fcntl1以后,再运行fcntl2发现fcntl2加锁出错,结果如下(resource pemporarily unavailable,资源暂时不可用,表示加锁失败,因为我们在fcntl1.c中加的是写锁):
这里写图片描述
cat fcntl.txt结果如下(是fcntl1.c写的内容):
这里写图片描述

我们将fcntl1.c与fcntl2.c中的struct flock wflock = {F_WRLCK,SEEK_SET,0,30,-1};均改为struct flock rflock = {F_RDLCK,SEEK_SET,0,30,-1}。即将写锁改为读锁就不会出现资源暂时不可用的加锁失败提示:
这里写图片描述

但是我们只有在写之前加写锁,在读之前加读锁。不会采用写操作而加读锁的方式,这里只是为了测试之用(cat fcntl.txt虽然改变不能作为参考,因为我们不会再实战中这样改)。这么说,如果某文件已被进程1加写锁,而进程2加锁失败时我们需要使进程2进入阻塞状态而不是直接退出。测试方法是依旧采用struct flock wflock = {F_WRLCK,SEEK_SET,0,30,-1};而将fcntl2的int ret_fcntl = fcntl(fd, F_SETLK, &wflock);改为int ret_fcntl = fcntl(fd, F_SETLKW, &wflock);使进程fcntl2在“资源暂时不可用”时进入阻塞状态,测试结果如下:

这里写图片描述

fcntl2加锁是在fcntl1解除锁之后,而不是其进程结束以后。

cat fcntl.txt(其内容是fcntl2.c写的内容):
这里写图片描述

总结:
F_SETLKW是F_SETLK的阻塞版本,W代表Wait,以方式F_SETLK使用文件锁时,其他进程默认是:加锁失败时会返回错误,进程结束;但是如果使用F_SETLKW参数(其它均不用改)则可以在加锁失败时进入休眠状态,继续等待(不报错),直到其他进程释放锁该进程可以加上锁,或者休眠进程被信号中断为止,则该进程被唤醒。

另外,当fcntl的cmd参数为F_GETLK时不是简单的获取锁,而是测试某个锁对某个文件能否加上锁(测试的加锁操作是否会被排斥),而不去真正加锁。

如果可以加锁,调用F_GETLK的结果是:struct flock其他数据不变但锁的类型l_type会变为F_UNLCK(表示现在可以去加测试的类型锁)。
如果不可以加锁,调用F_GETLK的结果是:如果当前存在其他进程对文件的锁,不允许改进曾加测试类型锁,struct flock所有数据都会改变,改变为当前正在起作用的锁的相关信息。l_pid变为当前正在上锁的进程id(不是用来测试的进程ID)。测试结果如下:

这里写图片描述

fcntl1.c只需要将原代码中锁的类型改为F_RDLCK,并加上getpid()输出即可

...
printf("pid=%d",getpid());
...

getlck代码:

/**
*201712222:10:50
*功能:测试F_GETLK参数
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>

int main(void)
{
    int fd = open("fcntl.txt",O_RDWR);
    if( fd == -1){
        perror("open");
        exit(EXIT_FAILURE);
    }
    struct flock lock={F_RDLCK,SEEK_SET,0,20,-1};

    printf("type = %d\n",lock.l_type);
    printf("F_WRLCK:%d\nF_RDLCK:%d\nF_UNLCK:%d\n",F_WRLCK,F_RDLCK,F_UNLCK);
    /*对读锁测试*/
    fcntl(fd,F_GETLK,&lock);
printf("F_RDLCK:type=%d,pid=%d\n",lock.l_type,lock.l_pid);
    /*对写锁测试*/
    lock.l_type = F_WRLCK;
    fcntl(fd,F_GETLK,&lock);
printf("F_WRLCK:type=%d,pid=%d\n",lock.l_type,lock.l_pid);


    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值