《UNIX环境高级编程》第4章 文件和目录

来源:互联网 发布:淘宝分享有赏红包 编辑:程序博客网 时间:2024/06/05 04:48

文件和目录

4.1 引言

本章将描述文件系统的其他特征和文件的性质。

4.2 函数stat、fstat、fstatat和lstat

#include<sys/stat.h>int stat(const char *restrict pathname,struct stat *restrict buf);int fstat(int fd,struct stat *buf);int lstat(const char *restrict pathname,struct stat *restrict buf);int fstatat(int fd,const char *restrict pathname,struct stat *restrict buf,int flag);
  1. stat函数将会返回与此命名文件有关的信息结构
  2. fstat函数获得已在描述符fd上打开文件的有关信息。
  3. lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号链接的有关信息,而不是该符号链接引用文件的信息。
  4. fatatat函数为一个相对于当前打开目录(由fd参数指向)的路径名返回文件统计信息。
    4.1flag控制着是否跟随着一个符号链接,当AT_SYSLINK_NOFOLLOW标志设置时,fstatat不会跟随符号链接,而是返回符号本身的信息。否则在默认情况下返回的是符号链接所指向的实际文件的信息。
    4.2 如果flag参数是AT_FDCWD,并且pathname是一个相对路径名,则fstatat函数返回相对于当前路径的pathname的文件信息。
    4.3 pathname是一个绝对路径名,则fd无效;
    buf是一个指针,它指向一个我们必须提供的结构。函数填充buf所指向的结构。具体的实现可能不同,但基本结构如下:
