第4章 文件和目录

  1. 文件类型
    (1)普通文件(regular file)。这是最常用的文件类型,这种文件包含了某种形式的数据。至于这种数据是文本还是二进制数据,对于UNIX内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。一个值得注意的例外是二进制可执行文件。为了执行程序,内核必须理解其格式。所有二进制可执行文件都遵循一种标准化的格式,这种格式使内核能够确定程序文本和数据的加载位置。
    (2)目录文件(directory file)。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。进程必须使用本章介绍的函数才能更改目录。
    (3)块特殊文件(block special file)。这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。注意,FreeBSD不再支持块特殊文件。对设备的所有访问需要通过字符特殊文件进行。
    (4)字符特殊文件(character special file)。这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
    (5)FIFO。这种类型的文件用于进程间通信,有时也称为命名管道(named pipe)。
    (6)套接字(socket)。这种类型的文件用于进程间的网络通信。套接字也可用于在一台宿主机上进程之间的非网络通信。第16章将用套接字进行进程间的通信。
    (7)符号链接(symbolic link)。这种类型的文件指向另一个文件。
    图4-4显示了在一个单用户工作站Linux系统中的统计值和百分比。这些数据是由4.22节中的程序得到的。

  2. 文件访问权限
    每个文件有9个访问权限位,可将它们分成3类,见图4-6。


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

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

  5. 函数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 nfd, const char *newpath, int flag);     
     
    两个函数的返回值:若成功,返回0;若出错,返回-1 
    这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
    对于linkat函数,现有文件是通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指定的。默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算。如果两个文件描述符中的任一个设置为AT_FDCWD,那么相应的路径名(如果它是相对路径)就通过相对于当前目录进行计算。如果任一路径名是绝对路径,相应的文件描述符参数就会被忽略。
    当现有文件是符号链接时,由flag参数来控制linkat函数是创建指向现有符号链接的链接还是创建指向现有符号链接所指向的文件的链接。如果在flag参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。如果这个标志被清除了,则创建一个指向符号链接本身的链接。
    为了删除一个现有的目录项,可以调用unlink函数。
    #include <unistd.h>     
     
    int unlink(const char *pathname);     
    int unlinkat(int fd, const char *pathname, int flag);     
     
    两个函数的返回值:若成功,返回0;若出错,返回-1   
    这两个函数删除目录项,并将由pathname所引用文件的链接计数减1。如果对该文件还有其他链接,则仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。
    unlink不会删除文件
    我们也可以用remove函数解除对一个文件或目录的链接。对于文件,remove的功能与unlink相同。对于目录,remove的功能与rmdir相同。
    #include <stdio.h>     
     
    int remove(const char *pathname);     
     
    返回值:若成功,返回0;若出错,返回-1 
    ISO C指定remove函数删除一个文件,这更改了UNIX历来使用的名字unlink,其原因是实现C标准的大多数非UNIX系统并不支持文件链接。
  6.  函数rename和renameat
    文件或目录可以用rename函数或者renameat函数进行重命名。
    #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 
    (1)如果oldname指的是一个文件而不是目录,那么为该文件或符号链接重命名。在这种情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname重命名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。
    (2)如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有.和..项)。如果newname存在(而且是一个空目录),则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr/foo重命名为/usr/foo/testdir,因为旧名字(/usr/foo)是新名字的路径前缀,因而不能将其删除。
    (3)如若oldname或newname引用符号链接,则处理的是符号链接本身,而不是它所引用的文件。
    (4)不能对.和..重命名。更确切地说,.和..都不能出现在oldname和newname的最后部分。
    (5)作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。
    如若newname已经存在,则调用进程对它需要有写权限(如同删除情况一样)。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行权限。
    除了当oldname或newname指向相对路径名时,其他情况下renameat函数与rename函数功能相同。如果oldname参数指定了相对路径,就相对于oldfd参数引用的目录来计算oldname。类似地,如果newname指定了相对路径,就相对于newfd引用的目录来计算newname。oldfd或newfd参数(或两者)都能设置成AT_FDCWD,此时相对于当前目录来计算相应的路径名。
  7. 文件的时间
    对每个文件维护3个时间字段,它们的意义示于图4-19中。


  8. 函数mkdir、mkdirat和rmdir
    用mkdir和mkdirat函数创建目录,用rmdir函数删除目录。
    #include <sys/stat.h>     
     
    int mkdir(const char *pathname, mode_t mode);     
     
    int mkdirat(int fd, const char *pathname, mode_t mode);     
     
    两个函数返回值:若成功,返回0;若出错,返回-1 
    用rmdir函数可以删除一个空目录。空目录是只包含.和..这两项的目录。
    #include <unistd.h>     
     
    int rmdir(const char *pathname);     
     
    返回值:若成功,返回0;若出错,返回-1 
  9. 函数chdir、fchdir和getcwd
    进程调用chdir或fchdir函数可以更改当前工作目录。
    #include <unistd.h>    
     
    int chdir(const char *pathname);    
     
    int fchdir(int fd);    
     
    两个函数的返回值:若成功,返回0;若出错,返回-1 

    如果编译图4-23程序,并且调用其可执行目标代码文件mycd,则可以得到下列结果:
    $ pwd    
    /usr/lib    
    $ mycd    
    chdir to /tmp succeeded    
    $ pwd    
    /usr/lib 
    从中可以看出,执行mycd命令的shell的当前工作目录并没有改变,这是shell执行程序工作方式的一个副作用。每个程序运行在独立的进程中,shell的当前工作目录并不会随着程序调用chdir而改变。由此可见,为了改变shell进程自己的工作目录,shell应当直接调用chdir函数,为此,cd命令内建在shell中。
    因为内核必须维护当前工作目录的信息,所以我们应能获取其当前值。遗憾的是,内核为每个进程只保存指向该目录v节点的指针等目录本身的信息,并不保存该目录的完整路径名。
    Linux内核可以确定完整路径名。完整路径名的各个组成部分分布在mount表和dcache表中,然后进行重新组装,比如在读取/proc/self/cwd符号链接时。
    我们需要一个函数,它从当前工作目录(.)开始,用..找到其上一级目录,然后读其目录项,直到该目录项中的i节点编号与工作目录i节点编号相同,这样地就找到了其对应的文件名。按照这种方法,逐层上移,直到遇到根,这样就得到了当前工作目录完整的绝对路径名。很幸运,函数getcwd就提供了这种功能。
    #include <unistd.h>    
     
    char *getcwd(char *buf, size_t size);    
     
    返回值:若成功,返回buf;若出错,返回NULL 
    必须向此函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度size(以字节为单位)。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错
    图4-24的程序将工作目录更改至一个指定的目录,然后调用getcwd,最后打印该工作目录。如果运行该程序,则可得
    $ ./a.out    
    cwd = /var/spool/uucppublic    
    $ ls -l /usr/spool    
    lrwxrwxrwx 1 root 12 Jan 31 07:57 /usr/spool -> ../var/spool 

    注意,chdir跟随符号链接,但是当getcwd沿目录树上溯遇到/var/spool目录时,它并不了解该目录由符号链接/usr/spool所指向。这是符号链接的一种特性。
    当一个应用程序需要在文件系统中返回到它工作的出发点时,getcwd函数是有用的。在更换工作目录之前,我们可以调用getcwd函数先将其保存起来。在完成了处理后,就可将所保存的原工作目录路径名作为调用参数传送给chdir,这样就返回到了文件系统中的出发点。
    fchdir函数向我们提供了一种完成此任务的便捷方法。在更换到文件系统中的不同位置前,无需调用getcwd函数,而是使用open打开当前工作目录,然后保存其返回的文件描述符。当希望回到原工作目录时,只要简单地将该文件描述符传送给fchdir。
  10. 符号链接
    符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。引入符号链接的原因是为了避开硬链接的一些限制。
    硬链接通常要求链接和文件位于同一文件系统中。
    只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下)。
    对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置。
    当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是该函数是否跟随符号链接到达它所链接的文件。如若该函数具有处理符号链接的功能,则其路径名参数引用由符号链接指向的文件。否则,一个路径名参数引用链接本身,而不是由该链接指向的文件。图4-17列出了本章中所说明的各个函数是否处理符号链接。在图4-17中没有列出mkdir、mkinfo、mknod和rmdir这些函数,其原因是,当路径名是符号链接时,它们都出错返回。以文件描述符作为参数的一些函数(如fstat、fchmod等)也未在该图中列出,其原因是,对符号链接的处理是由返回文件描述符的函数(通常是open)进行的。chown是否跟随符号链接取决于实现。在所有现代的系统中,chown函数都跟随符号链接。

    样例:
    $ mkdir foo                         创建一个新目录    
    $ touch foo/a                       创建一个0长度的文件    
    $ ln -s ../foo foo/testdir      创建一个符号链接    
    $ ls -l foo    
    total 0    
    -rw-r----- 1 sar            0 Jan 22 00:16 a  
    lrwxrwxrwx 1 sar            6 Jan 22 00:16 testdir -> ../foo 
    这创建了一个目录foo,它包含了一个名为a的文件以及一个指向foo的符号链接。在图4-18中显示了这种结果,图中以圆表示目录,以正方形表示一个文件。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值