《Unix环境高级编程》第四章 文件和目录--笔记

前言

        近期学习了一下UNIX环境高级编程,也做了一些相关知识点的笔记,以便后续自己的回忆以及阅读,同时方便读者的理解与应用。这对将来我们的嵌入式驱动代码的编写将会有很大的裨益,欢迎指出不足,择优修改,共勉成长!

目录

前言

4.1 函数stat、fstat、fstatat和lstat​​​​​​​

4.2 文件类型

4.2.1 普通文件(regular file)

4.2.2 目录文件(directory file)

4.2.3 块特殊文件(block special file)

4.2.4 字符特殊文件(character special file)

4.2.5 FIFO

4.2.6 套接字(socket)

4.2.7 符号链接(symbolic link)

4.3 设置用户ID和设置组ID

4.4 文件访问权限

4.5 新文件和目录的所有权

 4.6 函数access和faccessat

 4.7 函数umask

4.8 函数chmod、fchmod和fchmodat

4.9 粘着位(nian还是zhan随你们吧o.O)

4.10 函数chown、fchown、fchownat 和 lchown

 4.11 文件长度(涉及到了文件空洞)

        4.11.1 文件长度

        4.11.2 文件空洞

4.12 文件截断 

4.13 文件系统(嫌累可以滑到本小结末看文件系统的总结)

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

4.15 函数rename和renameat

4.16 符号链接(快捷方式来了)

 4.17 创建和读取符号链接

4.18 文件的时间

4.19 函数futimens、utimensat和utimes 

4.20 函数mkdir、mkdirat和rmdir

4.21 读目录

4.22 函数chdir、fchdir和getcwd

 4.23 设备特殊文件

4.24 文件权限访问小结(写不动了,直接上截图吧)

总结 (第四章 文件和目录 完结撒花)


4.1 函数stat、fstat、fstatat和lstat

        返回指定文件信息。

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 dirfd, const char *restrict pathname, struct stat *restrict buf, int flag);
                    //所有4个函数的返回值:若成功:返回0;若出错,返回-1

  • stat函数返回pathname指定的文件信息,将文件信息保存在buf参数中。
  • fstat函数与stat类似,只是通过文件描述符fd标识文件。
  • lstat与stat类似,只是当pathname是符号链接时,获得的是符号链接文件本身信息,而不是符号链接引用的文件的信息。
  • fstatat中,flag参数控制着是否跟随一个符号链接。当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而是返回符号链接本身的信息。否则在默认情况下,返回的是符号链接所指向的实际文件的信息。若fd为AT_FDCWD,而且pathname为相对路径名,fstata会计算相对于当前目录下的pathname参数。如果pathname为绝对路径,fd被忽略。这两种情况下根据flag的取值,fstatat的作用与stat和lstat一样。
  • buf为一个指针,指向我们提供的结构。其基本形式如下:
struct stat {
    mode_t     st_mode;        /* 文件类型和访问权限 */
    ino_t     st_ino;         /* i-node number */
    dev_t    st_dev;         /* 文件系统设备号 */
    dev_t    st_rdev;        /*特殊文件系统设备号*/
    nlink_t   st_nlink;       /* 硬链接个数 */
    uid_t     st_uid;         /* 所有者uid */
    gid_t     st_gid;         /* 所有者gid */
    off_t     st_size;        /* 文件大小(字节) */
    struct timespec st_atime;  /* 文件数据最近访问时间 */
    struct timespec st_mtime;  /* 文件数据最近修改时间 */
    struct timespec st_ctime;  /* inode节点状态最近改变时间 */
    blksize_t st_blksize;     /* 对文件I/O较合适的块长度 */
    blkcnt_t  st_blocks;      /* 该文件分配的512字节块的数量 */
};

注意:timespec结构类型按照秒和纳秒定义了时间,至少包括了以下两个字段:

time_t tv_sec;

long tv_nsec;

使用stat最多的地方就是ls -l命令,用其可以获得有关一个文件的所有信息。

4.2 文件类型

UNIX系统的大多数文件是普通文件或者目录,但是也有其他另外一些文件类型。

4.2.1 普通文件(regular file)

        最常用的文件类型,包含了某种形式的数据。至于数据是文本还是二进制形式对Unix环境来说并无区别。

4.2.2 目录文件(directory file)

        这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任意进程都可以读该目录的内容,但是只有内核可以直接写目录文件。

4.2.3 块特殊文件(block special file)

        提供对设备(如磁带)带缓冲的访问,每次访问以固定长度为单位进行。

4.2.4 字符特殊文件(character special file)

        提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。

4.2.5 FIFO

        这种类型的文件用于进程间通信,有时也称为命名管道

4.2.6 套接字(socket)

        这种类型的文件用于进程间的网络通信,也可以用于在一台宿主机上进程间的非网络通信。

4.2.7 符号链接(symbolic link)

        这种类型文件指向另一个文件,类似于快捷方式。

注意:文件类型信息包含在stat结构的st_mode成员中。以下宏的参数都是stat结构中的st_mode成员。

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

