APUE_文件管理

Unix文件系统

我们可以把一个磁盘分成一个或多个分区,每个分区可以包涵一个文件系统。
这里写图片描述
i-node是长度固定的记录项,它包含有文件的大部分信息:

  • 文件类型
  • 文件访问权限位
  • 文件长度
  • 指向文件数据块的指针

这里写图片描述

在上图中,每个i节点都有一个链接计数,其值是指向该节点的目录项数,只有当链接计数减少到0时,才可以删除该文件(释放数据块)

当为一个文件重命名时,该文件的实际内容并不会改变,只需要构造一个指向现有i节点的心目录项。

打开文件时内核的数据结构

这里写图片描述

文件描述符:对于内核我恶言,所有打开的文件都通过文件描述符引用,Unix系统shell把文件描述符0与进程的标准输入相关联
描述符1是标准输出,描述符2是标准错误,文件描述符的范围时0~OPEN_MAX-1.

文件类型

1)普通文件
这种文件类型是最常见的,无论是文本文件还是二进制文件,对于Unix内核而言都是没有区别的,对于普通文件的解释由处理该文件的应用程序完成
2)目录文件
这种文件包含了其他文件的名字以及指向这些文件的i节点指针
3)块特殊文件
提供了对设备带缓冲的访问,每次访问以固定的长度单位进行。
访问函数的信息

#include <sys/stat.h>
int stat(const char *restrict pathname, struct
 stat *restrict buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *restrict pathname, struct
 stat *restrict buf);

一旦给出了pathname,stat函数将返回与此命名文件相关的信息
第一个参数:文件的路径和文件名/文件描述符
第二个参数:结构体指针,定义结构体变量取地址
函数功能:主要用于获取的详细信息

    struct stat
    {
        ...
        mode_t st_mode;     /*文件的类型和权限*/ 
        off_t st_size;      /*文件的大小,单位字节,(重点)*/ 
        time_t st_mtime;        /*文*/ 
        ...

    }

普通文件管理基本函数

1)open函数

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

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

函数功能:主要用于打开/创建一个文件/设备

以第二个open函数为例,解析如下:
第一个参数:字符串形式的文件路径和文件名
第二个 参数:做标志的意思,必须包括以下访问模式中的一种
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
除此之外还可一按位或以下标志
O_APPEND 以追加的方式打开文件,写操作在文件的尾部进行处理
O_CREAT 文件存在则打开,文件不存在则创建
O_EXCL 与O_CREAT 搭配使用如果文件存在则创建失败。
O_TRUNC 文件存在并且是规则文件,拥有写权限,则将文件大小截取到0(清空文件)
第三个参数:当创建新文件时,需指定文件的权限,如0644
返回值:返回一个较小的非负整数,作为文件描述符
失败返回-1

注意 :
creat() is equivalent to open() with flags equal to O_CREAT|O_WRONLY|O_TRUNC.

2)close函数

#include <unistd.h>

       int close(int fd);

函数功能:
主要用于关闭参数指定的文件描述符,使得该文件描述符不再关联任何文件,该描述符可以被再次利用。
Tips:关闭一个文件的时候还会释放锁夹在该文件上的所有记录锁,当一个进程终止时,内核自动关闭该进程所有打开的文件。

3)read函数

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

函数功能:主要用于从指定的文件中读取指定的数据大小

Para1:文件描述符,open函数的返回值(从哪里读)
Para2:缓冲区的首地址(读到哪里去)
Para3:数据的大小
返回值:成功返回实际读到的数据大小,失败返回-1,当返回0时,表示读取到文件尾

4)write函数

       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

函数功能:主要用于从指定的文件中写入指定的数据大小.
Para1:文件描述符,open函数的返回值(写入到哪里去)
Para2:缓冲区的首地址(数据从哪里来)
Para3:数据的大小
返回值:成功返回实际读到的数据大小,失败返回-1,当返回0时,表示没有任何文件写入。

Tips:该函数出错的常见原因是

  • 磁盘已经写满
  • 超过了进程给定的文件长度限制。
  • -

creat函数

#include <fcntl.h>
int creat(const char *pathname, mode_t mode)

函数功能:
创建一个新文件

该函数等效于

open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

