IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 《UNIX环境高级编程》笔记 第四章-文件和目录 -> 正文阅读

[系统运维]《UNIX环境高级编程》笔记 第四章-文件和目录

1. 函数stat、fstat、fstatat和lstat

返回指定文件信息

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags);
  • stat函数返回pathname指定的文件信息,并保存在statbuf参数中

  • fstat函数与stat类似,只是通过文件描述符fd标识文件

  • lstat与stat类似,只是当pathname是符号链接时,获得的是符号链接文件本身信息,而不是符号链接指向的文件信息

  • fstatat中,如果pathname是一个绝对路径名,则dirfd参数忽略;如果pathname是一个相对路径名,则dirfd表示该相对路径的起始地址(即dirfd为打开目录文件的文件描述符);如果dirfd设置为AT_FDCWD,则相对路径的起始地址为该进程工作目录。flags参数设置为AT_SYMLINK_NOFOLLOW时,fstatat不会跟随符号链接,而是返回符号链接文件本身信息。

    # define AT_SYMLINK_NOFOLLOW	0x100	/* Do not follow symbolic links.  */
    

struct stat结构体保存了文件的各种信息,以上一个函数均把文件信息保存在该结构体中:

struct stat {
    dev_t     st_dev;         /* 文件系统设备号 */
    ino_t     st_ino;         /* inode number */
    mode_t    st_mode;        /* 文件类型和访问权限 */
    nlink_t   st_nlink;       /* 硬链接个数 */
    uid_t     st_uid;         /* 所有者uid */
    gid_t     st_gid;         /* 所有者gid */
    dev_t     st_rdev;        /* 特殊文件(块、字符特殊文件)的实际设备号 */
    off_t     st_size;        /* 文件大小(字节) */
    blksize_t st_blksize;     /* 对文件I/O较合适的块长度 */
    blkcnt_t  st_blocks;      /* 该文件分配的512字节块的数量 */

    struct timespec st_atim;  /* 文件数据最近访问时间 */
    struct timespec st_mtim;  /* 文件数据最近修改时间 */
    struct timespec st_ctim;  /* inode节点状态最近改变时间 */

    #define st_atime st_atim.tv_sec      /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
};

其中timespec结构体按照秒和纳秒定义了时间

struct timespec {
	__kernel_time_t	tv_sec;			/* 秒 */
	long		tv_nsec;		/* 纳秒 */
};

2. 文件类型

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

文件信息保存在stat结构体的st_mode成员中

2.1 普通文件(regular file)

最常用的文件类型,这种文件包含了某种形式的数据。至于这些数据是文本还是二进制数据,对UNIX内核而言并无区别。

2.2 目录文件(directory file)

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

2.3 块特殊文件(block special file)

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

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

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

2.5 FIFO

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

2.6 套接字(socket)

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

2.7 符号链接(symbolic link)

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

2.8 文件类型判断

通过stat系统调用获得的文件信息(stat结构体对象)中的st_mode成员,该成员保存了文件类型信息和访问权限。可以通过以下宏判断文件类型

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

例子:判断文件类型

int main(int argc, char* argv[]) {
    struct stat st;
    stat(argv[1],&st);
    if(S_ISREG(st.st_mode)) {
        cout << "普通文件" << endl;
    } else if(S_ISDIR(st.st_mode)) {
        cout << "目录文件" << endl;
    } else {
        cout << "其他文件" << endl;
    }
}
$ ./a.out  t.txt 
> 普通文件

3. 设置用户ID位和设置组ID位

与一个进程相关的ID有6个

实际用户ID
实际组ID
我们实际上是谁,即执行此进程的实际用户。这两个ID是用户登录时取自口令文件的
有效用户ID
有效组ID
附属组ID
用于文件访问权限检查
保存的设置用户ID
保存的设置组ID
用exec函数保存

通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。

将这6个ID**与每个文件的所有者和组所有者区分开来。**这里讲的6个ID是与进程关联的,而与每个文件关联的所有者uid和所有者gid由stat结构体中的st_uid和st_gid指定。

