郴州网站建设软件定制开发制作,咸宁哪个企业没有做网站,小程序制作服务器,长春招聘网官网. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE)…. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE) 之 进程关系 和 守护进程 (八) 一起学 Unix 环境高级编程 (APUE) 之 信号 (九) 一起学 Unix 环境高级编程 (APUE) 之 线程 (十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制 (十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO (十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信IPC (十三) [终篇] 一起学 Unix 环境高级编程 (APUE) 之 网络 IPC套接字 前面两篇博文讲了文件 IO 的基本操作但是它们操作的都是文件本身所存储的有效数据。而文件系统保存文件的时候不仅仅要存储文件内的数据还要存储许多亚数据即文件属性和其它特征数据。这篇博文 LZ 就带领大家讨论文件系统亚数据的操作。 1.stat(2) 1 stat, fstat, lstat - get file status
2
3 #include sys/types.h
4 #include sys/stat.h
5 #include unistd.h
6
7 int stat(const char *path, struct stat *buf);
8 int fstat(int fd, struct stat *buf);
9 int lstat(const char *path, struct stat *buf); stat(2) 函数族是专门用来获取文件的亚数据信息的。系统中 stat(1) 命令就是利用这个函数实现的。 它们会根据文件的路径(path)或是已打开的文件的文件描述符(fd)得到该文件的亚数据并将他们回填到 struct stat 类型的结构体中供调用者使用。 下面介绍下 struct stat 的字段含义 1 struct stat {2 dev_t st_dev; /* ID of device containing file */3 ino_t st_ino; /* inode 号 */4 mode_t st_mode; /* 权限和文件类型位图权限位9位类型3位us 1位gs 1位粘滞位(T位)1位。5 位图是用一位或几位数据表示某种状态。许多要解决看似不可能的问题的面试题往往需要从位图着手。*/6 nlink_t st_nlink; /* 硬链接数量 */7 uid_t st_uid; /* 文件属主 ID */8 gid_t st_gid; /* 文件属组 ID */9 dev_t st_rdev; /* 设备号只有设备文件才有 */
10 off_t st_size; /* 总大小字节数编译时需要指定宏 -D_FILE_OFFSIZE_BITES64否则读取大文件可能导致溢出 */
11 blksize_t st_blksize; /* 文件系统块大小 */
12 blkcnt_t st_blocks; /* 每个 block 占用 512B则整个文件占用的 block 数量。这个值是文件真正意义上所占用的磁盘空间 */
13 // 下面三个成员都是大整数实际使用时需要先转换
14 time_t st_atime; /* 文件最后访问时间戳 */
15 time_t st_mtime; /* 文件最后修改时间戳 */
16 time_t st_ctime; /* 文件亚数据最后修改时间戳 */
17 } 在 Linux 系统中一个文件实际占用了多大的磁盘空间要看 st_blocks 的数量而不是看 st_size 的大小。 一般情况下文件系统的一个 block 的大小为 4KB而每个 st_blocks 是 512B所以一个有效文件站用磁盘空间最小的大小为 8 个 st_blocks。 下面我们用前面学过的函数举个栗子来说明 blocks 这个东西。 1 #include stdio.h2 #include unistd.h3 #include fcntl.h4 5 #include sys/types.h6 #include sys/stat.h7 8 int main (void)9 {
10 int fd -1;
11
12 fd open(tmp, O_RDWR | O_CREAT, 0664);
13 lseek(fd, 1024UL*1024UL*1024UL*5UL-1UL, SEEK_SET);
14 write(fd, , 1);
15 close(fd);
16
17 return 0;
18 } $ gcc -Wall lseek.c -o lseek $ ls -lh tmp-rw-rw-r-- 1 yuhuashi yuhuashi 5.0G 4月 15 23:26 tmp $ stat tmp File: ‘tmp’ Size: 5368709120 Blocks: 8 IO Block: 4096 regular fileDevice: 13h/19d Inode: 222778 Links: 1Access: (0664/-rw-rw-r--) Uid: ( 1000/yuhuashi) Gid: ( 1000/yuhuashi)Access: 2015-04-15 23:28:38.532203798 0800Modify: 2015-04-15 23:28:38.532203798 0800Change: 2015-04-15 23:28:38.532203798 0800 Birth: - 这段代码的功能是生成一个 5GB 大小的空洞文件即内容全部为 \0 的文件。 实现方法很简单首先创建一个文件然后将文件位置指针向后偏移 5GB - 1byte使用 UL 是为了防止数据类型溢出然后用系统调用写入最后一字节关闭文件完成。 为什么最后要使用系统调用 write(2) 函数写入一个字节在上一篇博文中已经解释过了如果不发生系统调用则生成的文件 blocks 为 0即不占用实际的磁盘空间。 用 ls(1) 命令可以看到文件的大小为 5GB但是用 stat(1) 命令查看虽然 Size 是 5368709120但 Blocks 却只有 8这说明文件实际只占用了磁盘空间的 4KB。 那么既然 st_size 表示的不是实际的文件大小那它又有什么用途呢它其实只是文件系统记录的一个属性而已并没有什么特别之处。 类似 stat(2)、fstat(2)、lstat(2) 函数族这种命名的函数在 Linux 系统中有很多这一点值得大家注意总结出了这样的规律以后大家在见到新的函数的时候就能大致做到“见名知义”。 stat(2)一般函数族中函数名称不带前缀的表示普通的用法比如 stat(2) 函数通过文件路径 path 参数读取并解析一个文件的亚数据。 fstat(2)一般函数族中某个函数名称以 f 开头的表示解析的文件来源不再是文件路径 path而是已打开的文件对应的文件描述符了(fd)。 lstat(2)一般函数族中某个函数名称以 l 开头的表示如果 path 参数指定的文件是一个符号链接则不要展开它而是直接处理符号链接文件本身。上面两个函数如果拿到的是一个符号链接则会展开它读取符号链接所指向的真实文件的亚数据。 2.文件类型 通过 struct stat 结构体的 st_mode 成员可以获得文件类型信息。 Linux 系统中的文件共分为 7 种类型dcb-lsp d 目录c 字符设备文件b 块设备文件- 普通文件l 符号链接文件s 套接字文件p 管道文件 是不是很奇怪这些符号是什么其实当你使用 ls(1) 命令查看文件详细信息的时候就应该注意到了权限位的第一个符号就是上面这些符号也就是下面标红的部分。 1 $ touch file1.txt2 $ mkdir files3 $ mkfifo fifo4 $ sudo mknod fileb b 10 205 $ sudo mknod filec c 10 216 $ ln -s /etc/services services7 $ nc -Ul socet8 $ ls -l9 total 0
10 prw-rw-r-- 1 yuhuashi yuhuashi 0 4月 16 16:46 fifo
11 -rw-rw-r-- 1 yuhuashi yuhuashi 0 4月 16 16:24 file1.txt
12 brw-r--r-- 1 root root 10, 20 4月 16 16:30 fileb
13 crw-r--r-- 1 root root 10, 21 4月 16 16:31 filec
14 drwxrwxr-x 2 yuhuashi yuhuashi 40 4月 16 16:24 files
15 lrwxrwxrwx 1 yuhuashi yuhuashi 13 4月 16 16:31 services - /etc/services
16 srwxrwxr-x 1 yuhuashi yuhuashi 0 4月 16 16:39 socet st_mode 是使用位图的形式来保存文件的类型和权限信息的那么位图是什么 位图就是用某一位或几位来表示不同状态的一种手段。如果某一位为 1 则认为某个功能是使能的为 0 则认为对应的功能是 disable 的。 图1 st_mode 位图 其中 0-2 位表示 Other 权限3-5 位表示 Group 权限6-8 位表示 Owner 权限9 位表示粘着位T位10 位表示 GS 位11 位表示 US 位12-14 位表示上面所述的7种文件类型15 位预留。 好吧其实画完了才发现这图的高低位画反了本图的左侧为高位右侧为低位。 st_mode 位图介绍完了下面我们来说说其中的文件类型位。 系统提供了一些宏来操作这个位图其中下面的八个宏专门用于提取 12-14 位。 S_IFMT 0170000 bit mask for the file type bit fields 从 st_mode 中提取出文件类型位其它位清零S_IFSOCK 0140000 socket 套接字文件S_IFLNK 0120000 symbolic link 符号链接文件S_IFREG 0100000 regular file 普通文件S_IFBLK 0060000 block device 块设备文件S_IFDIR 0040000 directory 目录S_IFCHR 0020000 character device 字符设备S_IFIFO 0010000 FIFO 管道文件 1 struct stat info;
2 lstat(file.txt, info);
3 // 假如 st_mode 的值是 01006640 开头表示以八进制形式表示
4 info.st_mode S_IFMT // 0100000 直接取出 file.txt 的文件类型位其它位清零
5 info.st_mode S_IFDIR // 0 获得 file.txt 是否为目录
6 info.st_mode S_IFREG // 0100000 获得 file.txt 是否为普通文件
7 S_ISDIR(info.st_mode) // 0 使用宏判断 file.txt 是否为目录
8 S_ISREG(info.st_mode) // 1 使用宏判断 file.txt 是否为普通文件 系统也提供了七个带参数的宏可以帮我们直接判断文件的类型 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.) 是否为套接字文件 在使用这些宏的时候只需要将 st_mode 作为参数传入即可如果是某个类型的文件它们会返回 1否则返回 0可以方便的在 if 语句中使用。 3.设置用户 ID 和设置组 ID 这一节说的是 st_mode 位图中的 US 位和 GS 位。 可以使用下面的宏获得这两位的状态相信大家都能看得懂用法和上面介绍的文件类型位是一样的具体的就不再赘述了。 S_ISUID 0004000 set-user-ID bit S_ISGID 0002000 set-group-ID bit (see below) 4.文件访问权限 文件访问权限就是 st_mode 位图中的低 9 位。 大家都知道在 Linux 系统中文件的权限分为 3 个组文件属主权限、文件属组权限、其它用户权限而每个组又分为 4 种权限读取(r)、写入(w)、执行(x)、无权(-)。 所以在使用 ls(1) -l 命令时可以得到类似 -rwx-r-xr-x 的权限标志这个标志就是这样来的。 图2 权限标志 就像文件类型一样系统提供了宏供我们方便的从 st_mode 中得到对应的权限位 S_IRWXU 00700 mask for file owner permissions 当 st_mode 这个宏时计算结果将只保留 st_mode 中的文件属主权限其它位全部被清零。注意只是将计算结果中其它位清零并不是把 st_mode 本身的数据清零。 S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission 5.access(2) 1 access - check real users permissions for a file
2
3 #include unistd.h
4
5 int access(const char *pathname, int mode); 测试当前进程对 pathname 文件是否具有 mode 权限成功返回 0失败返回 -1 并设置具体的 errno。 如果 pathname 是符号链接则不会展开而是测试符号链接文件本身。 mode 可以选择 F_OK检测文件是否存在 R_OK检测是否具有读权限 W_OK检测是否具有写权限 X_OK检测是否具有执行权限 6.umask(2) 1 umask - set file mode creation mask
2
3 #include sys/types.h
4 #include sys/stat.h
5
6 mode_t umask(mode_t mask); 用于设定进程文件模式的掩码又称屏蔽字并返回之前的值。umask 值越大权限越低。umask(1) 命令就是用这个函数封装的。 参数 mask 由以下位构成使用按位或运算符指定多个模式 st_mode 屏蔽含义 S_IRUSR S_IWUSR S_IXUSR 属主读 属主写 属主执行 S_IRGRP S_IWGRP S_IXGRP 属组读 属组写 属组执行 S_IROTH S_IWOTH S_IXOTH 其他读 其他写 其他执行 表1 st_mode 掩码 7.chmod(2) 1 chmod, fchmod - change permissions of a file
2
3 #include sys/stat.h
4
5 int chmod(const char *path, mode_t mode);
6 int fchmod(int fd, mode_t mode); chmod(2) 函数族的函数用于更改现有文件的访问权限。看到这两个文件的命名方式是不是觉得似曾相识没错上面我们讲 stat(2) 系统调用的时候就说过这种命名约定fun()、ffun()、lfun()等等见名知义系统中还有很多函数都是遵循这样的约定的。 chmod(1) 命令就是使用 chmod(2) 函数族封装的mode 参数位图与 表1 中的掩码是一致的另外还支持 S_ISUID (US)、S_ISGID (GS) 和 S_ISVTX (T位)。多个位使用按位或的方式进行计算再传参。 8.粘着位 粘滞位是在早期的 Unix 系统中为常用的可执行程序bin设置的这样可以将使用频繁的程序驻留在内存中。现在 Unix 使用了 Page Cache 技术可以使常用的数据块驻留在内存中了所以粘滞位的作用越来越弱化了。 9.chown(2) 1 chown, fchown, lchown - change ownership of a file
2
3 #include unistd.h
4
5 int chown(const char *path, uid_t owner, gid_t group);
6 int fchown(int fd, uid_t owner, gid_t group);
7 int lchown(const char *path, uid_t owner, gid_t group); chown(2) 函数族用来修改文件所有者。修改成功返回 0修改失败返回 -1 并设置 errno。 修改文件所有者这件事在 Linux 中只有超级用户能做这是为什么呢举个简单的栗子 假设系统中有 A 和 B 两个用户而系统对用户磁盘进行了配额限制。如果 chown(2) 这件事任何人都可以做那么当 A 用户的磁盘配额不够用的时候他就可以创建一个具有 0777 权限的文件夹然后将这个文件夹的所有者修改为 B 用户这样自己依然可以使用这个文件夹但是却绕过了磁盘配额的限制将帐算在了 B 用户的头上。 所以为了安全起见 chown(2) 只能由超级用户来做普通用户不仅无法修改别人文件的所有者也无权修改自己文件的所有者。 10.truncate(2) 1 truncate, ftruncate - truncate a file to a specified length
2
3 #include unistd.h
4 #include sys/types.h
5
6 int truncate(const char *path, off_t length);
7 int ftruncate(int fd, off_t length); truncate(2) 函数族的文件用于截断 path 所指定的文件到 length 个字节。 如果想要清空一个文件可以在 open(2) 的时候指定 flags 为 O_TRUNC。 如果 length 参数小于文件之前的长度则 length 个字节后面的数据将被丢弃。 如果 length 参数大于文件之前的长度则在文件末尾用 \0 填充使文件达到 length 指定的长度也就是在文件的尾部创建了一个空洞。 11.古老的 UFS 文件系统 FAT 是大家所熟悉的 Dos 文件系统它是顺序存储的单链表结构所以单链表的缺点就是 FAT 文件系统的缺点。只能从前往后访问不能反向访问而且无法管理大文件。 UFS 文件系统是一个与 FAT 同时代的 Unix 文件系统但是 UFS 文件系统却是与 FAT 完全不同的文件系统它可以很好的支持大文件但小文件的管理却是它的弱点。 下面我们介绍一下 UFS 文件系统。 目录也是一个文件 它存储的内容是一个个的目录项而每一个目录项记录的是目录中文件的 inode 和文件名等信息如图 3 所示。 图3 目录 所以目录中并不是存储文件的数据而是存储文件的信息。 而 UFS 文件系统是如何管理磁盘的呢见下图图4 是参照 APUE 第三版 91 页图4-13 画的。 图4 磁盘、分区和文件系统 画图工具无法输入中文只能画成英文的了而且画上去的东西撤销掉有时候会留下痕迹我也是醉了。。 上图中的蓝框部分是我要详细介绍的部分。什么你说你看不到蓝框好吧图贴上去才发现蓝框不明显我错了但是我不想用这个让我抓狂的工具重新画一次了。蓝框就是倒数第二行的那个框。 下面我把蓝框的部分单独画一幅图出来详细说明一下它是每一个柱面组的组成部分柱面组就是上图第二行的 cylinder。 图5 柱面组 想当年小时候在纸上画画的时候我就经常被墨水弄得手上、纸上都脏兮兮的一片没想到 N 年后用了这款逼(cao)真(dan)的画图软件居然在电脑上能画得跟在纸上画得一样脏兮兮的。。 现在我解释一下图上画的都是什么东西。 上面横着的表格 data block文件数据的逻辑存储块每个块的大小是 4KB 的倍数。 data block bitmap这是一个位图为了提高数据块的查找速度而设计的。位图中的每一位对应 data block 中的一个块当某一位为 1 时表示对应的块被占用为 0 时表示对应的块可用。 inode包含文件的所有信息除了文件名因为文件名是包含在目录文件中的。 inode bitmap与 block 位图一样也是为了提高 inode 的访问速度而设计的。位图中的每一位对应 inode 区域的一块数据当某一位为 1 时表示对应的 inode 被占用为 0 时表示对应的 inode 可用。 图下半部分的表格 inodeinode 中记录数据存储位置的部分。是一个包含 15 个磁盘块地址的数组其中前 12 个地址是直接地址它们直接指向 data block 的数据块。如果使用 12 个数据逻辑存储块无法容纳下一个文件那么就会启用数组中第 13 个元素它不直接指向数据块而是指向一级间接块的。 1 level indirect block一级间接块。包含 256 个磁盘块地址的数组每个元素指向 data block 区域的一个数据逻辑存储块。如果依然无法容纳下一个文件那么将启用 inode 中的第14个元素它指向二级间接块。 2 level indirect block二级间接块。包含 256 个磁盘块地址的数组每个元素指向一个一级间接块数组。如果依然无法容纳下一个文件那么将启用 inode 中的第15个元素它指向三级间接块。 3 level indirect block三级间接块。包含 256 个磁盘块地址的数组每个元素指向一个二级间接块数组。 这回知道为什么 UFS 文件系统不惧怕大文件了吧但是——UFS 文件系统不善于管理小文件这是为什么呢 答案其实很简单因为文件系统中数据块的数量远比 inode 块多得多所以一个 inode 块才能够对应多个 data block。而如果小文件太多会导致在 data block 还有大量剩余空间的情况下就把 inode 耗尽了。 有些人认为 UFS 无法管理小文件其实这是不正确的只是 UFS 的设计策略使它不善于管理小文件而已。 现在知道为什么同时代的文件系统*nix 的要远比 win 的先进了吧。 关于上面这幅图我有一点需要补充说明一下图画得有不明确的地方 左侧 inode 数组中第 12 个元素指向的右侧粉色标识的一级间接块数组而图上中间紫色标识的二级间接块数组中的成员也指向粉色的一级间接块数组。其实紫色二级间接块数组中的成员并非同样指向了 inode 数组中第 12 个元素所指向的位置图上的意思是二级间接块数组中的每个成员都指向了一个一级间接块数组而并非是指向了与 inode[12] 的指向相同的地址左侧黄色标识的三级间接块同理。 12.函数 link(2)、unlink(2) 和 remove(3) 1 link - make a new name for a file2 3 #include unistd.h4 5 int link(const char *oldpath, const char *newpath);6 7 8 unlink - delete a name and possibly the file it refers to9
10 #include unistd.h
11
12 int unlink(const char *pathname);
13
14
15 remove - remove a file or directory
16
17 #include stdio.h
18
19 int remove(const char *pathname); 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) 函数封装的。它在删除文件的时候其实并没有立即将文件的数据块从磁盘上移除而是在被删除的文件没有任何进程引用的时候才将它的数据块释放。 13.rename(2) 1 rename - change the name or location of a file
2
3 #include stdio.h
4
5 int rename(const char *oldpath, const char *newpath); rename(2) 函数用于重命名文件或目录。 14.utime(2) 1 utime - change file last access and modification times
2
3 #include sys/types.h
4 #include utime.h
5
6 int utime(const char *filename, const struct utimbuf *times); utime(2) 函数用于修改文件的最后访问时间戳和最后修改时间戳。 个人感觉这个函数没什么实际用途除非是在干坏事的时候想要擦除脚印避免被别人发现。否则有什么理由能去修改一个文件的最后访问时间和最后修改时间呢 15.mkdir(2)、rmdir(2) 1 mkdir - create a directory2 3 #include sys/stat.h4 #include sys/types.h5 6 int mkdir(const char *pathname, mode_t mode);7 8 9 rmdir - delete a directory
10
11 #include unistd.h
12
13 int rmdir(const char *pathname); 与 mkdir(1) 和 rmdir(1) 命令一样用于创建和删除目录。但是 rmdir(2) 只能删除空白目录如果想要删除非空目录需要自行递归实现。 16.chdir(2) 1 chdir, fchdir - change working directory
2
3 #include unistd.h
4
5 int chdir(const char *path);
6 int fchdir(int fd); 用于改变进程的工作目录参数 path 和 fd 表示要修改到的目标目录。cd(1) 命令就是用这个函数封装的。这个函数在后面我们讲守护进程的时候会再次用到。 有人认为 chdir(2) 这个函数接口不应该被公开因为它可以穿透假根技术(chroot(1))带来一定的安全隐患。 17.getcwd(3) 1 getcwd - get current working directory
2
3 #include unistd.h
4
5 char *getcwd(char *buf, size_t size); 用于获取进程当前工作路径的绝对路径pwd(1) 命令是用该函数封装的。 18.读目录 所谓读目录其实是读取出目录中的文件列表对目录的访问分为两种方式glob(3) 和 xxxxdir(3) 函数族。 我们先来说说使用 glob(3) 的方式访问目录。 1 glob, globfree - find pathnames matching a pattern, free memory from glob()
2
3 #include glob.h
4
5 int glob(const char *pattern, int flags,
6 int (*errfunc) (const char *epath, int eerrno),
7 glob_t *pglob);
8 void globfree(glob_t *pglob); glob(3) 函数参数列表 pattern匹配文件路径的表达式可以是通配符。比如输入 /home/*glob(3) 函数会解析到 /home/ 目录下所有的文件列表。 flags特殊要求。 很多函数都支持特殊要求的设置在遇到可以设定特殊要求的函数时如果没有特殊要求如果它是一个位图就传入0如果它是一个指针就传入 NULL。 glob(3) 函数有很多特殊要求常用的只有以下几个 GLOB_NOSORT不排序解析到哪个就是哪个。默认是排序的使用这个选项可以提高效率。 GLOB_APPEND将多次调用 glob(3) 解析到的结果追加到一起。但是第一次调用的时候不能传入这个参数否则会导致 glob(3) 动态内存分配失败。 GLOB_NOCHECK不检查是否匹配到文件与 GLOB_APPEND 参数一同使用时可以把 pglob 参数看作是一个像 argv 一样的可变长数组可以存储任何字符串。 errfunc当 glob(3) 函数出错的时候的回调函数如果不需要异常处理可以传入 NULL。 pglob将解析的结果回填到这个参数中。 1 typedef struct {
2 size_t gl_pathc; /* Count of paths matched so far */
3 char **gl_pathv; /* List of matched pathnames. */
4 size_t gl_offs; /* Slots to reserve in gl_pathv. */
5 } glob_t; 大家看 gl_pathc 和 gl_pathv 有木有觉得眼熟是不是跟 argc 和 argv 很像其实它们的用法和很相似。 gl_pathv 中存放的是根据 pattern 参数解析出来的所有文件列表的路径而 gl_pathc 就是 gl_pathv 的数量。 一个目录中存放的文件的数量是不确定的所以 gl_pathv 中的成员数量一定也是不确定的那么它一定用到了 malloc(3) 函数族使用动态内存来保存解析出来的数据。根据“谁申请谁释放”的原则 glob(3) 还提供了一个释放它所申请的资源的函数globfree(3)。 globfree(3) 函数的使用就很明了了将 glob(3) 函数产生在 pglob 参数中所申请的内存释放掉。 下面写个简单的栗子用于获取某个目录下所有文件占用磁盘的空间(blocks / 2)模仿 du(1) 命令的实现。du(1) 命令是做了权限处理的而我们的 mydu 没有处理文件权限所以得出的结果可能略有差异。 1 #include stdio.h2 #include stdlib.h3 #include glob.h4 #include unistd.h5 #include string.h6 7 #include sys/types.h8 #include sys/stat.h9
10 #define BUFSIZE 1024
11
12 int path_noloop (const char *path) {
13 // 去除 . 和 ..
14 char *pos strrchr(path, /);
15 if (pos) {
16 if ((!strcmp(/., pos))
17 || (!strcmp(/.., pos))) {
18 return 0;
19 }
20 } else if ((!strcmp(., path)) || (!strcmp(.., path))) {
21 return 0;
22 }
23
24 return 1;
25 }
26
27 int mydu(const char *path)
28 {// /a/b/c/d/f/g
29 static char str[BUFSIZE] ;
30 glob_t globt;
31 int i 0, ret 0;
32 struct stat buf;
33
34 lstat(path, buf);
35
36 // path为目录文件
37 if (S_ISDIR(buf.st_mode))
38 {
39 // 非隐藏文件
40 snprintf(str, BUFSIZE, %s/*, path);
41 glob(str, 0, NULL, globt);
42
43 // 隐藏文件将两次解析的结果追加到一块所以特殊要求使用 GLOB_APPEND
44 snprintf(str, BUFSIZE, %s/.*, path);
45 glob(str, GLOB_APPEND, NULL, globt);
46
47 ret buf.st_blocks;
48
49 for (i 0; i globt.gl_pathc; i) {
50 // 递归目录的时候要注意目录并不是一个典型的树状结构它是具有回路的所以向下递归时遇到 . 和 .. 的时候不要进行递归
51 if (path_noloop(globt.gl_pathv[i])) {
52 ret mydu(globt.gl_pathv[i]);
53 }
54 }
55
56 // 用完了不要忘记释放资源
57 globfree(globt);
58 } else { // path 为非目录文件
59 ret buf.st_blocks;
60 }
61
62 return ret;
63 }
64
65 int main(int argc, char **argv)
66 {
67 printf(%d\n,mydu(argv[1]) / 2);
68
69 exit(0);
70 }
71 这里补充一点关于递归的优化 递归会将在代码段定义的局部变量重复的压栈所以减少局部变量的数量和占用的空间则可以增加压栈的层数。 所以只在递归点之前或递归点之后使用的局部变量可以定义为 static这样可以节省每次递归栈帧的空间横跨递归点的变量不能定义为 static。 访问目录的第二种方式是通过 xxxxdir(3) 函数族的函数。下面我们一一介绍它们。 1 opendir, fdopendir - open a directory2 3 #include sys/types.h4 #include dirent.h5 6 DIR *opendir(const char *name);7 DIR *fdopendir(int fd);8 9
10 readdir - read a directory
11
12 #include dirent.h
13
14 struct dirent *readdir(DIR *dirp);
15
16
17 closedir - close a directory
18
19 #include sys/types.h
20 #include dirent.h
21
22 int closedir(DIR *dirp);
23
24
25 telldir - return current location in directory stream
26
27 #include dirent.h
28
29 long telldir(DIR *dirp);
30
31
32 seekdir - set the position of the next readdir() call in the directory stream.
33
34 #include dirent.h
35
36 void seekdir(DIR *dirp, long loc); 看到它们的名字是不是觉得和文件的访问函数族很像其实他们的使用比文件的操作简单得多。 opendir(3) 函数用于打开一个目录并返回一个指向目录的结构体指针DIR。 readdir(3) 函数用于读取目录项。每次调用会返回一个目录项循环调用就可以读取出一个目录中的所有目录项返回 NULL 表示目录中的目录项读取完毕了。 注意系统调用也有一个 readdir(2) 函数不要弄混了哟这里说的是标准库的 readdir(3)。 closedir(3) 函数用于回收资源它和 opendir(3) 函数是成对使用的。在前两篇博文中学习各种 IO 的过程中相信小伙伴们对于这种方式出现的函数已经不陌生了。 而 telldir(3) 和 seekdir(3) 函数是用来定位目录项位置指针的使用场景很少。 下面这个栗子也是模仿 du(1) 命令运行结果和上面的栗子是一样的不过这次是用 xxxxdir(3) 函数族的函数实现的。 1 #define NEWPATHSIZE 10242 3 int64_t mydudir (const char *path)4 {5 int64_t sum 0;6 struct stat buf;7 struct dirent *de;8 struct stat subbuf;9 char newpath[NEWPATHSIZE];
10
11 if (lstat(path, buf) 0)
12 {
13 perror(lstat);
14 exit(1);
15 }
16
17 if (S_ISDIR(buf.st_mode)) // 文件夹
18 {
19 sum buf.st_blocks;
20 DIR *dirp opendir(path);
21 if (NULL dirp)
22 {
23 perror(opendir);
24 //exit(1);
25 return sum;
26 }
27
28 while (NULL ! (de readdir(dirp)))
29 {
30 if (NULL ! de)
31 {
32 snprintf(newpath, NEWPATHSIZE, %s/%s, path, de-d_name);
33 if (DT_DIR de-d_type) // 文件夹
34 {
35 if (strcmp(., de-d_name) strcmp(.., de-d_name))
36 {
37 strncat(newpath, /, NEWPATHSIZE);
38 sum mydudir(newpath);
39 }
40 }
41 else // 文件
42 {
43 if (lstat(newpath, subbuf) 0)
44 {
45 perror(lstat-sub);
46 //exit(1);
47 return sum;
48 }
49 sum subbuf.st_blocks;
50 }
51 }
52 }
53 closedir(dirp);
54 }
55 else // 文件
56 {
57 sum buf.st_blocks;
58 }
59
60 return sum;
61 } 好了到这里文件和目录部分也结束了有什么问题欢迎小伙伴们在评论中进行讨论。