以下程序取其命令行参数,然后针对每一个命令行参数打印其类型。

#include "apue.h"

int main(char argc,char *argv[])
{
    int i;
    sturct stat buf;
    char *ptr;
    for(i=1;i<argc;i++)
    {
        printf("%s:",arfv[i]);
        if(lstat(argv[i],&buf) < 0)
        {
            err_ret("lstat error");
            continue;
        }
        if(S_ISREG(buf.st_mode))
        {
            ptr = "regular";
        }
        else if(S_ISDIR(buf.st_mode))
        {
            ptr = "directory";
        }
        else if(S_ISCHR(buf.st_mode))
        {
            ptr = "character special";
        }
        else if(S_ISBLK(buf.st_mode))
        {
            ptr = "block special";
        }
        else if(S_ISFIFO(buf.st_mode))
        {
            ptr = "fifo";
        }
        else if(S_ISLINK(buf.st_mode))
        {
            ptr = "symbol link";
        }
        else if(S_ISSOCK(buf.st_mode))
        {
            ptr = "socket";
        }
        else
        {
              ptr = "** unknown mode ** ";  
        }
        printf("%s\n",ptr);
    } 
    exit(0);
}

 程序输出结果为:

在此处我们使用的是lstat函数而不是stat函数以防检测不到符号链接,符号链接的概念我们会在下文进行讲解。

$./a.out/etc/passwd /etc /dov/log /dev/tty \
>/var/lib/oprofile/opd_pipe /dev/sr0 /dev/cdrom /etc/passwd: reqular
/etc: directory
/dev/log: socket
/dev/tty:character special
/var/lib/oprofile/opd.pipe:fifo
/dev/sr0:block special
/dev/cdrom: symbolic link

4.3 设置用户ID和设置组ID

 与一个进程相关联的ID有六个甚至更多,如下表格所示:

实际用户ID
实际组ID

我们实际上是谁,即执行此进程的实际用户。

这两个ID是用户登录时取自口令文件的。

有效用户ID
有效组ID
附属组ID
用于文件访问权限检查。
保存的设置用户ID
保存的设置组ID
用exec函数保存。
  • 实际用户ID和实际用户组ID的话超级进程是有方法改变它们的。
  • 有效用户ID和有效用户组ID决定了我们的文件访问权限
  • ID和保存的设置组ID在执行有效用户ID和组ID的副本
        通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。每个文件有一个所有者和组所有者,所有者由stat结构中的st_uid指定,组所有者则由st_gid指定。
        注:当执行一个程序进程的有效ID际用户ID,组ID通
ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的 有效 ID设置 为文件所 ID ( s t _ u id ) 件模式字 可以设置另 位, 将执 有效 ID 设置 为文 所有者 I D ( st _g id )。 在文件模式字中的这两位被称为设置用户ID(set -user-ID)位 设置组ID(set-group-ID)位

        例如,若文件所有者是超级用户,而且设置了该文件的设置用户ID位,那么当该程序文件由一个进程执行时,该进程具有超级用户权限。

        例如,UNLX系统程序passwd(1)允许任一用户改变其口令,该程序是一个设置用户D程序。因为该程序应能将用户的新口令写入口令文件中(一般是/etc/passwd或/etc/shadow),而只有超级用户才具有对该文件的写权限,所以需要使用设置用户ID功能。因为运行设置用户ID程序的进程通常会得到额外的权限,所以编写这种程序时要特别谨慎。

4.4 文件访问权限

 st_mode值也包含了对文件的访问权限位。

每个文件有9个访问权限位,可将它们分成3类:

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

        其实很好理解和记忆R代表read、W代表wirte、X代表执行、USR代表USER(也就是用户)、GRP代表group(也就是用户组的概念)。对照上表记忆即可。

以下讨论几个规则:

  • 我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限。这就是为什么对于目录其执行权限位常被称为搜索位的原因。例如,为了打开文件/usr/include/stdio.h,需要对目录/、/usr和/usr/include具有执行权限。然后,需要具有对文件本身的适当权限,这取决于以何种模式打开它(只读、读-写等)。在当前目录是/usr/include的情况下打开stdio.h文件与打开./stdio.h作用相同。如果PATH环境变量(8.10节将对其进行说明)指定了一个我们不具有执行权限的目录,那么shell绝不会在该目录下找到可执行文件。
  • 对于一个文件的读权限决定了我们能否打开它进行读操作,与open函数的O_RDONLY和O_RDWR标志相关
  • 对于一个文件的读权限决定了我们能否打开它进行读操作,与open函数的O_RDONLY和O_RDWR标志相关
  • 在open中对一个文件指定O_TRUNC标志,需要拥有该文件的写权限
  • 在open中对一个文件指定O_TRUNC标志,需要拥有该文件的写权限
  • 在open中对一个文件指定O_TRUNC标志,需要拥有该文件的写权限

  • 如果用exec函数族执行某一文件,需要对该文件的执行权限,且该文件必须是普通文件类型。

