unix环境编程:文件和目录

时间:2024-04-22 17:07:05

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);

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;   /* 文件亚数据最后修改时间戳 */
 }

例:获取指定文件路径的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);
}

 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);

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;
}

8.umask()函数

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

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

新进程通常继承其父进程的 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); 

old_umask会存储原来的 umask值。

恢复 umask:

umask(old_umask);

 9 chmod函数

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

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

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);

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;
}

Linux 系统中 ln(1) 命令是用来创建文件链接的。

ln 产生硬链接

ln -s 产生符号链接,s 是 symbol,不是 soft,所以不是软链接而是符号链接。

硬链接和符号链接有什么区别呢?

硬链接的 inode 号没有改变,inode 号是文件的唯一标识。所以创建硬链接没有产生新文件,硬链接就是目录项的同义词,实际上就是在当前的目录项上多写了一条记录。
硬链接不能跨分区,不能为目录文件建立硬链接。

符号链接产生了新的 inode 号,说明产生了新的文件,但并不分配磁盘块(block)。
符号链接可以跨分区,可以为目录文件建立符号链接。

link(2)、unlink(2) 函数用于创建和删除符号链接。

remove(2) 相当于 rm(1) 命令,它是使用 unlink(2) 、rmdir(2) 函数封装的。它在删除文件的时候其实并没有立即将文件的数据块从磁盘上移除,而是在被删除的文件没有任何进程引用的时候才将它的数据块释放。

rename(2)
rename - change the name or location of a file 

#include <stdio.h> 
int rename(const char *oldpath, const char *newpath);

 rename(2) 函数用于重命名文件或目录。

11 chown,fchown 和 lchown函数

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

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

如果路径指向符号链接,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);

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

14 文件系统

Inode(索引节点): Inode是文件系统的核心数据结构,它包含了文件的元数据,如文件大小、创建和修改时间、权限信息、所有者和组信息,以及最重要的,指向文件实际数据块的指针列表。每个文件和目录在文件系统中都有一个唯一的Inode与之对应,通过Inode号码(Inode Number)来唯一标识。

指向Inode的目录项: 目录项(Directory Entry)是构成目录结构的基本单元,它记录了文件系统中文件或子目录的名字以及与其关联的Inode号码。当用户在命令行或其他接口中访问一个路径(如/home/user/document.txt),操作系统实际上是通过沿着路径中的目录逐层查找对应的目录项,获取到目标文件或目录的Inode号码,再通过Inode号码找到相应的Inode,进而访问文件的元数据和实际数据。

文件连接: 在传统的UNIX System V文件系统中,文件连接(File Linking)是指同一个Inode可以被多个目录项引用。这意味着,一个文件可以拥有多个路径名(即别名),这些路径名各自对应于不同的目录项,但都指向同一个Inode。常见的文件连接类型包括硬链接(Hard Link)和软链接(Symbolic Link,也称符号链接或Symlink):

  • 硬链接: 硬链接是同一文件系统内两个或更多目录项指向同一Inode的情况。创建硬链接相当于为现有文件添加一个新的目录项,但它们共享相同的Inode和数据块。因此,删除任意一个硬链接并不会导致文件数据丢失,只有当所有硬链接都被删除(或文件系统的Inode被回收)时,文件内容才会真正被清除。

  • 软链接: 软链接是一种特殊的文件,它本身是一个独立的Inode和数据块,其数据块中存储的是指向另一个文件(或目录)的路径名。访问软链接时,系统会解析其包含的路径名,找到真正的目标文件(其Inode)。删除软链接只影响链接本身,不会影响目标文件;而删除目标文件后,软链接将变为“断链”状态,试图访问它将导致找不到目标文件的错误。

分区与文件系统的关系: 磁盘可以被划分为一个或多个分区,每个分区可以格式化为一个独立的文件系统。如图4-1所示,这样的分区布局允许对磁盘空间进行逻辑组织和隔离,每个文件系统有自己的根目录(/),并在其中建立层次化的目录结构和文件。用户通过挂载(mount)操作将分区(或其包含的文件系统)关联到全局文件系统树的某个挂载点,使得这些分区内的文件和目录对用户可见并可访问。