struct stat{mode_t      st_mode;            //file type and mode(permission)ino_t       st_ino;             //i-node number(serial number)dev_t       st_dev;             //device number(file system)dev_t       st_dev;             //device number for special filesnlink_t     st_nlink;           //number of linksuid_t       st_uid;             //user id of owmergit_t       st_gid;             //group id of owneroff_t       st_size;            //size in bytes,for regular filesstruct timespec st_atime;       //time of last accessstruct timespec st_mtime;       //time of last modificationstruct timespec st_ctime;       //time of last file status changeblksize_t   st_blksize;         //best io block sizeblkcnt_t    st_blocks;          //nunber of disk blocks allocated}

使用stat函数最多的地方可能就是ls -l命令,用其可以获得文件的详细信息。

4.3 文件类型

UNIX系统的几种文件类型:

序号 文件类型 描述 (1) 普通文件(regular file) 这是最常见的文件类型,这种文件包含了某种形式的数据。 (2) 目录文件(directory file) 这种文件包含了其他文件的名字以及指向这些文件有关信息的指针。有相应权限的进程可以读目录文件,但只要内核可以直接写目录文件。进程必须使用相应的函数才可以更改目录。 (3) 块特殊文件(block special file) 这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。(就像nandflash) (4) 字符特殊文件(character special file) 这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中所有的设备要么是字符特殊文件,要么是块特殊文件。 (5) FIFO 这种类型的文件用于进程间通信,有时也称为命名管道(named pipe)。 (6) 套接字(socket) 这种文件用于进程间的网络通信。套接字也可以用于在一台宿主机上进程间的非网络通信。 (7) 符号链接(symbolic link) 这种类型的文件指向另一个文件。

文件的类型信息包含在stat结构的st_mode成员中,st_mode的成员包括:

宏 文件类型 S_ISREG() 普通文件 S_ISDIR() 目录文件 S_ISCHR() 字符特殊文件 S_ISBLK() 块特殊文件 S_ISFIFO() 管道或FIFO S_ISLNK() 符号链接 S_ISSOCK() 套接字

POSIX.1允许将进程间通信(IPC)对象(如消息队列和信号量等)说明为文件。

宏 对象类型 S_TYPEISMQ() 消息队列 S_TYPEISSEM() 信号量 S_TYPEISSHM() 共享存储对象

但是本书所讨论的4种UNIX系统都不将这些对象表示为文件。


早起的UNIX不提供S_ISXXX宏,于是需要将st_mode与屏蔽字S_IFMT进程逻辑与运算,然后与名为S_IFXXX的常量进行比较。在

#include <sys/stat.h>#define S_ISDIR (mode) ((mode)&S_IFMT)==S_IFDIR)

4.4 设置用户ID(位)和设置组ID(位)(注:用来决定进程相关ID和文件ID的关系)

aaa.文件相关的ID:每个文件有一个所有者和组所有者;
所有者由stat结构中的st_uid指定,
组所有者则由stat结构中的st_gid指定。

bbb.进程相关:与一个进程相关联的ID有6个或更多,如下
实际用户ID、实际组ID
标识我们究竟是谁。
这两个字段在登录时取自口令文件中的登录项。通常在一个登录会话期间这些值并不改变,但超级用户进程可以改变他们。
(自己理解为实际用户ID和实际组ID是进程在运行时获得的属性,不知道可不可以这样理解。)


有效用户ID、有效组ID、附属组ID
决定了我们的文件访问权限。
情况1.
当执行一个程序文件时,进程的有效用户ID=实际用户ID,有效组ID=实际组ID。
情况2.
可以在程序文件模式字(st_mode)中设置特殊标志:
设置用户ID(set-user-ID)位,使得:进程的有效用户ID=文件所有者用户ID(st_uid)
设置组ID(set-group-ID)位,使得:进程的有效组ID=文件所有者组ID(st_gid)
例如,若文件系统所有者是超级用户,而且设置了该文件的设置用户ID位,那么当该程序文件由一个进程执行时,该进程具有超级用户权限。
设置用户ID为何设置组ID位都包含在文件的st_mode值中。这两位可以分别用常量S_ISUID和S_ISGID测试。


保存的设置用户ID、保存的设置组ID
执行一个程序时保存的设置用户ID保存的设置组ID包含了有效用户ID有效组ID的副本。

4.5 文件访问权限

st_mode值也包含了对文件的访问权限位。当提及文件时,指的是前面提到的任何类型的文件。所有文件类型都有访问权限(access permission)。

st_mode屏蔽 含义 S_IRUSR 用户读 S_IWUSR 用户写 S_IXUSR 用户执行 S_IRGRP 组读 S_IWGRP 组写 S_IXGRP 组执行 S_IROTH 其他读 S_IWOTH 其他写 S_IXOTH 其他执行

用户指的是文件所有者(owner),chmod命令用户修改这9个权限位。该命令允许我们用u表用户,g表示组,o表示其他。

4.6 新文件和目录的所有权(注:进程所创建的文件的所有权与进程和当前目录的关系)

在第3章中用open或create创建新文件时,我们并没有说明赋予新文件的用户ID和组ID是什么。
新文件的用户ID设置为进程的有效用户ID。
新文件的组ID可以是进程的有效组ID;也可是它所在目录的组ID。

4.7 函数access和faccessat(注:以进程的实际用户ID和实际组ID测试打开文件的访问权)

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

#include <unistd.h>int access(const char *pathname,int mode);int faccseeat(int fd,const char *pathname,int mode,int flag);//若成功返回0,出错返回-1
mode 说明 F_OK 测试文件是否存在 R_OK 测试读权限 W_OK 测试写权限 X_OK 测试执行权限

faccessat和access函数在下面两种情况下是相同的:
1.pathname为绝对路径;
2.fd取值为AT_FDCWD,pathname为相对路径;
否则faccessat函数计算相对于打开目录(由fd参数指向)的pathname。
flag参数可以用于改变faccessat的行为,如果flag设置为AT_EACCESS,则内核是以有效用户ID和有效组ID进行权限测试的,而不是实际用户ID和实际组ID.

4.8 函数umask(注:进程所有的)

umask函数为进程设置文件模式创建屏蔽字

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

cmask是9个常量(S_IRUSR、S_IWUSR等)中若干个按位“或”构成的。
进程在创建一个文件时,一定会使用文件模式创建屏蔽字,结合open和create函数的mode参数,指定新文件的访问权限位;
在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭。
更改进程的文件模式创建屏蔽字不影响父进程的屏蔽字;
每个shell都有umask命令,用来显示和改变shell的文件模式创建屏蔽字,同时shell还支持符号形式的umask命令:umaks -s;

4.9 函数chmod、fchmod、fchmodat

chmod、fchmod、fchmodat这3个函数使我们可以改变现有文件的访问权限。

#include <sys/stat.h>int chmode(const char *pathname,mode_t mode);int fchmode(int fd,mode_t mode);int fchmodeat(int fd,const char *pathname,mode_t mode,int flag);

chmod函数在指定的文件上进行操作;
fchmod函数则对已打开的文件进行操作;
fchmodat函数在两种情况下与chmod函数是一样的,一是当pathname为绝对路径时,二是当fd为AT_FDCWD而pathname为相对路径。否则fchmodat函数计算相对于fd的pathname。flag参数改变fchmodat的行为,flag为AT_SYMLINK_NOFOLLOW标志时,fchmodat不会跟随符号链接。

4.10 粘着位

(注:不太用得到,有些历史故事;)

4.11 函数chown、fchown、chownat和lchown

这几个函数用来改变文件的用户ID和组ID。如果两个参数owmer和group中的任意一个是-1则对应的ID不改变;

#include <unistd.h>int chown(const char *pathname,uid_t pwner,gid_t group);int fchown(int fd,uid_t pwner,gid_t group);int fchownat(int fd,const char *pathname,uid_t pwner,gid_t group,int flag);int lchown(const char *pathname,uid_t pwner,gid_t group);

如果文件不是符号链接,这4个函数的操作类似;
如果是符号链接,lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW)更改符号链接本身的所有者,而不是该符号链接所指向文件的所有者。