文件访问权限测试流程: (按顺序执行以下四步)

        进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,而这种测试可能涉及文件的所有者(st_uid和st_gid)、进程的有效ID(有效用 户D和有效组ID)以及进程的附属组ID(若支持的话)。两个所有者ID是文 件的性质,而两 个有效ID和附属组ID则是进程的性质。
        1.若进程的有效用户ID是0(超级用户), 则允许访问。这 给予了超级用户对整个文件系统进行处理的最充分的自由。
        2.若进程的有效用户ID等于文件的所有者ID(也就是进程拥有此文件),那么如果所有者适当的访问权限位被设置,则允许访问;否则拒绝访问。适当的 访问权限位指的是,若进程为读而打开该文件,则用户读位应为1;若进程为写而 打开该文件,则 用户写位应为1;若进程将执行该文件,则用户 执行 位应为1。
        3.若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限位 ,则允 访 则拒绝 访 问。
        4.若其他用户适当的访问权限位被 设置,则允 许访问;否则拒绝访问。
对于以上四点我的理解是:
        超级用户有对文件系统为所欲为的权限以及能力——(比如这个文件没有用户读权限但是我想读,而我又是超级用户,改造他的权限就完啦!)。如果你不是超级用户,就要看这个文件权限如何了,如果用户可以读写执行这个文件,那么文件的对应读写执行权限位应该置为1,对应的,咱们前文提到过的组和其他用户也是这种处理方式。

4.5 新文件和目录的所有权

 新目录的所有权规则与新文件所有权规则相同

  • 新文件的用户ID设置为进程的有效用户ID
  • 新文件的组ID设置为进程的有效组ID或该文件所在目录的组ID。

 4.6 函数access和faccessat

        这两个函数说到底就是测试文件的属性以及权限位,把文件描述符放进去,测就完了,看下面操作你就懂了。

两个函数的具体形式:

#include <unistd.h>
int access(const char *pathname,int mode);
int faccessat(int fd, const char *pathname,int mode,int flag);
                        //两个函数的返回值:若成功,返回0:若出错,返回-1 
        faccessat函数与access函数在下面两种 情况下是相同的 :一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。否则,faccessat计算相对于打开目录(由fd参数指向)的pathname(flag参数实际上就是测的东西是实际组ID实际用户ID 和有效组ID有效用户ID与否,设为AT_EACCESS则是有效用户ID和有效组ID)。
        mode参数的话如下表所示:
mode说明
R_OK测试读权限
W_OK测试写权限
X_OK测试实行权限
F_OK判断文件存在与否

 4.7 函数umask

#include <ays/stat.h>
mode_t umask(mode_t cmask);
            //返回值:之前的文件模式创建屏蔽字

         这个函数的用法实际上就是创建屏蔽字,通俗一点来说,我创建文件前设一个umask函数在前面,在函数里面传入我要屏蔽的权限位(比如用户读、组写、其他堵写等,以此类推)。然后我再去设置这个文件对应位的读写执行权限就不行了。讲理论太枯燥了,上实战(只写部分代码,博主太累了o.O):

    umask(0); // 没有屏蔽任何权限位
    creat("foo",RWRWRW); // 我建一个文件为用户读写组读写其他用户读写
    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); // 屏蔽掉了组读和组写权限位以及其他用户读写权限位
    creat("bar.txt",RWRWRW); // 我再建一个文件作对比(注意其中四个权限位被屏蔽掉了)

看结果: 

$ ./a.out
$ ls -l foo bar
-rw------- 1 bar         0 Dec 7 21:20 bar
-rw-rw-rw- 1 foo         0 Dec 7 21:20 foo

        这回清楚了吧,我把bar文件的那几个位屏蔽掉之后,天王老子来设置也没有用!所以这函数还是很妙的。对了,友友们还可以根据数字来设置屏蔽位哟(差点忘了o.O),例如这样umask(0400)我把用户读给你屏了。

屏蔽位含义
0400用户读
0200用户写
0100用户执行
0040组读
0020组写
0010组执行
0004其他读
0002其他写
0001其他执行

4.8 函数chmod、fchmod和fchmodat

这三个函数更改我们现有文件的访问权限。

#include<sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
                                        //3个函数返回值:若成功,返回0:若出错。返回-1

         chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。 chmodat函数与chmod函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWDpathname参数为相对路径。否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname。flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接

        为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必
须具有超级用户权限。
参数mode是下中所示常量的按位或。(粘着位在后面会讲到的,先忍他一阵)
mode说明

S_ISUID
S_ISGID

S_ISVTX

执行时设置用户ID位
执行时设置组ID位

保存正文(粘着位)

S_IRWXU
S_IRUSR
S_IWUSR
S_IXUSR
所有者读写执行权限
所有者读权限
所有者写权限
所有者执行权限
S_IRWXG
S_IRGRP
S_IWGRP
S_IXGRP
组读写执行权限
组读权限
组写权限
组执行权限
S_IRWXO
S_IROTH
S_IWOTH
S_IXOTH
其他读写执行权限
其他读权限
其他写权限
其他执行权限

         注意:chmod函数更新的只是i节点(第三章的内容,赶紧回去看看)最近一次被更改的时间。按系统默认方式,ls -l列出的是最后修改文件内容的时间。

