笔记来源:《unix环境编程》

看了第四章《文件和目录》,主要将如何查看和修改文件和目录的相关信息。讲了20多个系统调用。

2.stat、fstat 和lstat 函数:获取文件的相关信息
#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);
int lstat(const char *path,struct stat *buf);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

stat函数用来获取文件的数据信息。系统中命令就是利用这个函数实现的。根据文件的路径(path)或是文件描述符(fd)得到该文件的相信息,填到struct stat类型的结构体中。

struct stat 的字段:

struct stat {
      dev_t     st_dev;     /* ID of device containing file */
      ino_t     st_ino;     /* inode 号 */
      mode_t    st_mode;    /* 权限和文件类型,位图,权限位9位,类型3位,u+s 1位,g+s 1位,粘滞位(T位)1位。
  /位图是用一位或几位数据表示某种状态。许多要解决看似不可能的问题的面试题往往需要从位图着手。*/
      nlink_t   st_nlink;   /* 硬链接数量 */
      uid_t     st_uid;     /* 文件属主 ID */
      gid_t     st_gid;     /* 文件属组 ID */
      dev_t     st_rdev;    /* 设备号,只有设备文件才有 */
      off_t     st_size;    /* 总大小字节数,编译时需要指定宏 -D_FILE_OFFSET_BITS=64,否则读取大文件可能导致溢出 */
      blksize_t st_blksize; /* 文件系统块大小 */
      blkcnt_t  st_blocks;  /* 每个 block 占用 512B,则整个文件占用的 block 数量。这个值是文件真正意义上所占用的磁盘空间 */
     // 下面三个成员都是大整数,实际使用时需要先转换
     time_t    st_atime;   /* 文件最后访问时间戳 */
     time_t    st_mtime;   /* 文件最后修改时间戳 */
     time_t    st_ctime;   /* 文件亚数据最后修改时间戳 */
 }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

例:获取指定文件路径的stat结构体并打印。 