lseek函数
每个打开的文件带哦用有一个与其相关联的”当前文件偏移量”,他通常是一个非负整数,从文件开始处计算字节数,当一个文件打开时,除非指定O_APPEND选项,否则该偏移量置为0.

       #include <sys/types.h>
       #include <unistd.h>

       off_t lseek(int fd, off_t offset, int whence);

函数功能:主要用于调整指定文件中的读写位置。
Para1:文件描述符
Para2:文件中的偏移量

0 表示向文件的尾部进行偏移
=0 表示文件读写位置不变
<0 表示向文件的开头位置进行偏移

Para3:偏移的起始位置(从什么地方开始偏移)
SEEK_SET 文件的开头位置
SEEK_CUR 当前位置
SEEK_END 文件结尾位置 文件中最后一个字节的下一个位置(文件的最后一个字节是一个换行符)
返回值:成功返回距离文件头的偏移量,失败返回-1
Tips:文件的偏移量可以大于文件的当前长度,这种情况下,对文件的下一次读写将加长文件,并在文件中形成一个空洞(对于文件中没有写过的字节都被读为0),文件的空洞并不占用磁盘的存储区。

dup/dup2函数

      #include <unistd.h>

       int dup(int oldfd);

函数功能:
主要根据参数指定的旧文件描述符复制一个新的文件描述符,新文件描述符选择最小的未被使用的,成功返回新的文件描述符失败返回-1.
注意:
文件描述符的复制本质上就是让未见描述符对应同一个文件表,从而通过多个文件描述符都可以操作文件。
这里写图片描述

    int dup2(int oldfd, int newfd);

函数功能
主要用于对第一个参数进行oldfd进行复制,新的文件描述符通过第二个参数newfd来指定,如果newfd被占用,则先考虑关闭再使用,成功则返回新的文件描述符,失败返回-1

fcntl函数

    #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

Para1:文件描述符,open函数的返回值(决定对哪个文件操作)
Para2:具体的命令(决定执行一个什么样的操作)
F_DUPFD 主要用于复制文件描述符,新的文件描述符大于等于第三个参数arg的值即可,也就是不会强制关闭已经被占用的文件描述符,相对来说,比dup2安全.
F_GETFD/F_SETFD 主要用于获取/设置文件描述符的标志
F_GETFL/F_SETFL 主要用于获取/设置文件的状态标志
FSETLK/F_GETLKW/F_GETLK 主要用于操作建议锁
Para3:可变长参数(是否需要取决于第二个参数cmd)

当使用文件锁功能,是指向以下结构体的指针

               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;     /* 进程ID 一般给-1即可
                               (F_GETLK only) */
           ...
       };

返回值:
F_DUPFD 成功返回文件描述符,失败返回-1
F_GETFD/F_GETFL 成功返回标志值 失败返回-1
其他命令成功返回0.失败返回-1.
函数功能:
1)复制文件描述符(了解)
2) 操作文件描述符标志(了解)
3) 操作文件状态标志(了解)
4) 操作建议锁/文件锁的功能成功

文件锁

使用fcntl函数实现文件锁的功能

1)文件锁的由来
当有多个进程同时读写同一个文件时,可能引发文件中数据内容的混乱,如果所有进程都是读文件,则可以同时进程,但是只要有一个进程执行写操作,则应该一个一个串行
而不是并行,此时采用文件锁/建议锁可以实现此功能。
文件锁本质上就是读写锁,也就是一把读锁和一把写锁,其中读锁是以把共享锁,允许其他进程加读锁,不允许其他进程加写锁,而写锁是一把互斥锁,不允许其它进程加读锁/写锁。
2)使用F_SETLK做函数的第二个参数。
当l_type的值为: F_RDLCK/F_WRLCK 实现加锁功能
F_UNLCK 实现释放锁的功能
如果文件中已经存在一个冲突的锁,则加锁失败,返回-1;

由例子代码可知:
对于文件加写锁之后还是可以向文件中写入数据的,结果说明锁是独立于文件的读写操作的,并没有真正的锁定对文件的读写操作,也就是说锁只能锁定其它锁(本质上会导致第二次加
锁失败,两把读锁除外)。
解决方案:
可以在读写操作之前尝试加读写锁,根据能否加锁成功来决定是否进行读写操作,从而实现使用文件锁来控制读写操作.
释放锁的方式:
a.进程结束后自动释放所有文件锁
b.设置锁的类型为 F_UNLCK,使用fcntl函数重新设置。
3)使用F_SETLKW作为函数的实参
功能与F_SETLK类似,所不同的是,如果文件中已经存在一个冲突的锁导致当前锁无法加上,则fcntl函数会进入阻塞状态进行等待,直到那个冲突的锁被释放为止.