设置用户ID位和设置组ID位:

在stat结构体的文件模式字(st_mode)中设置一个特殊标志,意为当执行此文件时,将进程的有效用户ID设置为文件所有者uid。这个标志就是文件模式字st_mode中的一位:设置用户ID位

与此类似,在文件模式字中可以设置另一个特殊标志,意为当执行此文件时,将进程的有效组ID设置为文件所有者gid。这个标志就是文件模式字st_mode中的一位:设置组ID位。

可以再文件模式字(st_mode)中设置一个特殊标志,意为当执行此文件时,将进程的有效用户ID设置为文件所有者uid。与此类似,在文件模式字中可以设置另一个特殊标志,意为当执行此文件时,将进程的有效组ID设置为文件所有者gid。

比如文件所有者是超级用户,并且设置了该文件的设置用户ID位,那么当执行该文件时,这个进程就拥有超级用户权限(因为有效用户ID设置为了用户所有者uid)。

由于设置用户ID位和设置组ID位都包含在文件stat的st_mode成员中,因此可以使用以下常量通过按位与来判断是否设置了这两个标志位

S_ISUID // 取得S_ISUID位的状态。判断是否设置了设置用户ID位
S_ISGID // 取得S_ISGID位的状态。判断是否设置了设置组ID位
if(S_ISUID & st.st_mode) {
    cout << "设置了设置用户ID位" << endl;
} else {
    cout << "没有设置设置用户ID位" << endl;
}

if(S_ISGID & st.st_mode) {
    cout << "设置了设置组ID位" << endl;
} else {
    cout << "没有设置设置组ID位" << endl;
}

4. 文件访问权限

stat结构体的st_mode成员还包含了对文件的访问权限位

每个文件有9个访问权限位,可将其分为3类

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

C中的定义如下:

# define S_IRUSR	0400       /* Read by owner.  */
# define S_IWUSR	0200      /* Write by owner.  */
# define S_IXUSR	0100       /* Execute by owner.  */
/* Read, write, and execute by owner.  */
# define S_IRWXU	(S_IRUSR|S_IWUSR|S_IXUSR)

# define S_IRGRP	(S_IRUSR >> 3)  /* Read by group.  */
# define S_IWGRP	(S_IWUSR >> 3)  /* Write by group.  */
# define S_IXGRP	(S_IXUSR >> 3)  /* Execute by group.  */
/* Read, write, and execute by group.  */
# define S_IRWXG	(S_IRWXU >> 3)

# define S_IROTH	(S_IRGRP >> 3)  /* Read by others.  */
# define S_IWOTH	(S_IWGRP >> 3)  /* Write by others.  */
# define S_IXOTH	(S_IXGRP >> 3)  /* Execute by others.  */
/* Read, write, and execute by others.  */
# define S_IRWXO	(S_IRWXG >> 3)

其中用户指的是文件所有者,组指的是文件所有者所在组的用户,其他指的是其他用户

**注意,open函数和creat函数的最后一个参数也通过以上常量进行按位或获得。**也可以通过数字如0777设置。

可以通过chmod命令修改这9个文件访问权限位

  • 当通过名字打开任一类型的文件时,需要保证对该名字中的每一个目录文件都有执行权限

    比如打开文件/usr/include/stdio.h文件,不光需要对stdio.h文件有权限,还需要对目录/、/usr、/usr/include目录拥有执行权限。

    需要注意,对目录文件的读权限和执行权限是不同的。读权限允许我们读目录,获得在该目录中所有文件名的列表。执行权限则允许我们通过该目录,即搜索该目录。

  • 对于一个文件的读权限决定了我们能否打开它进行读操作,与open函数的O_RDONLY和O_RDWR标志相关

  • 对于一个文件的写权限决定了我们能否打开它进行写操作,与open函数的O_WRONLY和O_RDWR标志相关

  • 在open中对一个文件指定O_TRUNC标志,需要拥有该文件的写权限

  • 为了在一个目录中创建新文件,需要对该目录有执行和写权限。

  • 删除一个文件,需要对该目录有执行和写权限,对文件本身不需要权限

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