4.9 粘着位(nian还是zhan随你们吧o.O)

        S_ISVTX位被称为粘着位(sticky bit)。如果一个可执行程序文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区(程序的正文部分是机器指令)。这使得下次执行该程序时能较快地将其装载入内存。现今较新的UNLX系统大多数都配置了虚拟存储系统以及快速文件系统,所以不再需使用这术。

        但是!!!现今的系统扩展了粘着位的使用范围,Single UNLX Specificaton允许针对目录设置粘着位。如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:

  • 拥有
  • 拥有此目录
  • 是超级用户。
        目录/tmp和/var/tmp是设置粘着位的典 型候选者—— 任何用户都可在这两个目录中创 一用户 ( 其他 ) 录的 常都 、写 执行 。但是用户不应能删除或重命名属于其他人的文 件,为此在这 两个目录的文件模式中都设置了粘着位。

4.10 函数chown、fchown、fchownat 和 lchown

        下面几个chown用于更ID和ID如果个参数ownergroup的任意一个是-1,则对应的ID不变。

#include <unistd.h>
int chown(const char *pathmame,uid_t owmer,gid_t group);
int fchown(int fd,uid_t owner,gid_t growp);
int fchownat(int fd,const char *pathmame,uid_t owmer,gid_t group,int flag);
int lchown(const char *pathmame,uid_t owmer,gid_t grop);
                            //4个函数的返回值:若成功,返回0:若出错,返回-1

         除了所引用的文件是符号链接以外,这4个函数的操作类似。在符号链接情况下,1chown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者。

        fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号链接的所有者。

        fchownat函数与chown或者1chown函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。在这两种情况下,如果flag参数中设置了AT_SYMLINK_NOFOLLOW标志,fchownat与lchown行为相同,如果flag参数中清除了AT_SYMLINK_NOFOLLOW标志,则fchownat与chown行为相同。如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat函数计算相对于打开目录的pathname。

        _POSIX_CHOWN_RESTRICTED对指定的文则(其实也就是我们现在普遍用的系统里面的规则)

        1. 只有超级用户进程能更改该件的ID

        2. 如果进程拥有此文件(其有效用户ID等于该文件的用户ID),参数owner等于-1或文件的用户ID,参数group程的有ID或进ID之一个非超级用户进程可以更改该文件的组ID。

 4.11 文件长度(涉及到了文件空洞)

        4.11.1 文件长度

         stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。

  •         对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示。对于目录,文件长度通常是一个数(如16或512)的整倍数。
  •         对于符号链接,文件长度是在文件名中的实际字节数。例如,在下面的例子中,文件长度7就是路径名usr/lib的长度:
lrwxrwxrwx 1 root         7 Sep 2507:14 lib -> usr/lib

         现今,大多数现代的UNLX系统提供字段st_blksizest_blocks。其中,第一个是对文件I/O较合适的块长度,第二个是所分配的实际512字节块块数。回忆3.9节,其中提到了当我们将st_blksize用于读操作时,读一个文件所需的时间量最少。为了提高效率,标准I/O库(我们将在第5章中说明)也试图一次读、写st_blksize个字节。

        4.11.2 文件空洞

        空洞是由所设置的 偏移 量超过 文件 尾端 某些数据后 造成的 例子 考虑 下列
$ ls -l core
-rw-r--r-- 1 sar         8483248 Nov 18 12:18 core
$ du -s core
        文件 c o r e 度稍稍超 8 MB , d u 该文 使 用的 盘空 量是 2 7 2 5 1 2字节块(即139264字节)。很明显,此文件中有很多空洞。
注意:

        在很多BSD类系统上,du命今报告的是1024字节块的块数,Solaris报告的是512字节块的块数Linux决于POSIXLY_CORRECT了该环du告的1024字节设置该境变量时du令报告的是512字节块的块数。

         对于没有写过的字节位置,read函数读到的字节是0。如果执行下面的命令,可以看出正常的I/O操作读整个文件长度:

$ wc -c core 
8483248 core
        //带-c选项的wc(1)命令计算文件中的字符数(字节)。

         如果使用实用程序(如cat(1))复制这个文件,那么所有这些空洞都会被填满,其中所有实际数据字节皆填写为0。

$ cat core> core.copy 
$ ls -1 core*
-rw-r--r--1 sar     8483248 Nov 1812;18 core
-rw-rw-r--1 sar     8483248 Nov 1812:27 core.copy
$ du -s core*


//结果
272     core
16592   core.copy
        从中可见,新文件所用的实际字节数是8495104(512×16592)。此长度与ls命令报告的长度不同,其原因是,文件系统使用了若干块以存放指向实际数据块的各个指针。