4.12 文件长度

stat结构成员st_size表示以自己问单位的文件长度。此字段只对普通文件、目录文件和符号链接有意义。
普通文件:其文件长度可以是0,读取这种文件时,将得到文件结束(end-of-file)指示。
目录文件:文件长度通常是一个数(如16或512)的整数倍。
符号链接:文件长度是符号所指向的文件名中的字节数(不包括null字节),如下所示长度为7(即usr/lib);

lrwxrwxrwx 1 root   7 Sep 25 07:14 lib-> usr/lib

现在很多UNIX系统提供字段st_blksize和st_blocks。第一个是对文件IO较合适的块长度,第二个是所分配的实际512字节块的块数;(不同的UNIX系,其st_blocks所用的单位可能不是512字节的块)


文件中的空洞
普通文件可以包含空洞,空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。

$ ls -l core-rw-r--r-- 1 sar 8483248 Nov 18 12:18 core$ du -s core272 core

可以看出,文件core长度为8483248字节,但占用了块272个(272x512=139264个字节),所有文件中有空洞;
空洞字节位置read读到的是0(\0),如果使用shell的wc命令(使用read实现)可以看到正常的IO操作读整个文件长度:

$ wc -c core8483248 core

如果复制带有空洞的文件,那么这些空洞会用0填满;因此文件占用的磁盘块会增大;

$ cp core core.copy$ du -s core*272 core1659 core.copy

4.13 文件截断

可以使用truncate函数和ftruncate函数将文件截短;
将一个文件的长度截断为0在打开文件时使用O_TRUNC标志也可以做到这一点。

#include <unistd.h>int truncate(const char *pathname,ott_t length);int ftruncate(int fd,ott_t length);

这两个函数将文件截断为length长度,若文件长度大于length则之外的部分不能再访问,若文件长度小于length则文件长度将增加(创建以个空洞)。

4.14 文件系统

磁盘、分区、文件系统:
这里写图片描述