文件访问权限测试流程:

进程每次打开、创建或删除一个文件时,内核都会进行文件访问测试检查。该测试涉及文件的st_uid和st_gid、进程的有效用户ID和有效组ID和附属组ID。

  1. 若进程的有效用户ID是0(超级用户),则允许访问。这给予了超级用户对整个文件系统进行处理的充分自由
  2. 若进程的有效用户ID等于文件的st_uid(也就是进程拥有该文件),那么如果所有者适当的访问权限位被置位,则允许访问。比如若进程读打开该文件,则该文件的用户读位应为1;若进程写打开该文件,则该文件的用户写位应为1;若进程执行打开盖文件,则该文件的用户执行位应为1。
  3. 若进程的有效组ID或附属组ID等于文件的st_gid,那么如果组适当的权限位被置位,则允许访问
  4. 若其他用户适当的访问权限位被置位,则允许访问。

需要注意,权限测试流程是按照顺序一步一步执行的。如果进程拥有此文件,则按照第二步测试权限,后续步骤不再执行;如果进程不拥有该文件但是进程属于某个适当的组,则按照第三步测试权限,后续步骤不再执行。

5. 新文件和目录的所有权

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

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

6. 函数access和faccessat

按照实际用户ID进行访问权限测试

int access(const char *pathname, int mode);
int faccessat(int dirfd, const char *pathname, int mode, int flags);
// 成功返回0,失败返回-1并置位errno

faccessat中当pathname是绝对路径,dirfd忽略;如果pathname是相对路径且dirfd是AT_FDCWD,相对路径的起始目录是进程的工作目录,否则相对路径的起始目录是dirfd打开的目录文件

  • mode参数:

    • F_OK:检验文件是否存在

    • R_OK/W_OK/X_OK:这三个常量按位与可以检测读写执行权限

      #define	R_OK	4		/* Test for read permission.  */
      #define	W_OK	2		/* Test for write permission.  */
      #define	X_OK	1		/* Test for execute permission.  */
      #define	F_OK	0		/* Test for existence.  */
      
  • flags参数:如果设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。

使用示例:

    if(access("t1.txt",R_OK) < 0) {
        perror("文件不存在");
    }

7. 函数umask

用于创建文件前设置文件模式创建屏蔽字(建立文件时预设的权限掩码):该屏蔽字中被置位的指定权限,当创建文件时不会设置该权限(即该权限被屏蔽掉了)

mode_t umask(mode_t mask); // 返回之前的文件模式创建屏蔽字

其中参数mask可以是第4节中9个常量的按位或组成(或者通过数字如0002屏蔽掉其他写权限),指定哪些权限被屏蔽了。

该函数常与open、creat函数配合使用,通过umask函数设置文件模式创建屏蔽字。在进程创建一个新文件或新目录时,一定会使用文件模式创建屏蔽字。在之前的open函数有一个参数mode,open根据mode设置文件访问权限时,文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭

例子:通过umask配合open创建文件

    umask(0); // 没有屏蔽任何权限位
    open("tt.txt",O_RDWR | O_CREAT,0777); // 按照0777权限创建文件
    umask(S_IRGRP | S_IWGRP); // 屏蔽掉了组读和组写权限位
    open("tt1.txt",O_RDWR | O_CREAT,0777); // 按照0777权限创建文件(注意其中两个权限位被屏蔽掉了)
$ ls -la
> -rwxrwxrwx 1 root root    0 Oct 21 12:36 tt.txt # 可见文件权限是0777
> -rwx--xrwx 1 root root    0 Oct 21 12:36 tt1.txt # 可见组读和组写权限被屏蔽了

8. 函数chmod,fchmod,fchmodat

更改现有文件的访问权限