17 symlinkreadlink函数

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

#include <unistd.h>
int symlink(const char *actual_path, const char *sym_path);
const char *actual_path: 指向实际目标文件路径的字符串指针,即符号链接要指向的文件或目录。
const char *sym_path: 指向新创建的符号链接路径的字符串指针。

symlink()函数创建一个新的符号链接(软链接),使得sym_path指向actual_path。

在创建符号链接时,不需要actual_path事先存在。即使目标文件不存在,仍可以成功创建符号链接。

actual_path和sym_path可以位于不同的文件系统中。

readlink()函数

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

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 (--atime)
st_mtime 文件数据的最后修改时间 文件内容最后一次被修改的时间 缺省(默认排序依据)
st_ctime i节点状态的最后更改时间 文件权限、所有者、链接数等元数据最后一次被修改的时间 -c (--ctime)

修改时间(st_mtime):表示文件内容上一次发生实质性改动的时间。当文件内容(如文本、二进制数据等)被增删或替换时,该时间会更新。

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

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

系统管理应用:系统管理员常常利用存取时间来清理长时间未被访问的文件,如删除过去一周内未被访问的a.out或core文件。此类操作通常通过find(1)命令完成。

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

19 utime函数

utime()函数是一个用于更改文件存取时间(access time)和修改时间(modification time)的C语言系统调用。

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

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

struct utimbuf结构体

struct utimbuf {
    time_t actime;  // access time
    time_t modtime; // modification time
};

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

操作与权限要求

根据times参数的不同情况,utime()函数有不同的行为和权限需求

times为NULL:

将文件的存取时间和修改时间都设置为当前时间。
要求满足以下任一条件:进程的有效用户ID等于文件所有者ID。
进程对该文件具有写权限。

times非NULL:

文件的存取时间和修改时间设置为times指向的utimbuf结构体中的值。
进程的有效用户ID等于文件所有者ID,或进程是一个超级用户进程(具有管理员权限)。
仅具有写权限不足以执行此操作。

20 mkdir函数和rmdir函数

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

mkdir()函数

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

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

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

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

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

rmdir()函数

1int rmdir(const char *pathname);

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

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

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值包含了主设备号和次设备号,用来唯一标识该终端设备。

用途:在ttyname()函数中,st_rdev字段用于识别一个文件是否为终端设备文件,并且可以进一步确定其具体的设备类型和编号。该函数通常会根据传入的文件描述符(fd)对应的文件的st_rdev值,查找与之匹配的终端设备名称(如/dev/ttyS0或/dev/pts/0)。

24.  sync和fsync函数

 磁盘I/O缓存机制:传统UNIX系统内核中设置有缓冲存储器(缓存),大部分磁盘读写操作都通过该缓存进行。当数据被写入文件时,首先会被复制到内核缓存中。只有当缓存空间未满时,这些数据不会立即被排入输出队列,而是等待缓存填满或内核需要回收缓存空间存放其他磁盘块数据时,才会将相应缓存块排入输出队列,真正进行I/O操作,这种做法被称为“延迟写”(delayed write),可以减少磁盘读写次数,提高整体I/O效率。

延迟写的潜在问题

    尽管延迟写有助于提高磁盘利用率,但它降低了文件内容的更新速度,即写入缓存的数据在一段时间内并未真正写入磁盘。因此,当系统发生故障(如突然断电)时,缓存中的未写入磁盘的数据可能丢失,导致文件更新内容的丢失。

维护一致性:sync与fsync系统调用

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

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

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

比较fsync与O_SYNC标志

     fsync函数与文件打开时指定的O_SYNC标志在确保数据立即写入磁盘方面有相似之处,但应用时机和粒度不同:

  • fsync:在需要时显式调用,针对单个文件进行操作,确保该文件所有修改过的数据立即写入磁盘,然后返回。

  • O_SYNC:在打开文件时作为标志传入,影响后续对该文件的所有write操作。对设置了O_SYNC标志的文件执行write操作时,系统都会等待数据写入磁盘后再返回,确保每次写操作后文件内容立即更新。