柱面组的i节点和数据块:
这里写图片描述
文件系统: 可以把一个磁盘分为多个分区,每个分区包含一个文件系统;
目录项: Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。
inode: 是固定长度的记录项,它包含有关文件的大部分信息;
硬连接: 一般情况下目录项和inode一一对应,即每个文件名对应一个inode,但linux允许多个文件名对应一个inode;每个i节点都有一个连接计数,其值是指向该i节点的目录项数。连接计数包含在stat的st_nlink中。只有当连接计数减少到0时才可以删除该文件(也就是释放该文件占用的磁盘块),这种连接称为硬链接。(注:目录项指向inode,称为硬连接;mv操作就是只改变文件名而不改变数据块的操作。)
符号链接: 除了硬链接以外,还有一种特殊情况。文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的”软链接”(soft link)或者”符号链接(symbolic link)。(注:符号链接文件的内容是被链接文件的路径,符号文件大小等于被链接文件的路径名长度。)


创建目录后的文件系统:
这里写图片描述
如上图所示,
编号为2549的inode,其类型字段表示它是一个目录,链接计数为2(.和它的父目录(目录名的所在地));
编号为1267的inode,其类型字段表示它是一个目录,链接数大于或等于3,至少有3个目录项指向它,(.和它的父目录(目录名的所在地)和子目录中的testdir),在父目录中每增加一个子目录都能使该父目录的链接计数增加1。

4.15 函数link、linkat、unlink、unlinkat和remove

上节所述,任何一个文件都可以有多个目录项指向其i节点。创建一个指向享有文件的链接的方法使使用link或linkat函数。

#include <unistd.h>int link(const char *existingpath,const char *newpath);//绝对路径形式int link(int efd,const char *existingpath,int nfd,const char *newpath,int flag);//相对路径形式

只创建newpath中的最后一个分量,路径中的其他部分应该已经存在。
当现有文件时符号链接时,由flag参数来控制linkat函数是创建指向符号链接的链接还是指向符号链接所指向的文件的链接。当flag=AT_SYSLINK_FOLLOW标志,则创建指向符号链接目标的链接,否则创建一个指向符号链接本身的链接。


可以用unlink函数删除一个现有的目录项:

#include <unistd.h>int unlink(const char *pathname);//绝对路径形式int link(int fd,const char *pathname,int flag);//相对路径形式

这两个函数删除目录项,并将pathname文件的inode的链接计数减1.当链接计数为0时,且没有进程打开该文件,则删除该文件。当关闭一文件时,内核会检查打开该文件的进程个数,如果为0,再检查链接计数,如果也是0,就删除该文件的内容。
ulink的这项特性经常用来确保即使程序崩溃它所创建的临时文件也不会遗留下来。
对于临时文件:先open打开,再unlink解除连接,这样在进程退出时会自动释放文件的数据块。


也可以用remove函数解除一个文件或目录的连接。对于文件,remove的功能与unlink相同。对于目录remove的功能与rmdir相同。

#include <stdio.h>int remove(const char *pathname);

4.16 函数rename和renameat

文件或目录可以用rename函数或者renameat函数进行重命名。

#include <stdio.h>int rename(const char *oldname,const char *newname);//绝对路径int renameat(int oldfd,const char *oldname,int oldfd ,const char *newname);//相对路径

4.17 符号链接

*硬链接通常要求链接和文件位于同一文件系统中。
*符号链接不限制在一个文件系统内,任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移动到系统的另一个位置。

4.18 创建和读取符号链接

#include <unistd.h>int symlink(const char *actualpath,const char *symlpath);//绝对路径形式int symlink(int fd,const char *actualpath,const char *symlpath);//相对路径形式

函数创建了一个指向actualpath的新项目sympath。在创建此符号链接时,并不要求actualpath已经存在。并且actualpath和sympath不要求在同一文件系统中。


因为open函数跟随者符号链接本身,所有需要有一种方法打开链接本身,并读该链接中的名字。
readlink和readlinkat函数提供这种功能。

#include <unistd.h>ssize_t readlink(const char *restrict pathname,char *restrict buf,size_t bufsize);ssize_t readlinkat(int fd,const char *restrict pathname,char *restrict buf,size_t bufsize);

这两个函数组合了open、read、和close的所有操作。

4.19 文件时间

每个文件维护3个时间字段:

字段 说明 例子 ls选项 st_atim 文件数据的最后访问时间 read -u st_mtim 文件数据的最后修改时间 write 默认 st_ctim inode状态最后更改时间 chmod、chown -c