进程的有效用户ID必须等于文件所有者uid,或者该进程拥有超级用户权限才可以成功调用以下函数修改文件访问权限。

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
//成功返回0,失败返回-1
  • 参数flags:当设置AT_SYMLINK_NOFOLLOW,fchmodat不会跟随符号链接,而是针对符号链接文件本身。

  • 参数mode:以下常量的按位或

    mode说明
    S_ISUID
    S_ISGID
    执行时设置用户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
    其他读写执行权限
    其他读权限
    其他写权限
    其他执行权限

9. chown、fchown、fchownat、lchown函数

更改文件的用户ID和组ID

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);
//成功返回0,失败返回-1

owner和group参数如果为-1,则对应的ID不进行修改

对于lchown函数,如果是符号链接文件,则修改的是符号链接文件本身,而不是它指向的文件

对于fchownat函数,如果flags设置为AT_SYMLINK_NOFOLLOW标志,则修改的是符号链接文件本身,而不是它指向的文件

注意:

  • 只有超级用户进程可以更改文件的所有者。文件所有者可以将文件组更改为该所有者所属的任何组。超级用户进程可以任意更改组。
  • 如果这些函数由非超级用户进程(有效用户id不为0)调用,成功返回时,该文件的设置用户ID位和设置组ID位被清除

10. 文件长度

stat结构体对象的st_size成员变量表示文件的长度(字节),此字段只对普通文件、目录文件、符号链接有意义

  • 对于普通文件,其长度可以为0,开始读该文件时将会得到eof
  • 对于目录文件,其长度通常是一个数(16或512)的整数倍。
  • 对于符号链接,文件长度通常是其指向的文件名的实际字节数(不包括字符串结尾的null字节)

stat的st_blksize成员:

对文件I/O较合适的块长度。当使用st_blksize用于读操作时,读一个文件所需时间量最少。标准I/O库函数也试图一次读写st_blksize个字节的数据。

stat的st_blocks成员:

512字节块的数量

文件中的空洞:

普通文件可以包含空洞,设置的偏移量超过文件尾端,并写入了某些数据后造成。由于磁盘上可以不存放空洞,因此使用ls命令和du命令打印出来的文件大小是不一致的。

ls:打印出来的是文件内容字节数,包含了空洞大小

du:打印出来的是指定的目录或文件所占用的磁盘空间。(512或1024字节块数)

