2.文件与I/O 文件管理

 

文件管理:在linux下,每个文件都保存在内核中的stat结构中,该结构保存一个文件的状态,这个结构中几乎包含了所有需要的信息。

1 文件状态 stat

在内核中,linux下文件的状态信息和文件内容是分开存储的。在linux中用stat函数来取得一个文件的状态,stat的函数原型:

#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
参数path     :文件所在的路径。对未进行open的文件进行操作。
参数fd          :已经打开的文件描述符。对已经open的文件进行操作。
参数buf          :返回的struct stat结构体的指针,属于值结果参数。
函数执行成功返回0,失败返回-1。
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用文件的信息。
stat结构体以及说明如下:
    struct stat {  
            mode_t     st_mode;           //文件访问权限                                     
     // st_mode包含文件的类型与访问权限、设置用户ID位和设置组ID位,所有信息以位的形式存储,往往涉及大量位运算操作。
            ino_t      st_ino;                 //inode索引节点号                           
            dev_t      st_dev;               //文件系统的设备号  
            dev_t      st_rdev;                 //设备文件的设备号  
     //这3个变量涉及文件系统的实现。    
            nlink_t    st_nlink;                //文件的硬连接数  
     //这个变量涉及文件系统的实现。              
            uid_t      st_uid;                  //所有者用户ID                           
            gid_t      st_gid;                  //所有者组ID
        //empty
            time_t     st_atime;                //最后一次访问该文件的时间  
            time_t     st_mtime;           //最后一次修改该文件的时间  
            time_t     st_ctime;                //最后一次改变该文件状态的时间  
     //几乎所有的文件I/O函数都会改变这3个和时间有关的变量,只有access和stat函数例外。
            off_t      st_size;                 //文件大小,以字节为单位;只对普通文件有效
            blksize_t st_blksize;              //最合适的缓冲区大小
            blkcnt_t   st_blocks;               //该文件在外存上占用的盘块数
     //empty
          }; 

2 文件类型

linux下共有7中文件类型:

  1. 普通文件 -  :文本文件和二进制文件
  2. 目录文件 d  :目录文件主要存放目录下的文件名和指向它们的指针
  3. 块设备文件 b:带有缓冲区的设备文件
  4. 字符设备文件 c :无缓冲区的设备文件
  5. 命名管道  f:用于进程间通信的文件,只存在于内核中
  6. 符号链接  l:指向另一个文件
  7. 套接字     s:网络进程之间的通信,一种特殊的管道,也可以用于同一主机内的进程间非网络通信。

在stat结构体中文件类型存储在st_mode变量中,表示文件类型的是3个连续的二进制数,共有8个状态,包括7中文件类型与未知类型。linux提供一些宏来取得相应的二进制位用于判断文件的类型,宏定义在文件sys/stat.h中。
文件类型和对应的测试宏:

     S_ISREG()     :     普通文件
     S_ISDIR()     :     目录文件
     S_ISCHR()     :     块设备文件
     S_ISBLK()     :     字符设备文件
     S_ISFIFO() :     管道文件
     S_ISLNK()     :     符号链接
     S_ISSOCK():     套接字

使用方法:宏(stat结构体变量.st_mode),若非宏所属的这种类型,返回0,是的话返回非0值。

3 文件权限

3.1 访问权限

每个文件的访问权限分3类,每一类分别针对3种用户。

  • 文件的用户:文件所有者(u),文件所有者的组(g),其他用户(o)。
  • 文件的访问权限:可读(r)、可写(w)、可执行(x)。

在stat结构体中st_mode变量一共有9个标志位给访问权限,其顺序由高到低分别是:

  • 文件所有者(u)          :可读(r)、可写(w)、可执行(x);
  • 文件所有者的组(g)     :可读(r)、可写(w)、可执行(x);
  • 其他用户(o)               :可读(r)、可写(w)、可执行(x);

如果判断该位是否具有权限则利用st_mode与相应的测试宏进行与操作即可,类似于判断文件类型。
文件权限测试宏:

     S_IRUSR     :0400:用户-读
     S_IWUSR     :0200:用户-写
     S_IXUSR     :0100:用户-执行
     S_IRGRP     :0040:用户组-读
     S_IWGRP     :0020:用户组-写
     S_IXGRP     :0010:用户组-执行
     S_IROTH     :0004:其他用户-读
     S_IWOTH     :0002:其他用户-写
     S_IXOTH     :0001:其他用户-执行

使用方法:stat结构体变量.st_mode &宏,若非宏所属的这种类型,返回0;是的话返回非0值。

3.2 设置用户ID位与设置用户组ID位