修改时间是文件内容最后一次被修改的时间。
状态更改时间是该文件的inode最后一次呗修改的时间。
注意:系统不维护inode的最后访问时间,所以stat行access函数并不更改这3个时中的任意一个。

4.20 函数futimens、utimensat和utimes

一个文件的访问和修改时间可以用以下几个函数更改。

#include <sys/stat.h>int futimes(in fd,const struct timespec times[2]);int utimensat(in fd,const char *path,const struct timespec times[2],int flag);

这里不展开了。。。 《APUE》PG101-103

4.21 函数mkdir、mkdirat和rmdir

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

这两个函数创建一个空目录。其中.和..是自动创建的,所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。


#include <sys/stat.h>int mkdir(const char *pathname);

rmdir函数可以删除一个空目录。空目录是只包含.和..的目录。
rmdir函数删除目录和ulink函数删除文件的规则相同,即将目录链接数设为0,若没有进程打开则释放空间,否则待相关进程结束后再释放目录空间。

4.22 读目录

对某个目录具有访问权限的任一用户都可以读该目录,但只有内核才能写目录。
目录的实际格式与UNIX系统的实现和文件系统的设计相关,因此读目录文件的程序与系统相关,文件简化读目录的过程,UNIX包含了读目录的相关例程,很多实现阻止应用程序使用read函数读取目录内容,由此进一步将应用程序和目录格式中的实现相关细节隔离。(注:简单的说就是因为目录文件在各个系统中的实现不一样,所以使用区别于操作普通文件的方式去操作目录文件,因此产生了以下函数。)

#include <dirent.h>DIR *opendir(const char *pathname);DIR *fdopendir(int fd); //将打开的文件描述符转换成目录处理函数需要的DIR结构。struct dirent *readdir(DIR *dp);void rewinddir(DIR *dp);int closedir(DIR *dp);long telldir(DIR *dp);void seekdir(DIR *dp,long loc);
//<dirent.h>中的dirent结构至少包括以下成员:ino_t d_ino;    //inode numberchar d_name[];  //null-terminated filename

opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。
readdir执行初始化操作,使第一个readdir返回目录中的第一个目录项。

4.23 函数chdir、fchdir和getcwd

每个进程都以一个当前工作目录,此目录是搜索所有相对路径名的起点。当用户登录UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第6个字段—用户起始目录(home directory)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。
进程调用chdir或fchdir函数可以更改当前工作目录。

#include <unistd.h>int chdir(const char *pathname);int fchdir(int fd);

使用pathname或文件描述符fd来指定新的当前工作目录。


使用getcwd函数来获取进程当前工作目录。

#include <unistd.h>char *getcwd(char *buf,size_t size);

4.24 设备特殊文件

*每个文件系统所在的存储设备都由主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为与其通信的外设板次设备号标识特定的子设备。例如:一个磁盘驱动器经常包含若干个文件系统。在同一个磁盘启动器上的各个文件系统具有相同的主设备号,但次设备号却不同。
*我们通常可以使用两个宏:major和minor来访问主、次设备号,大多数实现都定义这两个宏。
*系统中每个文件名关联的st_dev值是文件系统的设备号。
*只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号。

printf("dev=%d/%d",major(buf.st_dev),minor(buf.st_dev));

4.25 文件访问权限位小结

9个访问权限位常量可以分为以下3组:
S_IRWXU=S_IRUSR|S_IWUSR|S_IXUSR
S_IRWXU=S_IRGRP|S_IWGRP|S_IXGRP
S_IRWXU=S_IROTH|S_IWOTH|S_IXOTH

4.26 小结

本章围绕stat函数,详细介绍了stat结构中的每一个成员。这使我们对UNIX文件和目录的各个属性都有所了解。我们讨论了文件和目录在文件系统中是如何设计的以及如何使用文件系统命名空间。
对文件和目录的所有属性以及对文件和目录进行操作的所有函数的全面了解,对于UNIX编程是非常重要的。

0 0
原创粉丝点击