int fd = open("tt.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
lseek(fd, 200000, SEEK_END);
char buf[10] = "abc";
write(fd, buf, 3);
close(fd);
$ ls -l tt.txt 
> -rwxr-xr-x 1 root root 200003 Oct 21 15:09 tt.txt
$ du -h tt.txt # 以K,M,G为单位打印文件占用磁盘大小
> 4.0K    tt.txt

可以看出该文件实际占用磁盘空间大小是小于文件内容大小的,这是因为该文件包含了空洞,而磁盘中可以不存放空洞。该文件占用了4KB磁盘空间是由于linux ext4文件系统,磁盘块长度为4096字节(4KB)。因此该文件只占用了一个磁盘块数据

11. 文件截断

使用truncate和ftruncate函数进行文件截断

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

将文件截断为length字节。如果以前文件大于length,则截断为length;如果以前文件小于length,则文件长度增加,增加的部分读作0(即可能在文件中创建了一个空洞)

12. 文件系统

可以把磁盘分为一个或多个分区。每个分区可以包含一个文件系统。inode节点是固定长度的记录项,它包含有关文件的大部分信息

在这里插入图片描述

inode也会消耗硬盘空间,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息每个inode节点的大小,一般是128字节或256字节

在这里插入图片描述

  • 上面的图中可以看到有两个目录文件中的目录项指向同一个i节点。每个inode都有一个链接计数(硬链接),其值就是指向该inode的目录项个数。只有当该链接计数为0时,才可以删除该文件(释放该文件占用的磁盘数据块)。这也是为什么删除一个目录项的函数是unlink而不是delete。在stat数据结构中,st_nlink成员变量表示该文件的硬链接个数
  • 除了硬链接(目录项的inode编号直接表示链接的inode节点),还有一种链接称为符号链接。符号链接文件的实际内容(其数据块中的内容)是指向的文件的名字
  • inode包含了文件的所有信息:文件类型、文件访问权限位、文件长度、指向文件数据库的指针等。stat结构体中大多数信息取自inode。
  • 因为目录项中的inode编号指向同一文件系统中的对应inode节点,一个目录项不能指向另一个文件系统的inode节点,因此inode不能跨文件系统
  • 给文件重命名:并不改变文件实际内容,只需要构造一个指向现有inode的新目录项,并删除老目录项即可。硬链接数目不变。

目录文件的链接计数:

在这里插入图片描述

mkdir testdir

假如新建了目录testdir,则文件系统实例如图所示。

可以看出任何叶目录(没有子目录的目录)文件的链接计数总是2(父目录文件中的testdir目录项、testdir目录文件中的.目录项)

不是叶目录的目录文件链接计数大于等于3(父目录文件的该目录项、该目录文件的.目录项、子目录文件的…目录项)。注意,在父目录中的每个子目录都会使得该父目录文件的链接计数+1(子目录文件中的…目录项)

13. 函数link、linkat、unlink、remove

任何一个文件都可以有多个目录项指向其inode节点。可以使用link或linkat系统调用创建一个指向现有文件的硬链接

int link(const char *oldpath, const char *newpath);
int linkat(int olddirfd, const char *oldpath,int newdirfd, const char *newpath, int flags);

flags参数:当现有文件是符号链接文件时,设置该参数为AT_SYMLINK_FOLLOW就创建指向符号链接目标的硬链接,否则就创建指向符号链接文件本身的硬链接。

删除一个现有的目录项可以通过unlink或unlinkat函数,该操作要求对目录有写和执行权限。

int unlink(const char *pathname);
int unlinkat(int dirfd, const char *pathname, int flags);

参数flags:当设置为AT_REMOVEDIR标志时,unlinkat类似于rmdir一样删除目录,否则unlinkat和unlink执行相同操作。

  • 删除目录项使得指向的inode节点硬链接数-1。

  • 只有该硬链接数为0时,该文件内容才可以被删除。但是如果有进程打开了该文件,则其内容也不能删除。当进程关闭一个文件时,内核首先检查打开该文件的进程个数,如果为0,内核再检查其硬链接个数,如果也为0就删除该文件内容

  • 注意,unlink只是删除了目录项,并不是删除了文件内容。文件内容必须是硬链接数为0并且没有进程打开该文件时才会删除。

  • 如果pathname是符号链接,则unlink删除的是符号链接本身。

同样可以使用remove函数解除对一个文件或目录的硬链接:

int remove(const char *pathname);

对于文件,remove等同于unlink,对于目录,remove等同于rmdir

14. 函数rename和renameat

用于文件或目录重命名

int rename(const char *oldpath, const char *newpath);
int renameat(int olddirfd, const char *oldpath,int newdirfd, const char *newpath);
  • 如果oldpath或newpath是符号链接,则处理的是符号链接文件本身,而不是它指向的文件。

15. 创建和读取符号链接

符号链接是对一个文件的间接指针。符号链接文件的inode指针指向的数据块保存内容即为它指向的目标文件名(因此对于符号链接,文件长度通常是其指向的文件名的实际字节数不包含null),符号链接类似于快捷方式。

符号链接不要求在同一文件系统,即目标文件和符号链接文件可以位于不同的文件系统中

通过symlink和symlinkat函数创建一个符号链接

int symlink(const char *target, const char *linkpath);
int symlinkat(const char *target, int newdirfd, const char *linkpath);

由于open函数跟随符号链接(open的是符号链接文件指向的目标文件),因此如果要读取符号链接文件本身内容,可以使用readlink和readlinkat函数

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz);
//成功返回读取的字节数,失败返回-1