4.12 文件截断 

        有时我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截断为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点。为了截断文件可以调用函数truncate和ftruncate。

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

                //两个函数的返回值:若成功,返回0:若出错,返回-1

         这两个函数将一个现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的数据就不再能访问。如果以前的长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

4.13 文件系统(嫌累可以滑到本小结末看文件系统的总结)

        我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统(见图4-13)。i(也可以叫做inode节点)节点是固定长度的记录项,它包含有关文件的大部分信息。

  • 在下图指向ii中都指向该 i节点的目录数。接计数减少至0时,才可删除该文(就是可以释该文件占的数据)。 就是为什么 “解除对一个文件的链接”操作不总是意味“释放该文件占用的原因是为除一unlink而不是 delete 因。stat构中链接计数包含在st_nlink员中,其基本系统nlin k_t接类链接
  • 另外一种链接类型称为符号链接(symbolic link)。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。在下面的例子中,目录项中的文件名是3个字符的字符串lib,而在该文件中包含了7个字节的数据usr/lib:
lrwxrwxrwx l root        7 Sep 2507:14 lib -> urs/lib
  • 该i节点中的文件类型是S_IFLNK,于是系统知道这是一个符号链接。
  • 节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件据块的指针等。stat结构中的大多数信息都取自i节点。只有两项重要数据存放在目录项中:文件名和i节点编号。
  • 因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点。这就是为什么ln(1)命令(构造一个指向一个现有文件的新目录项)不能跨越文件系统的原因。我们将在下一节说明link函数。
  • 当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需 构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数不会改变。例如,为将文件/usr/lib/foo重命名为/usr/foo,如果目录/usr/lib和/usr在同一文件系统中,则文件foo的内容无需移动。这就是mv(1)命令的通常操作方式。

目录文件的链接计数字段:

假定我们在中构造了一个新

$ mkdir testdir

         编号为2549的i节点,其类型字段表示它是一个目录,链接计数为2。任何一个叶目录(不包含任何其他目录的目录)的链接计数总是2,数值2来自于命名该目录(testdir)的目录项以及在.号为1267的i节其类示它是目录链接数大或等于3。它3的原3项指向它命名录项(图4-15中没有表示出来),第二个是在该目录中的.项,第三个是在其子目录testdir中的..项。注意,在父目录中的每一个子目录都使该父目录的链接计数增加1。

小结(个人理解):其实文件系统讲的就是文件磁盘等一些关系,像我们windows计算机有C盘、D盘、E盘,这就是一个一个的磁盘分区。分区里面有什么,肯定有文件啊,数据啊,那么就有i节点来指向文件的相关信息,什么长度啊、信息啊、能不能读写啊都在i节点里面。数据理所当然就应该在数据块啦。这时候问题来了,如果我把一个文件复制一个一模一样的到这个磁盘下,会不会重新分配数据块给这个文件包含的数据呢?理所当然不会,这样多浪费啊。那么我要用这个文件的数据怎么办呢,i节点链接计数+1不就行了。也就是说有很多个链接指向我这个文件的i节点,都可以用这里面的数据,其实也就类似于副本,这样说就能理解了吧,后文也会提到符号链接,这个符号链接就相当于是我们电脑的快捷方式。你如果想彻底删除文件相关数据块,就得把所有副本和源文件删除掉,i节点链接数不就清零了嘛。其实日常操作电脑的时候我们可以发现,在同一个磁盘分区下,新建一个副本会很快,但是当我们把c盘分区的文件复制到d盘分区,用的时间真的长太多,这就是文件系统里面的区别。

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

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

#include <unistd.h>
int link(const char *existingpath,const char *newpath);
int linkat(int efd,const char *existingpath,int nd,const char *newpath,int flag);
                    //两个函数的返回值:若成功,返回0:若出错,返回-1

         这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。

        对于linkat函数,现有文件是通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指况下如果两个路个是相对路径那么它需要通过相对于对应的文件描述符进行计算。如果两个文件描述符中的任一个设置为AT_FDCWD,那么相应的路径名(路径)过相对于当前进行计名是绝对径,相应的文件描述符参数就会被忽略(和大部分函数大同小异了)。

        如果在flag数中ATSYMLINK_FOLLOW标志,就标的链接。如果这个标则创个指向符号链接本身的链接。

        创建新增加链接计操作。

为了删除一个现有的目录项,可以调用unlink函数。
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd,const char *pathmame,int fag);
        //两个函数的返回值:若成功,返回0:若出错,返回-1
        这两个函数删除目录项,并将由pathname所引用文件的链接计数减1。如果对该文件还有其他链接,则仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。

         只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程个数;如果这个计数达到0,内核再去检查其链接计数;如果计数也是0,那么就删除该文件的内容(其实也就类似于我们平常打开了一个文件,然后又要去删除他,删不掉,把文件关掉就行了)。

        如果pathname参数unlinkat相对于由fd文符参数代表的录的路径名如果fd数设AT_FDCWD,过相对于程的目录来计算路径名。如果pathname参数是绝对路径名,那么fd参数被忽略(和大多数函数一样)。

         fag参数给出了一种方法,使调用进程可以改变unlinkat函数的默认行为。AT_REMOVEDIR标志被设置时,unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除,unlinkat与unlink执行同样的操作。