文件权限位中有两位分别用于查看并设置是否有设置用户ID位与设置用户组ID位的权限。与文件权限有关的进程ID分为4种情况:

  • ruid:实际用户ID:表示进程属于哪个用户,一般是谁创建了进程,进程就属于哪个用户。
  • euid:有效用户ID :表示进程目前实际具有的用户权限。一个进程得到某个用户的允许,暂时成为该用户的进程,从而可以行使与该用户其他进程同样的权限。
  • rgid:实际用户组ID:表示进程属于哪个用户组,一般是谁创建了进程,进程就属于哪个用户的用户组。
  • egid:有效用户组ID:表示进程目前实际具有的用户组权限。一个进程得到某个用户组的用户的允许,暂时称为该用户的用户组的进程,从而可以行使与该用户所在用户组其他进程同样的权限。

文件的设置用户ID位和设置用户组ID位可以使进程的有效用户ID和用户组ID等同于文件所有者的用户ID和用户组ID,即可以使进程拥有者的权限暂时等同于文件所有者的权限。用户ID和组ID的值是在调用exec函数的时候改变的,因此设置用户ID位和设置组ID位只对可执行文件(二进制文件)起作用。

如果判断设置用户ID位和设置用户组ID位的值,需要用st_mode与相应的宏进行与操作:
设置用户ID位与设置用户组ID位测试宏:

     S_ISUID     :04000 :设置用户ID位
     S_ISGID     :02000     :设置用户组ID位

使用方法:stat结构体变量.st_mode &宏,若非宏所属的属性,返回0;是的话返回非0值。

设置用户ID位的作用:用户程序本身不可以操作内核区的文件,但是通过向内核请求得到的批准,暂时创建一个新的进程后,利用这个进程就可以用内核的权限操作文件。进程的有效用户ID有以下几种情况:

  1. 0(该进程属于根用户/得到根用户的授权),此时,可以访问,即进程拥有文件所有者的权限。
  2. 和文件的所有者用户ID一致(该进程属于文件的所有者/得到所有者的授权),且文件所有者适当的文件权限位已经被设置,则可以访问,否则拒绝访问。
  3. 和文件的组ID一致(该进程属于文件所在的组/得到该组的授权),且文件的用户组权限已经设置,则可以访问。

不满足上面3条,则拒绝访问。

4 文件权限操作

4.1 测试文件访问权限 access

测试当前的用户是否有权限对某些文件进行操作,linux下使用access函数进行访问权限测试。access函数的原型:

#include <unistd.h>
int access(const char *pathname, int mode);
参数pathname     :测试文件的路径
参数mode         :测试文件的模式。
     R_OK     :测试读权限
     W_OK     :测试写权限
     X_OK     :测试可执行权限
     F_OK     :测试文件是否存在
函数执行成功返回0,失败返回-1。

access函数使用进程的实际用户ID和用户组ID与文件所有者ID和组ID进行比较:

  • 如果进程的实际用户ID与文件所有者ID相等,则使用参数mode指定的权限与文件所有者的权限进行比较。
  • 如果上面不成立,且进程的实际组ID与文件的组ID一致,则使用mode指定的权限与文件的组权限进行比较。
  • 如果上述条件都不成立,则使用mode指定的权限与其他用户权限进行比较。

4.2 使用文件权限屏蔽字

在实际应用中,系统有时并不希望用户设置文件所有的权限,因此设置了umask权限屏蔽码,可以屏蔽一些不希望用户干预的权限位。这样即便用户设置了相关的位,但是由于掩码的原因设置无效。
此时文件权限 = 进程创建文件的权限字 & umask掩码屏蔽。
linux用umask来创建一个权限屏蔽字。umask函数的原型:

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
参数mask:一个新的屏蔽字,是一个位向量,表示9个权限标志位,哪一位的屏蔽位为1,说明用户在创建文件时所指定的权限标志无效。如果mask的值是0,则新文件的所有权限完全由用户来指定,完全有效。
umask函数的返回值是以前的屏蔽字,可以将它保存后用于操作结束后对屏蔽字的恢复。

在shell中可以使用命令umask来查看掩码的值,同样也可以加上权限值来直接设置掩码的值。

$umask 查看掩码;     $umask 007 设置掩码为007;

注意:当使用umask函数时,仅对当前进程有效,退出程序后无效。

4.3 改变文件访问权限

Linux下使用chmod函数来改变文件的权限。chmod函数的原型:

#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
参数path     :需要更改权限的文件的路径
参数mode     :新的权限。
参数fd          :已经打开的文件的文件描述符
     S_ISUID     :04000:设置用户ID位
     S_ISGID     :02000:设置用户组ID位
     S_ISVTX     :01000:粘性位
     S_IRWXU     :0700:用户-读写执行    
     S_IRUSR     :0400:用户-读
     S_IWUSR     :0200:用户-写
     S_IXUSR     :0100:用户-执行
     S_IRWXG     :0070:用户组-读写执行    
     S_IRGRP     :0040:用户组-读
     S_IWGRP     :0020:用户组-写
     S_IXGRP     :0010:用户组-执行
     S_IRWXO     :0007:其他用户-读写执行    
     S_IROTH     :0004:其他用户-读
     S_IWOTH     :0002:其他用户-写
     S_IXOTH     :0001:其他用户-执行
     函数执行成功返回0,失败返回-1。