注意在buf中返回的符号链接内容不以null结尾。

readlink函数其实整合了open、read、close函数的所有操作

16. 文件的时间

stat结构体中的st_atime、st_mtime、st_ctime成员即为文件时间信息

字段说明修改该时间的函数例子
st_atime文件数据的最近访问时间read
st_mtime文件数据的最近修改时间write
st_ctimeinode节点状态最近更改时间chmod、chown

需要注意修改时间(st_atime)和状态更改时间(st_ctime)的区别。前者是文件内容最近修改时间,后者是inode节点最后一次修改时间。如更改文件访问权限、更改用户ID、更改链接数等等,这些操作并没有更改文件的实际内容,因为inode节点和文件实际内容是分开存放的。

目录是包含目录项(文件名和相关inode节点编号)的文件。增加、删除或修改目录项会影响到它所在目录的三个时间(st_atime、st_mtime、st_ctime)。例如创建一个新文件会影响到包含此新文件的目录。

但是读写一个文件对所在目录无影响

17. 函数futimens、utimensat、utimes函数

用于修改文件内容的访问、修改时间。

futimens、utimensat函数可以指定纳秒级别的时间戳。用到的数据结构是与stat中相同的timespec

int futimens(int fd, const struct timespec times[2]);
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
struct timespec {
	__kernel_time_t	tv_sec;			/* 秒 */
	long		tv_nsec;		/* 纳秒 */
};
  • 参数times:

    是一个包含两个元素的数组,第一个元素是访问时间,第二个元素是修改时间。这两个时间值是子1970年以来的秒数,不足秒的部分用纳秒表示。

    • 如果times为nullptr,则访问时间和修改时间都设置为当前时间
    • 某个元素的tv_nsec成员设为UTIME_NOW,则相应时间设置为当前时间,忽略tv_sec成员
    • 某个元素的tv_nsec成员设为UTIME_OMIT,则相应时间不变,忽略tv_sec成员
    • 权限检查:当times非空且tv_nsec成员都是UTIME_OMIT,则不执行任何权限检查;否则会执行对该文件的权限检查
  • 参数flags:

    如果设置了AT_SYMLINK_NOFOLLOW标志,则符号链接本身时间会被修改;否则修改的是符号链接文件指向的文件。

也可以通过utimes函数设置访问时间和修改时间

int utimes(const char *filename, const struct timeval times[2]);
struct timeval {
    long tv_sec;        /* seconds */
    long tv_usec;       /* microseconds */
};

times是一个指向包含两个元素的结构体数组指针。数组元素分别是访问时间和修改时间。

18. 函数mkdir、mkdirat、rmdir

使用mkdir和mkdirat函数创建目录

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int dirfd, const char *pathname, mode_t mode);

参数mode是目录文件访问权限。该权限由文件模式创建屏蔽字作为掩码

常见的错误是指定目录文件的读写权限,但是没有指定执行权限。对于目录文件来说,至少应该设置执行权限,以允许访问该目录中的文件

使用rmdir函数删除一个空目录

int rmdir(const char *pathname);

19. 读目录

对某个目录具有访问权限的任一用户都可以读该目录,但是只有内核才能写目录文件。一个目录文件的写权限位和执行权限位决定了在该目录中能否创建或者删除文件

19.1 打开目录文件

使用opendir、fdopendir打开目录文件。

返回一个指向该目录流的指针,该流被定位在目录的第一个条目上。目录中的每一个条目都指向该目录中的一个文件,目录中还有两个特殊的目录条目即".“和”…"分别是该目录文件和上一级目录文件。失败返回NULL

DIR *opendir(const char *name);
DIR *fdopendir(int fd);

19.2 读取目录项

readdir()函数返回一个指向dirent结构的指针,该结构代表了目录流中的下一个目录条目。当到达目录流的末端或发生错误时,返回NULL