重点来了!!!

        unlink 命令在同一时间只能删除一个文件或链接,而 rm 命令可以删除多个;unlink 命令不能删除目录,而 rm 命令能删除目录。

        rm 命令在执行的时候,首先会安全检查,如果你没有文件的写权限,那么系统会要求你给出写权限(sudo或者切换至管理员用户),或者使用强制删除选项 -f;而 unlink 则不会进行安全检查,直接删除文件。

        在某些情况下,相比于 rm,你可能更喜欢使用 unlink。比如你希望强制删除一个文件,而不考虑安全或者权限问题;或者如果删除失败(比如文件不存在)的话你希望能看到报错信息,这种情况下就可以使用 unlink。因为使用 rm -f 强制删除文件,如果文件不存在的话,不会显示任何错误信息。

4.15 函数rename和renameat

 用于文件或目录重命名

其实这个博主讲的挺好的,可以去他那看看这两个函数的具体用法。

C语言rename()函数:重命名文件或目录_c语言 rename_GoRustNeverStop的博客-CSDN博客

#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd,const char *oldname,int newfd,const char *newname);
                //两个函数的返回值:若成功,返回0;若出错,返回-1
        根据oldname是指文件、目录还是符号链接,有几种情况需要加以说明。我们也必须说明如果newname已经存在时将会发生什么。
  • 如果oldname不是录,那么为该或符链接命名情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname重命名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。
  • 如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有.和..项)。如果newname存在(而且是一个空目录),则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr/foo重命名为/usr/foo/testdir,因为旧名字(/usr/foo)是新名字的路径前缀,因而不能将其删除。
  • 如若oldname或newname引用符号链接,则处理的是符号链接本身,而不是它所引用的文件。
  • 不能对.和..重命名。更确切地说,.和..都不能出现在oldname和newname的最后部分。
  • 作为如果oldname和newmame引用同一文

4.16 符号链接(快捷方式来了)

        符号 链接 的间接 指针, 与上 的硬 链接有所 硬链 接指向文件的 i 节点 的原因是了避硬链限制。
  • 硬链接通要求链和文件位
  • 超级用户才能创建指向的硬链接(在底件系支持况下)
        对符 链接 象并 任何文 件系 统限制 户都 建指向 符号链 符号 将一 整个 目录 结构移 另一 个位置

有个图有点重要,可以看看。 

        图4-17的一个例外是,同时用○_CREAT和o_EXCL两者调用open函数。在此情况下,若路径名引用符号链接,open将出错返回,errno设置为EEXIST。这种处理方式的意图是堵塞一个安全性漏洞,以防止具有特 权的进程 被诱骗写错误的文件。

 4.17 创建和读取符号链接

可以用symlink或symlinkat函数创建一个符号链接。实际上ln -s也可以创建符号链接,但是单独的ln就只能创建硬链接了。

关于符号链接(软链接)和硬链接这个博主写的不错,较为详细,看完记得回来!

什么是软链接、硬链接_king config的博客-CSDN博客

#include <unistd.h>
int symlink(const char *actuapath,const char *sympath);
int symlinkat(const char *acfualpath,int fd,const char *sympath);
                        //两个函数的返回值:若成功,返回0:若出错,返回-1

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

        因为open函数跟随符号链接,所以需要有一种方法打开该链接本身,并读该链接中的名字。readlink和readlink at函数 提供了这种功能。
#include <unistd.h>
ssize_t readlink(const char *restrict pathname,char *restrict buf,size_t bufsize)
ssize_t readlinkat(int fd,const char* restrict pathmame,char *restrict buf,size_t bufsie);
                //两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1
        两个函数组合了open、read 和close的所 有操作。如果函 数成功 执行,则返回读入buf的字节数。在buf中返回的符号链接的内容不以null字节终止。

4.18 文件的时间

        stat结构体中的st_atim、st_mtim、st_ctim成员即为文件时间信息

        每个文件性所保存的实际精度依系统的实现。 对于录在,纳秒这被填0精度高于秒级被转换成纳秒并记录在纳秒这个字段中。

字段说明例子ls选项
s t _ a t i m
文件数据的最后访间时间
r ea d
- u
st_mtim
文件数据的最后修改时间
w r i t e
默认
st _ c t i m
i节点状态的最后更改时间
c h m o d 、c h o w n
- C
        注意,修改时间(st_ntim)和状态更改时间 (st_ctim)之 间的区别。修改时间是文件内容最后一次被修改的时间。状态更改时间是该文件的i节点最后一次被修改的时间。如更改文件的访问权限、更改用户ID、更改链接数等,但它们并没有更改文件的实际内容。因为i节点中的所有信息都是与文件的实际内容分开存放的, 所以,除了要记录文件数据修改时间以外,还需要记录状态更改时间,也就是更改i节点中信息的时间。

        ls命令按这3个时间值中的一个排序进行显示。系统默认(用-1或-t选项调用时)是按文件的修改间的-u项使1s访问时间-c项则使其按状态更改排序

        图4-20列出了我们已说明过的各种函数对这3个时间的作用。目录是包含目录项(文件名和相关的i节点编号)的文件,增加、删除或修改目录项会影响到它所在目录相关的3个时间。这就是在图4-20中包含两列的原因,其中一列是与该文件(或目录)相关的3个时间,另一列是与所引用的文件(或目录)的父目录相关的3个时间。例如,创建一个新文件影响到包含此新文件的目录,也影响该新文件的i节点。但是,读或写一个文件只影响该文件的i节点响(我觉得读文件之所以影响到i节点可能是因为文件信息里面的访问时间变了,但是下图并没有显示出来,所以这个地方是存在歧义的)。