4)使用F_GETLK作为函数的第二个参数
试图使用第三个参数指定的锁向文件上放置,如果能加上则不去加锁,而是将锁的类型修改为F_UNLCK;如果不能加锁,则将文件上已经存在的锁信息通过第三个参数带出来,并且将给文件
加锁的进程号设置到l_pid字段中。

tat/fstat函数

   #include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>

       int stat(const char *path, struct stat *buf);
       int fstat(int fd, struct stat *buf);

第一个参数:文件的路径和文件名/文件描述符
第二个参数:结构体指针,定义结构体变量取地址
函数功能:主要用于获取的详细信息

struct stat
{
    ...
    mode_t st_mode;     /*文件的类型和权限*/ 
    off_t st_size;      /*文件的大小,单位字节,(重点)*/ 
    time_t st_mtime;        /*文*/ 
    ...0

}

原子操作
两个进程同时写一个文件会出现“覆盖”,问题处在先定位到尾端,然后写,它使用了两个分开的函数调用。
Uinx提供了一种原子操作:在打开文件时设置O_APPEND标志,使得内核在每一次写之前都将进程的当前偏移量设置到文件的尾端,就不必每次调用lseek定位。

#include <unistd.h>
ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, const void *buf,size_t nbytes, off_t offset);

Tips:调用pread相当于调用lseek后调用read,但还存在着重要的区别

  • 调用pread时,无法中断其定位和读操作。
  • 不更新当前文件的偏移量

缓冲区相关函数
大多数磁盘I/O通过缓冲区进行,,当向文件写数据的时候,内核先将数据复制到缓冲区,然后排入队列,晚些时候再写入磁盘,称之为延迟写,当内核需要重用缓冲区来存放其他磁盘数据的时候,它会把所有延迟写的数据写入磁盘。

#include <unistd.h>
void sync(void);  //将有修改过的块缓冲区数据排入写队列,然后就返回
int fsync(int filedes);  //对由文件描述符指定的额外年间起作用,并且等到写磁盘结束才返回。并同步更新文件的属性。
int fdatasync(int filedes) //与上面类似,但只影响文件的数据部分。

文件权限管理函数

access函数
当用open函数打开一个文件的时候,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。
access和faccess函数俺实际用户ID和实际用祜族ID进行访问权限测试

 #include <unistd.h>

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

       int faccess(int fd,const char *pathname, int mode,int flag);

第一个参数:字符串形式的文件路径和文件名
第二个参数:模式/权限
F_OK 判断文件是否存在(重点)
R_OK 判断文件是否可读
W_OK 判断文件是否可写
X_OK 判断文件是否可执行
函数功能:主要用于判断文件权限
返回值:成功返回0,失败返回-1
umask函数
Unix系统的大多数用户不处理他们的umask值,但是如果要确保指定的访问权限位已经激活,那么必须在程序运行时修改umask值

umask函数为进程设置文件创建屏蔽字,并返回之前的屏蔽字

#include <sys/stat.h>
mode_t umask(mode_t cmask);

函数功能:根据参数指定的权限设置权限屏蔽字,也就是创建新文件时去除参数指定的权限,成功返回之前的权限屏蔽字,系统默认屏蔽字0002

Tips:更改进程文件创建屏蔽字不影响其父进程

chmode/fchmod函数

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int filedes, mode_t mode);

chmod函数在指定的文件上进行操作,而fchmod函数则对已经打开的文件进行操作

Tips:改变一个文件的权限位,进行的用户ID必须等于文件的所有者ID,或者是root权限。
truncate/ftruncate函数

#include <unistd.h>
       #include <sys/types.h>
       int truncate(const char *path, off_t length);
       int ftruncate(int fd, off_t length);

函数的功能:主要用于将指定的文件将截取到指定的大小
如果文件变小了,则多余的数据会被丢失,如果数据变大了,扩展的空间用’\0’填充文件的读写位置不会改变。
第一个参数:代表文件的路径
第二个参数:代表文件的大小

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值