#include<sys/types.h>
#include<sys/stat.h>
#include<iostream>
using namespace std;
int main(int argc,char *argv[]){
    int i;
    struct stat buf;
    char *ptr="/etc";
    if(lstat(ptr,&buf)<0){
        cout<<"lstat error"<<endl;
        return 0;
    }
    std::cout << "-----------------------------" << std::endl;
    std::cout << "File type: ";
    switch (buf.st_mode & S_IFMT) {
        case S_IFBLK:
            std::cout << "block device";
            break;
        case S_IFCHR:
            std::cout << "character device";
            break;
        case S_IFDIR:
            std::cout << "directory";
            break;
        case S_IFIFO:
            std::cout << "FIFO (named pipe)";
            break;
        case S_IFLNK:
            std::cout << "symbolic link";
            break;
        case S_IFREG:
            std::cout << "regular file";
            break;
        case S_IFSOCK:
            std::cout << "socket";
            break;
        default:
            std::cout << "unknown";
    }
   
    cout << "File permissions (octal): " << std::oct << buf.st_mode << std::endl;
    cout << "Owner ID: " << buf.st_uid << std::endl;
    cout << "Group ID: " << buf.st_gid << std::endl;
    cout << "Size (bytes): " << buf.st_size << std::endl;
    // 时间戳相关字段(根据需要选择打印)
    cout << "Last access time: " << ctime(&buf.st_atime);  // 注意ctime返回字符串末尾包含换行符
    cout << "Last modification time: " << ctime(&buf.st_mtime);
    cout << "Last status change time: " << ctime(&buf.st_ctime);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
 3.文件类型

通过 struct stat 结构体的 st_mode 成员可以获得文件类型信息。Linux 系统中的文件共分为 7 种类型: 目录、字符设备文件、块设备文件、普通文件、符号链接文件、 套接字文件、管道文件。

普通文件:最常见且最基本的文件类型,包含任何形式的数据。无论是文本(如ASCII字符组成的纯文本文件)还是二进制数据(如图像、音频、视频、可执行程序等),对于操作系统内核来说并无本质区别。

目录文件:目录是一种特殊的文件,存储了其他文件和子目录的名字以及与它们相关的信息(如inode号码、权限等)。对一个目录具有读权限的任何进程都可以查看其中的内容(即列出目录下的文件名)。只有内核有权修改目录内容,包括添加、删除、重命名文件或目录等操作。目录文件是文件系统层次结构的基础,通过它们组织和管理文件系统的其余部分。

字符特殊文件:代表与系统中某些字符设备相关的接口,如串行端口、键盘、鼠标、终端等。字符设备通常以流的方式逐字节或逐字符地进行输入输出操作,不涉及块或缓冲的概念。

块特殊文件:这类文件对应于系统中的块设备,如硬盘、固态硬盘、光驱等。与字符设备不同,块设备通常以固定大小的块(如512字节、4KB等)进行数据传输,并且通常支持缓存和随机访问。块特殊文件允许应用程序对块设备进行读写操作,实现对磁盘数据的高效存取。

FIFO:命名管道,用于进程间通信的特殊文件类型。与无名管道(仅存在于内存中,用于直接相连的进程间通信)。FIFO具有一个持久的文件系统入口,使得不在同一进程树中的进程也能通过指定的路径名访问它来进行通信。FIFO遵循先进先出(FIFO)原则,发送到FIFO的数据将按顺序传递给从FIFO接收数据的进程。

套接口:套接口是另一种用于进程间通信的文件类型,特别适用于网络通信。它允许运行在同一台计算机上或跨越网络的不同进程之间进行双向数据交换。套接口不仅支持基于TCP/IP协议的网络连接,还可以用于同一台宿主机上的进程间的非网络通信(如Unix域套接字)

符号连接: 这种文件指向另一个文件。文件类型信息包含在 stat结构的 st_mode 成员中。 这些宏的参数都是s t a t 结构中的 st _mode 成员。


系统提供了七个带参的宏,直接判断文件的类型:

S_ISREG(m)        is it a regular file?                                        是否为普通文件

S_ISDIR(m)         directory?                                                     是否为目录

S_ISCHR(m)        character device?                                        是否为字符设备文件

S_ISBLK(m)        block device?                                                是否为块设备文件

S_ISFIFO(m)       FIFO (named pipe)?                                     是否为管道文件

S_ISLNK(m)        symbolic link? (Not in POSIX.1-1996.)         是否为符号链接文件

S_ISSOCK(m)     socket? (Not in POSIX.1-1996.)                    是否为套接字文件

4、设置用户ID和设置组 ID

每个文件都有一个所有者(st_uid)和一个组所有者(st_gid)。两个信息决定文件的访问权限控制。

  1. 实际用户ID(RUID):表示文件的实际拥有者身份,即登录系统的用户。这个值在登录时从口令文件(如/etc/passwd)中获取,并在登录会话期间保持不变,除非超级用户(root)通过特定方法进行更改。
  2. 实际组ID(RGID):表示实际用户所属的实际主组。与RUID类似,RGID在登录时确定并在会话期间一般不改变,源自口令文件中的用户主组信息。
  3. 有效用户ID(EUID):用于文件访问权限检查。通常情况下,EUID等于RUID,但在某些情况下(如执行具有特殊权限的程序文件时),EUID可能临时变为其他值。
  4. 有效组ID(EGID):用于文件访问权限检查。EGID通常与RGID相同,但在某些情况下可能临时变为其他值,如执行设置了相应标志的程序文件时。
  5. 附加组ID(SGIDs):除了主组外,每个用户可能还属于多个附加组。这些组ID存储在进程上下文中,用于在访问文件时检查是否具有附加组的权限。
  6. 保存的设置用户ID(SUID):在执行exec函数加载新程序时,当前进程的EUID会被复制到SUID。这一机制允许新程序在需要时恢复到原来的EUID。
  7. 保存的设置组ID(SGID):类似于SUID,保存当前进程EGID的一个副本,以便新程序在必要时恢复到原来的EGID。

设置用户ID(SUID)和设置组ID(SGID)位

      在文件的权限模式(st_mode)中,存在两个特殊的标志位:SUID和SGID。如果设置了某个可执行文件的SUID位,当非所有者的用户执行该文件时,其进程的EUID将临时变为文件所有者的UID,从而获得文件所有者的权限。同样,如果设置了SGID位,执行该文件的进程其EGID将临时变为文件的组所有者GID。

5 文件存取许可权

权限位与chmod命令: 每个文件有9个存取许可权位,分为三类:用户(所有者)、组和其他。这三类用户各自有读(r)、写(w)和执行(x)三种权限。chmod命令用于修改这些权限位,使用字母u、g和o分别代表用户、组和其他,以及相应的读、写、执行权限(如chmod u+x file为文件所有者添加执行权限)。

执行权限(x):对于目录,执行权限意味着用户可以进入该目录(搜索、查找文件),因此也称为“搜索位”。 例如,要打开文件/usr/dict/words,用户需要对目录/、/usr、/usr/dict具有执行权限。对于普通文件,执行权限意味着可以将其作为程序执行。

读权限(r):对目录的读权限允许用户列出其中的文件名。对普通文件的读权限意味着可以打开并读取其内容。

写权限(w):对目录的写权限允许用户在其中创建、删除或重命名文件。对普通文件的写权限意味着可以修改其内容。


在目录中创建新文件:需对该目录具有写权限和执行权限。

删除文件:用户需对包含该文件的目录具有写权限和执行权限。删除操作本身不需要对目标文件有任何权限。

执行文件:使用exec系列函数执行文件时,用户需具有文件的执行权限。

open函数中的标志(如O_RDONLY、O_WRONLY、O_RDWR、O_TRUNC等)与文件的读、写权限紧密相关。

要以只读或读写方式打开文件,用户必须具有对应的读权限。
要以写入或读写方式打开文件,用户必须具有写权限。
若要使用O_TRUNC标志截断文件,用户必须具有写权限。

进程尝试访问文件时,内核以进程的uid、gid和文件的uid和gid进行存取权限的判断。

(1)超级用户特权:如果进程的有效用户ID(EUID)为0(即超级用户),则允许访问。

(2)文件所有者匹配:如果进程的EUID等于文件所有者ID:

(理解:进程也属于某一个用户和用户组)

如果对应的用户权限位(读、写、执行)被设置,允许访问;否则,拒绝访问。

(3)组匹配:如果进程的有效组ID(EGID)或添加组ID之一等于文件的组ID:

如果对应的组权限位被设置,允许访问;否则,拒绝访问。

(4)其他用户:如果“其他”用户的权限位被设置,允许访问;否则,拒绝访问。

在上述步骤中,一旦满足某一步的条件,内核就会批准或拒绝访问,不再检查后续步骤。

6 新文件和目录的所有权  

新文件的用户ID: 新文件的用户ID(UID)直接设置为创建该文件的进程的有效用户ID。

新文件的所有者将是执行创建操作的进程所代表的用户身份。

新文件的组ID: 两种可能的选择:

(1) 有效组ID(默认情况):新文件的组ID可以设置为创建进程的有效组ID(EGID)默认情况

(2) 所在目录的组ID:新文件的组ID也可以设置为它所在目录的组ID。新创建的文件将继承其所在目录的组所有权,而非完全依赖于进程的有效组ID。

7.access()函数

在不打开文件的情况下,根据当前进程的用户身份和权限,判断对指定文件或目录是否有特定的访问权限。这个函数在需要验证用户是否有权访问某个文件。

int access(const char *pathname, int mode);
  • 1.

int mode::指定测试的访问模式。可以是常量的组合(使用位或运算符 | 来组合)
F_OK: 检查文件是否存在。
R_OK: 检查是否具有读取权限。
W_OK: 检查是否具有写入权限。
X_OK: 检查是否具有执行权限(普通文件,是否可以被执行;对于目录,则表示是否可以搜索该目录,即能否进入该目录)。 

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main() {
    const char *filename = "/etc";
    if (access(filename, R_OK ) == 0) {
        printf("The current process has read access to the file.\n");
    } else if(access(filename,W_OK)==0){
        printf("The current process has write access to the file.\n");
        // Optionally, you can check errno here for specific error codes.
    }else{
        printf("error");
    }
    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
8.umask()函数

umask 定义了进程在创建新文件或目录时,应从默认权限中移除的权限位(设定掩码),并返回之前的值。

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
  • 1.
  • 2.
  • 3.

新进程通常继承其父进程的 umask。在许多系统中,登录shell的默认 umask 通常是 022,即禁止组和其他用户的写权限。

适当设置 umask 可以增强系统的安全性,防止新建文件过于开放。umask设置是进程级的,只影响当前进程及其派生的子进程。

参数mask由以下位构成,使用按位或运算符指定多个模式:

st_mode 屏蔽

含义

S_IRUSR

S_IWUSR

S_IXUSR

属主读

属主写

属主执行

S_IRGRP

S_IWGRP

S_IXGRP

属组读

属组写

属组执行

S_IROTH

S_IWOTH

S_IXOTH

其他读

其他写

其他执行

mode_t mask: 一个表示权限掩码的整数,通常以八进制形式给出。掩码中的每一位对应于文件权限的相应位,如果某一位为1,则表示对应权限在创建新文件或目录时应被禁止。

 将 umask 设置为禁止组和其他用户写入新创建的文件,并禁止组和其他用户执行新创建的目录,可以这样设置:

mode_t old_umask = umask(S_IWGRP | S_IWOTH | S_IXGRP | S_IXOTH);
  • 1.

old_umask会存储原来的 umask值。

恢复 umask:

umask(old_umask);
  • 1.
 9 chmod函数

chmod和fchmod函数用于修改文件或目录存取权限,可以赋予或撤销用户(所有者、组成员或其他用户)对指定文件或目录的读、写和执行权限。chmod 函数通过文件路径直接修改权限,适用于知道文件路径但文件未被程序当前打开的情况。fchmod 函数则是针对已经通过 open 系统调用获得文件描述符的文件进行权限修改。

#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
  • 1.
  • 2.

const char *path: 指向包含要修改权限的文件或目录路径的字符串指针。
mode_t mode:新的权限模式。这是一个整数,通常通过按位或(|)操作组合预定义的宏来构建。这些宏包括:

S_IRWXU: 对文件所有者的读(S_IRUSR)、写(S_IWUSR)和执行(S_IXUSR)权限。

S_IRWXG: 对文件所属组的读(S_IRGRP)、写(S_IWGRP)和执行(S_IXGRP)权限。

S_IRWXO: 对其他用户的读(S_IROTH)、写(S_IWOTH)和执行(S_IXOTH)权限。

#include <sys/stat.h>
int fchmod(int fildes, mode_t mode);
  • 1.
  • 2.

int fildes: 已经打开的文件描述符,指向要修改权限的文件。
mode_t mode: 同 chmod 函数,指定新的权限模式。

例:

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    // 使用 chmod 修改权限
    if (chmod("/path/to/file.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
        perror("chmod failed");
        exit(EXIT_FAILURE);
    }
    // 打开文件并使用 fchmod 修改权限
    int fd = open("/path/to/another_file.txt", O_RDWR);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }
    if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
        perror("fchmod failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    // 其他操作...
    close(fd);
    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
11 chown,fchown 和 lchown函数

chown()/fchown()/lchown():更改由 指定的文件或目录的所有者id和组id。

int chown(const char *pathname, uid_t owner, gid_t group);
  • 1.

如果路径指向符号链接,chown()会更改符号链接指向的目标文件的所有权,而非符号链接自身,lchown相反。

12 文件长度 

stat结构中的st_size字段。

(1)普通文件:表示文件以字节为单位的长度。文件长度可以为0,此时读取该文件时将接收到文件结束(EOF)指示。

(2)目录文件:文件长度通常是一个特定数值,如16或512的整倍数。

(3)符号链接:st_size字段表示符号链接中实际包含的文件名字节数。例如,对于链接lib -> usr/lib,其长度为7,即路径名usr/lib的长度。

13 文件截短

   在操作文件时,有时需要将文件的长度缩短,甚至将其截短至零字节。这可以通过使用truncate()或ftruncate()函数来实现。这两个函数分别以路径名和文件描述符为输入参数,将指定文件的长度截短至指定长度。

#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
  • 1.
  • 2.
  • 3.

pathname:指向包含待截短文件路径名的字符串指针。
fd:已打开文件的文件描述符。
length:新的文件长度,以字节为单位。如果原文件长度大于length,超出部分将被截去;若原文件长度小于length,则行为与系统实现相关,可能扩展文件并填充零字节(创建空洞)。

 15 link,unlink,remove,rename函数

link函数:任何一个文件可以有多个目录项指向其 i节点。创建一个向现存文件连接的方法是使用link函数。

unlink函数:删除一个现存的目录项,让连接计数减1。

只有当连接计数到0时,才可以被删除。
只要有进程打开了该文件,内容也不能被删除。关闭一个文件时,也需要检查文件打开的进程计数。

如果pathname是符号连接,那么unlink涉及的是符号连接而不是由该连接所引用的文件。

也可以用remove函数解除一个文件或目录的连接。

对于文件,remove和unlink相同,对于目录,remove功能和rmdir相同。

rename:对文件或目录更名。

17 symlink和readlink函数

symlink()函数和readlink()函数分别是用于创建和读取符号链接(symbolic link)的系统调用。

#include <unistd.h>
int symlink(const char *actual_path, const char *sym_path);
  • 1.
  • 2.

const char *actual_path: 指向实际目标文件路径的字符串指针,即符号链接要指向的文件或目录。
const char *sym_path: 指向新创建的符号链接路径的字符串指针。

symlink()函数创建一个新的符号链接(软链接),actual_path和sym_path可以位于不同的文件系统中。

readlink()函数

#include <unistd.h>
int readlink(const char *pathname, char *buf, int bufsize);
  • 1.
  • 2.

const char *pathname: 指向要读取的符号链接路径的字符串指针。
char *buf: 用于接收符号链接目标路径的缓冲区地址。
int bufsize: 缓冲区大小,应足够容纳链接目标路径。

(1)readlink()函数合并了open(), read(), 和close()的功能,一次性读取指定符号链接所指向的实际路径,并将其内容存放在提供的buf中。

(2)返回值为实际读取的字节数,而非null终止的字符串长度。因此,buf中的内容不以null字符终止。如果需要处理为C字符串,需确保buf中有足够的空间并手动添加null终止符。

18 文件的时间

字段

描述

示例

ls(1)选择项

st_atime

文件数据的最后存取时间

用户最后一次读取文件的时间

-u

st_mtime

文件数据的最后修改时间

文件内容最后一次被修改的时间

缺省

st_ctime

i节点状态的最后更改时间

文件权限、所有者、链接数等元数据最后一次被修改的时间

-c

修改时间(st_mtime):表示文件内容上一次发生实质性改动的时间。当文件内容被增删或替换时,时间会更新。

更改状态时间(st_ctime):反映文件i节点(inode)最后一次发生变化的时间。这包括但不限于对文件的存取权限、所属用户、组、链接数等元数据的更改,即使文件内容本身未变动,这些操作也会导致st_ctime更新。

存取时间(st_atime):系统不会保存对i节点的最后一次存取时间,access()和stat()函数调用不会更改这三个时间字段中的任何一个。

  系统管理员常用存取时间来清理长时间未被访问的文件。此类操作通常通过find命令完成。

ls命令排序:默认情况下(使用-l或-t选项),ls命令按照文件的修改时间(st_mtime)升序或降序显示。使用-u选项时,ls命令按照文件的存取时间(st_atime)排序。使用-c选项时,ls命令按照文件的更改状态时间 (st_ctime) 排序。

19 utime函数

utime()函数用于更改文件存取时间和修改时间。

#include <sys/types.h>
#include <utime.h>
int utime(const char *pathname, const struct utimbuf *times);
  • 1.
  • 2.
  • 3.

const char *pathname: 指向文件路径的字符串指针。
const struct utimbuf *times: 指向utimbuf结构体的指针,用于指定新的存取时间和修改时间。如果为NULL,将这两个时间设置为当前时间。

struct utimbuf结构体

struct utimbuf {
    time_t actime;  // access time
    time_t modtime; // modification time
};
  • 1.
  • 2.
  • 3.
  • 4.

这两个成员存储的是自1970年1月1日(国际标准时间)以来经过的秒数。

times参数的不同情况:

times为NULL:将文件的存取时间和修改时间都设置为当前时间。

times非NULL:文件的存取时间和修改时间设置为times指向的utimbuf结构体中的值。

20 mkdir函数和rmdir函数

mkdir()函数用于创建新目录,而rmdir()函数用于删除空目录。

mkdir()函数

#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
  • 1.
  • 2.
  • 3.

const char *pathname: 指向新目录路径的字符串指针。

mode_t mode: 指定新目录的权限模式。通常使用八进制形式,如S_IRWXU | S_IRWXG | S_IRWXO(0777),表示所有用户都有读、写、执行权限。

mkdir()函数创建一个空目录,同时自动创建必要的.(当前目录)和..(父目录)目录项。
所指定的权限mode受进程的文件模式创建掩码(umask)影响,实际权限为mode & (~umask)。
创建目录时,通常需要至少设置一个执行权限位(如x),以允许用户访问目录中的文件名。否则,可能导致无法进入或列出目录内容。

如果创建目录后其链接计数变为0,并且没有其他进程打开此目录,系统将释放与此目录关联的空间。若仍有进程持有该目录的打开句柄,即使连接计数为0,目录也不会立即释放。直到所有进程关闭该目录后,才会释放资源。

rmdir()函数

int rmdir(const char *pathname);
  • 1.

const char *pathname: 指向要删除的空目录路径的字符串指针。

     rmdir()函数用于删除一个空目录。如果目录非空,调用将失败。删除操作不仅包括移除目录本身,还包括其下的.和..目录项。在最后一个进程关闭该目录前,虽然不能在其中创建新文件,但已打开该目录的进程仍能继续在其下执行其他操作,直至所有进程关闭该目录。

21 读目录

(1)目录访问权限:具有读取权限的用户可以查看目录中的内容(列出目录下的文件和子目录名称)。但直接修改目录(修改元数据)只有操作系统内核有权限这样做。目录的写权限允许在该目录中创建或删除文件(直接修改目录元数据不同)。

(2)为了简化跨不同UNIX实现的编程,POSIX(可移植操作系统接口)定义了一套标准的函数来读取目录。

opendir(const char *pathname);
  • 1.

    打开指定路径的目录,并返回一个DIR类型的指针,该指针用于后续的读取操作。如果打开失败,则返回NULL。

readdir(DIR *dp);
  • 1.

    从打开的目录流(opendir返回的DIR指针)读取下一个目录项,并将其信息填充到一个struct dirent结构体中返回。当到达目录末尾时,返回NULL。每个struct dirent通常至少包含文件名和inode号。

rewinddir(DIR *dp)
  • 1.

将目录流的位置重置到目录的开头,允许重新开始读取目录项。

closedir(DIR *dp)
  • 1.

关闭由opendir打开的目录流,释放相关的资源。成功时返回0,失败时返回-1。

struct dirent结构体代表了一个目录项。struct dirent至少包含两个成员:

struct dirent{
  ino_t d_ino;
  char d_name[NAME_MAX+1];
}
  • 1.
  • 2.
  • 3.
  • 4.

ino_t d_ino:整型变量,存储目录项对应的inode号码。

char d_name[NAME_MAX + 1]:用于存储目录项的文件名,包括结尾的空字符\0。

NAME_MAX最大文件名长度,加1存放字符串结束符。

DIR结构类似于标准I/O库中的FILE结构。FILE结构是C标准库中用于处理文件流的抽象,而DIR结构则是用于处理目录流的,由操作系统提供的一组目录操作函数(opendir, readdir, rewinddir, closedir)内部使用。DIR结构保存了当前正在被读取的目录的相关信息。

22  chdir, fchdir 和getcwd 函数

在类UNIX系统中,每个进程都有一个“当前工作目录”(cwd),是处理相对路径名的基础。相对路径名是相对当前工作目录。如果当前工作目录/a/b,那么相对路径c/d实际指a/b/c/d。

当用户登录到UNIX系统时,系统会根据/etc/passwd文件中用户的记录设置初始工作目录。

当前工作目录是每个进程的一个属性,不同的进程可以有不同的当前工作目录。

起始目录是与用户账户关联的属性,所有属于该用户的进程在其启动时,默认会将这个目录设为其当前工作目录。

为了改变当前工作目录,UNIX提供了两个系统调用:chdir、fchdir

int chdir(const char *pathname);
  • 1.

根据pathname改变当前进程的工作目录。操作成功,返回0;否则,返回-1。

int fchdir(int filedes);
  • 1.

通过文件描述符filedes改变当前工作目录,文件描述符必须引用一个目录。成功返回0,失败返回-1。

获取绝对路径getcwd

char *getcwd(char *buf, size_t size);
  • 1.

buf:字符缓冲区的指针,存放当前工作目录的绝对路径名。

size:指定buf缓冲区的大小(字节为单位)。

当调用getcwd函数时,它会从当前工作目录出发,向上遍历目录层次直到根目录,沿途将每一步的目录名拼接起来,形成一个完整的绝对路径,并将这个路径存储在buf指向的缓冲区内。

如果函数执行成功,会返回buf的值,即指向存储了绝对路径名的缓冲区地址。

如果函数执行失败,则返回NULL。

23 特殊设备文件

ttyname()函数涉及到了st_dev和st_rdev这两个字段,在文件系统及设备管理中有用。

(1)st_dev字段:st_dev字段表示文件所在的文件系统的设备号。对于每个文件,其st_dev值反映了包含该文件的文件系统所关联的主设备号和次设备号。这个设备号标识了文件系统所在的物理或虚拟存储设备。

在ttyname()函数的上下文中,st_dev用于确定给定文件是否属于某个特定的文件系统。例如,检查一个文件是否属于 /dev 目录下的终端设备文件,就需要对比其st_dev值与目标文件系统的设备号。

(2)st_rdev字段:st_rdev字段仅适用于字符特殊文件和块特殊文件,它存储的是这些特殊文件所代表的实际设备的设备号。对于终端设备(如TTY设备),其st_rdev值包含了主设备号和次设备号,用来唯一标识该终端设备。

    st_rdev字段用于识别一个文件是否为终端设备。该函数通常会根据传入的文件描述符(fd)对应的文件的st_rdev值,查找与之匹配的终端设备名称(如/dev/ttyS0或/dev/pts/0)。

24.  sync和fsync函数

这种做法被称为“延迟写”(delayed write)可以减少磁盘读写次数。

    虽然延迟写提高磁盘利用率,但降低了文件更新速度,当系统发生故障时,缓存中的未写入磁盘的数据可能丢失,导致文件更新内容的丢失。

   为了确保磁盘上实际文件系统与内核缓存内容的一致性,UNIX系统提供了sync和fsync两个系统调用函数:

(1)sync:该函数将所有已修改过的缓存块排入写队列,然后立即返回,不等待实际I/O操作结束。系统守护进程一般每隔30秒调用一次sync,以定期刷新内核的块缓存。用户也可以通过执行sync命令来手动调用sync函数。

(2)fsync:与sync不同,fsync针对单个文件,不仅将文件的已修改缓存块排入写队列,而且会等待I/O操作完成,确保所有修改过的块都已经真正写入磁盘后才返回。fsync适用于需要即时数据持久化保证的应用场景,如数据库,确保每次调用后文件内容立即更新到磁盘。