4.19 函数futimens、utimensat和utimes 

        futimens和utimensat函数可以指定纳秒级精度的时间戳。用到的数据结构是与stat函数族相同 的timespec结构。

 

#include <sys/stat.h>
int futimens(int fd,const struct timespec times[2]);
int utimensat(intjd,const char *path,const struct timespec times[2],int flag);
                //两个函数返回值:若成功,返回0:若出错,返回-1

        这两个函数的times数组参数的第一个元素包含访问时间,第二元素包含修改时间。这两个时间值是日历时间,如1.10节所述,这是自特定时间(1970年1月1日00:00:00)以来所经过的秒数。不足秒的部分用纳秒表示(感觉看到这就行了,后面的修改时间遇到问题再来差也是一样的)

间戳4种方进行指定
  1. 如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间。
  2. 如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_NOW,应的间戳就设时间略相tv_sec
  3. 如果times参数指向两个timespec结构的数组,任一组元素的tv_nsec字段的值为UTIME_OMIT,相应的时间戳保忽略相应的tv_sec字段
  4. 如果times参数指向两个timespec结构的数组,且tv_nsec字段的值为既不是UTIME_NOW也不是UTIME_OMIT,在这种情况下,相应的时间戳设置为相应的tv_sec和tv_nsec字段

数所取决times参数值(相当于看你的权限如何)。 

  • 如果times指针,或者任一tv_nsec字段设UTIME_NOW,则进程ID 必须件的所有者ID;对该文件必须具个超
  • 如果times是非空指针,并且任一tv_nsec字段的值既不是UTIME_NOW也不是 UTIME_OMIT,则进程的有ID该文所有者ID,或者进程必须是个 超级用户进程。对文件只具有写权限是不够的。
  • 如果times是非空指针,并且两个tv_nsec字段的值都为UTIME_OMIT,就不执行任何的 权限检查。
        fu t i me n s 的时间, u t i m e ns a t 数提供 种使 用文 名更改文件时 间的方法。
        utimensat的fag参数可用于进一步修改默认行为。如果设置了AT_SYMLINK_NOFOLLow标志, 则符 链接本身 的时 间就会被 ( 如果 指向符 链接 ) 认的 行为 跟随 号链接,并把文件 的时间改成符号链接的时间。
也可以通过utimes函数设置访问时间和修改时间

 

#include <sys/time.h>
int utimes(const char *pathname,const struct timeval times[2]);
                    //函数返回值:若成功,返回0;若出错,返回-1

         utimes函数对路径名进行操作。times参数是指向包含两个时间戳(访问时间和修改时间)元素的指针时间戳是用微妙

struct timeval {
    time_t tv_sec;/* seconds */
    long tv_usec;/* microseconds */
};
        注意,我们不能对状态更改时间st_ctim(i 节点最近被修改 的时间)指定一个值,因为调用utimes函数时, 此字段 会被自动更新。

4.20 函数mkdir、mkdirat和rmdir

mkdir和mkdiratrmdir删除目录 

#include <sys/stat.h>
int mkdir(const char *pathname,mode_t mode);
int mkdirat(int fd,const char *pathname,mode_t mode);
            //两个函数返回值:若成功,返回0;若出错,返回-1

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

        ​​​​​​​常见的错误是指定目录文件的读写权限,但是没有指定执行权限。对于目录文件来说,至少应该设置执行权限,以允许访问该目录中的文件,在BSD系统的组ID是父目。Solaris 10Linux 3.2.0使父目ID

用rmdir函数可以删除一个空目录。空目录是只包含.和..这两项的目录。 

#include <unistd.h>
int rmdir(const char *pathname);
                //返回值:若成功,返回0;若出错,返回-1
        如果调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及.和..项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。(即使另一些进程打开该 目录,它们在此目录下也不能执行其他操作。这样处理的原因是,为了使rmdir函数成功执行,该目录必须是空的。)

4.21 读目录

        对于各个函数的解释就放在代码段里面了,这里不再做过多解释(目录流指代一大串目录,例如/temp/dev就是一个目录流)。

        对某个目录具有访问权限的任一用户都可以读该目录,但是,为了防止文件系统产生混乱,只有内核才 目录。一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件,它们并不表示能否写目录本身。
#include <dirent.h>