在linux下,可以通过chmod命令直接改变权限。在程序中利用chmod函数是一样的效果。

fchmod函数仅仅是对已经打开的文件进行操作,其他地方与chmod是一样的。

5 文件系统结构简介

文件系统中存储的最小单位是块(block),一块到底有多大是由磁盘进行格式化时指定的 。linux下使用mke2fs命令将一个指定的磁盘分区格式化,并且在该分区上建立一个文件系统,命令参数-b用于指定块的大小。所有的柱面组之前有两个主要的块:引导块与超级块

  • 引导块:用来存储磁盘分区信息和启动信息,任何系统都不能改写引导块
  • 超级块:描述整个分区的文件系统信息,如块的大小、文件系统版本号、上次挂载文件系统的时间等。

文件系统将整个分区划成若干个同样大小的柱面组,每个柱面组有6个部分组成:

  1. 超级块副本         :和柱面之前的超级块一样,只是复制了一份而已
  2. 柱面组描述表     :存储柱面组的信息,如inode节点的起始位置,数据块的起始位置等。这些信息一损坏就会丢失整个分区
  3. inode节点位图   :inode位图信息本身占一块,每个位代表柱面组的一块,1表示本块已用,0表示未用。
  4. 数据块位图         :类似于inode位图,每个位代表柱面组的一块,1表示本块已用,0表示未用。
  5. inode存储区       :存储本柱面组的inode节点
  6. 数据块存储区     :存储本柱面组的数据块。(根据文件类型数据块分为:存储文件内容的数据块、存储目录项的目录块)

6 文件的硬链接

硬链接:把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。一个文件有几个文件名(用ln命令实现多个文件名),我们就说该文件的链接数为几。由定义可知,此链接数可以是1, 这表明该文件只有一个文件名。创建一个硬链接:(linux下使用link函数创建一个硬链接)。link函数的原型:

#include <unistd.h>
int link(const char *oldpath, const char *newpath);
     参数oldpath          :创建硬链接的源本,也就是现有的文件
     参数newpath     :创建的新链接
     函数成功执行返回0,失败返回-1。
删除一个硬链接:(linux下使用unlink函数创建一个硬链接)
unlink函数的原型:
#include <unistd.h>
int unlink(const char *pathname);
     参数pathname:将pathname的最末端的目录项删除。同时目录项所对应的磁盘文件的链接数减1。如果链接数为0,则文件将被删除。
     函数成功执行返回0,失败返回-1。

注意:当一个文件的链接数为0且文件不再处于打开的状态时,系统将自动删除该文件。如果文件的链接数为0,但是文件处于打开状态则文件不被删除,只是对文件的读和写操作实际上是对内存缓冲区进行操作,当文件关闭时,内存中的缓冲区清空,文件也就消失了。

7 改变文件所有者

linux下使用chown函数改变一个文件的所有者。chown函数的原型:

#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
     参数path     :文件的路径
     参数owner     :新的所有者用户的ID。值为-1时表示文件所有者用户ID不发生变化。
     参数group     :新的所有者用户组的ID。值为-1时表示文件所有者用户组ID不发生变化。
     参数fd          :已经打开的文件的文件描述符
     函数成功执行返回0,失败返回-1。

如果要修改一个文件的所有者ID和组ID,该用户必须是根用户,或者需要同时满足以下3点才可以成功更改文件的所有者:

  1. 修改文件进程的有效用户ID等于该文件的ID,即修改经过文件所有者授权。
  2. 参数owner为-1或者文件用户的ID,即文件用户的可以不更该/改成原所有者的文件
  3. 参数group等于进程的有效组ID,即用户只能将文件改到用户组当中

注意:对于文件所有者的修改,即使指定的用户并不存在,chown函数部不检查其存在性,同样不会报错,依然会将所有者修改为用户指定的,只有在操作该文件的时候会报错。  

fchown与chown函数相比,只是对已经打开的文件进行操作。

lchown与chown函数相比,只是改变符号链接的所有者,不改变所指向的文件的所有者。

8 操作文件的时间简介

文件结构中有3个与文件操作有关的时间:

  • st_atime     :最近访问文件的时间
  • st_mtime     :文件内容修改时间
  • st_ctime     :文件状态修改时间,即stat结构体中变量有所变化。

使用stat命令可以查看时间属性。

注意:read函数只对文件内容进行读操作,因此不会改变ctime,只会改变atime。

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值