struct dirent * readdir(DIR * dirp);
struct dirent {
    ino_t          d_ino;       /* inode number */
    off_t          d_off;       /* not an offset */
    unsigned short d_reclen;    /* 该条目长度 */
    unsigned char  d_type;      /* 文件类型 */
    char           d_name[256]; /* 文件名,最长255个字符 */
};

19.3 关闭目录文件

通过closedir关闭一个由opendir函数打开的目录文件,即关闭参数dir所指的目录流

int closedir(DIR *dir);

19.4 设置目录流读取位置

rewinddir()用来设置参数dir目录流读取位置为原来开头的读取位置

void rewinddir(DIR *dirp);

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

long telldir(DIR *dirp);

seekdir()用来设置参数dir目录流读取位置,在调用readdir()时便从此新位置开始读取。参数offset 代表距离目录文件开头的偏移量

void seekdir(DIR *dir, long loc);

19.5 读取目录文件中目录项信息示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>

int main(){
    DIR* dir = opendir("/home/xtark/wudi/210525");
    struct dirent *d;
    while(d = readdir(dir)){
        printf("the inode number is : %ld\n",d->d_ino);
        printf("the d_off is %ld\n",d->d_off);
        printf("the lengh of this record is %d\n",d->d_reclen);
        printf("the type of this file is %d\n",d->d_type);
        printf("the file name is %s\n\n\n",d->d_name);
    }
}

运行结果:可以看出有两个目录项分别是".“和”…"分别指向该目录文件与上一级目录文件

xtark@xtark-vmpc:~/桌面/linux_study/section8$ gcc test1.c
xtark@xtark-vmpc:~/桌面/linux_study/section8$ ./a.out 
the inode number is : 1045496
the d_off is : 2240378328779100918
the lengh of this record is : 32
the type of this file is : 8
the file name is : test.o


the inode number is : 1045454
the d_off is : 2801854051817173045
the lengh of this record is : 24
the type of this file is : 4
the file name is : .


the inode number is : 1045439
the d_off is : 2838125603082282148
the lengh of this record is : 24
the type of this file is : 4
the file name is : ..


the inode number is : 1045490
the d_off is : 3113460208815849764
the lengh of this record is : 32
the type of this file is : 8
the file name is : test.h

20. 函数chdir、fchdir、getcwd

每个进程都有一个当前工作目录,此目录是所有相对路径名的起点。当用户登录UNIX系统时,其当前工作目录是口令文件/etc/passwd中该用户登录项的第六个字段-用户的起始目录。

注意:假如在目录A中运行了目录B中的程序,那么进程B的工作目录是目录A

通过chdir函数和fchdir函数可以更改调用进程的当前工作目录

int chdir(const char *path);
int fchdir(int fd);

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

char *getcwd(char *buf, size_t size);

其原理是从当前工作目录(.)开始,用…找到其上一级目录,然后读其目录项,直到该目录项中的inode编号与工作目录inode编号相同,这样就从该目录项中获取到了文件名,逐层上移,直到遇到根目录

21. 设备特殊文件

stat中的st_dev成员是文件所在文件系统的设备号(主次设备号)

stat中的st_rdev成员仅在字符特殊文件和块特殊文件有用,表示实际设备的设备号(主次设备号)

每个文件系统所在的存储设备都有其主次设备号。主设备号用来区分不同类型的设备,同一类型的设备具有相同的驱动程序,主设备号用来标识驱动程序。次设备号用来区分同一类型的不同设备,表示具体指向哪个设备。

对于普通文件来讲,其主设备号来标识储存该文件的实际存储设备的主设备号,而次设备号表示所在文件系统的所在分区(可以把同一存储设备的不同分区看成是不同的设备)。

通过major和minor宏来获取设备号中的主、次设备号

struct stat st;
stat("tt.txt",&st);
cout << "主设备号:" << major(st.st_dev) << "次设备号:" << minor(st.st_dev) << endl;
// 打印:主设备号:252 次设备号:1
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-22 11:20:10  更:2021-10-22 11:21:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 20:40:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码