/*打开目录文件,返回一个指向该目录流的指针,该流被定位在目录的第一个条目上。目录中的每一个条目都指向该目录中的一个文件,目录中还有两个特殊的目录条目即".“和”…"分别是该目录文件和上一级目录文件。失败返回NULL*/
DIR *opendir(const char *parhname);
DIR *fdopendir(int fd);
        //两个函数返回值:若成功,返回指针:若出错,返回NULL 




//读取目录项,该结构代表了目录流中的下一个目录条目
struct dirent *readdir(DIR *dp);
        //返回值:若成功,返回指针;若在目录尾或出错,返回NULL

// 关闭目录文件,关闭参数dir所指的目录流
int closedir(DIR *dp);
        //返回值:若成功,返回0;若出错,返回-1

//设置目录流读取位置
void rewinddir(DIR *dp);//用来设置参数dir目录流读取位置为原来开头的读取位置

long telldir(DIR *dp);//函数的返回值记录着一个目录流的当前位置。此返回值代表距离目录文件开头的偏移量

void seekdir(DIR *dp,long loc);//用来设置参数dir目录流读取位置,在调用readdir()时便从此新位置开始读取。参数offset代表距离目录文件开头的偏移量
        定义在头文件<dirent.h>中的dirent结构与实现有关。实现对此结构所做的定义至少包含下列两个成
ino_t d_ino;                /* i-node number */
char d_name[];         /* nuil-terminated filename */
        注意,d_name项的大小并没有指定,但必须保证它能包含至少NAME_MAX个字节(不包含终止null字节,回忆图2-15)。
        DIR结构是一个内部结构,上述7个函数用这个内部结构保存当前正在被读的目录的有 关信息。其作用类似于FILE结构。FILE结构由标准I/0库维护,我们将在第5章中对它进行说明。

重点叕来了!!! 

        由opendirfdopendir返回的指DIR的指针由另5数使opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项。DIR结构由fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。注意,目中各的顺序与它们通按字

4.22 函数chdir、fchdir和getcwd

进程调用chdir或fchdir函数可以更改当前工作目录。 假如在目录A中运行了目录B中的程序,那么进程B的工作目录是目录A。

        每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)。当用户登录到UNLX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第6个字段——用户的起始目录(home directory)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。

#include <unistd.h>
int chdir(const char"pathname");
int fchdir(int fd); 
//两个函数的返回值:若成功,返回0:若出错,返回-1

在这两个函数中,分别用pathname或打开文件描述符来指定新的当前工作目录。 

        因为当前工作目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程(我们将在第8章更详细地说明进程之间的关系)。这就意味着下列的程序并不会产生我们可能希望得到的结果。
#include "apue.h"
int main(void)
{
    if(chdir("/tmp")<0)
    {
        err_sys("chdir failed");
    }
    printf("chdir to /tmp succeeded\n");
    exit(0);
}

 如果编译程序,并且调用其可执行目标代码文件mycd,则可以得到下列结果:

5 pwd
/usr/lib
$ mycd
chdir to /tmp succeeded
$ pwd
/usr/lib

        从中可以看出,执行mycd命令的shell的当前工作目录并没有改变,这是shell执行程序工作方式的一个副作用。每个程序运行在独立的进程中,shell的当前工作目录并不会随着程序调用chdir而改变。由此可见,为了改变shell进程自己的工作目录,shell应当直接调用chdir函数,为此,cd命令内建在shell中。

通过getcwd函数获取调用进程的当前工作目录

        注意,它从当前目录(.)..其上目录读其目录项,直到该目录项中的i节点编号与工作目录i节点编号相同,这样地就找到了其对应的文件名。按照这逐层上移样就得到工作的绝对路

include <unistd.h>
char *getcwd(char +buf,size_t size);
//返回值:若成功,返回buf;若出错,返回NULL
        必须向此函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度size(以字节为单位)该缓冲名再加个终止null错。

 4.23 设备特殊文件

         st_devst_rdev两个字段起混18.9节我们ttyname需要使用这个字段规则很简单

  • 每个文件系在的设备由其。设备号所基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为与其通信的外设板;一个磁个文件系统。在同一磁盘驱动器上的各文件系统通常具有相同的主设备号,但是次设备号却不同。
  • 系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文 件名以及与其对应的i节点。
  • 我们通常可以使用两个宏:major和minor来访问主、次设备号,大多数实现都定义这两个宏。这就意味着我们无需关心这两个数是如何存放在dev_t对象中的。
  • 只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号。

 一般major标识主设备号,minor标识次设备号。

4.24 文件权限访问小结(写不动了,直接上截图吧)

 

总结 (第四章 文件和目录 完结撒花)

        学到这,大家应该对文件系统也有个全面的了解了。文件系统长啥样、怎么创建、怎么去使用、阅读记录都讲明白了,在之后的学习过程中,这些函数的使用将非常重要!内容围绕stat函数,详细介绍了stat结构中的每一个成员。这使我们对UNLX文件和目录的各个属性都有所了解。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值