文件操作4(Beginning Linux Programming 笔记7)
p152 stream 错误: 标准 io 库出错的时候,通常都返回一个 null 指针或者 EOF ,同时把错误的具体信息保存到全局变量 errno 中。这是个很多函数比如 fprintf 都可能改写的变量,只有当出错时,其值才具备参考价值。
extern int errno;
由于文件到达末尾和出错都可能返回同样的值比如 EOF ,库函数提供一些方法来区分。
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);
ferror 用来检查 stream 上是否发生了错误,没有出错返回 0 ,出错返回非 0 值。 feof 检测是否到达了 stream 的末尾,非 0 值表示到达文件末尾。
stream 和 fd 的关系: 每个 stream 都和个 fd 相关联,甚至是 fd 的操作和 stream 的操作在转换后可以混合使用,但是这样没法利用 io 库的 buffer 等特性,如果有相关操作可能造成不可预见的问题。他们之间通过下面的函数可以进行转换:
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);
有些特殊情况下,这些函数会比较实用。比如如果要通过系统调用获得已经打开的 stream 的一些信息,可以通过 fileno 获得 fd ,然后调用系统调用比如 fstat 来获得文件的信息;反过来,也可以把已经打开的 fd 转换为 stream 以后,利用标准库的 buffer 等特性操作文件。
p153 文件和目录维护 下面介绍系统调用一般都有对应 shell 命令可以参照。chmod 和 chown 为系统调用:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
# chown
#include <sys/types.h>
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
unlink 用来删除一个文件在目录中的入口,返回 0 表示删除成功。通常有一个小技巧就是先 open 一个文件,然后进行完一定的操作以后进行 unlink 操作,这就意味着这个文件只在程序执行时存在,为临时文件,进程执行完毕以后,文件自动被关闭。
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);
rmdir 和 mkdir 分别用来删除和创建目录:
#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *path, mode_t mode);
# rmdir
#include <unistd.h>
int rmdir(const char *path);
每个进程都会有一个当前目录,可以通过 chdir 来改变当前目录,也可以通过 getcwd 来获得当前工作目录。
#include <unistd.h>
int chdir(const char *path);
# getcwd
#include <unistd.h>
char *getcwd(char *buf, size_t size);
getcwd 函数会把获得路径名保存到 buffer ,如果路径名大于 size(ERANGE error), 函数返回 null , 如果操作成功则返回 buf 。
p155 遍历目录 目录操作的相关函数是在 dirent.h 中声明的,标准库定义了一个结构 DIR 用来描述目录。指向这种结构的指针被称为 directory stream, 跟 file stream 类似。需要包含的头文件都一样,相关函数原型如下:
#include <dirent.h>
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
long int telldir(DIR *dirp);
void seekdir(DIR *dirp, long int loc);
int closedir(DIR *dirp);
- opendir 打开一个目录(文件),操作成功返回 directory stream ,否则返回 null 指针。
- readdir 返回指定的 directory stream 中的下一个项(文件或子目录)的结构。按照 POSIX 兼容标准,读到最后返回 null ,并且不改变 errno 的值;读取出错时也返回 null ,同时改变 errno 的值。 注意如果该函数执行过程中有其他进程在对该目录下的项进行操作,该函数不保证拿到最新修改后的数据。返回的 dirent 结构中包含下面两项: ino_t d_ino 文件(包括子目录)的 inode 节点; char d_name[]: 文件(包括子目录)名。要检查获得的项是一般文件还是目录,需要调用前面介绍的 stat 函数。
- telldir 用来返回表示当前 directory stream 的位置的整数,一般通过配合下面的 seekdir 使用。
- seekdir 重设读取的 dirp 的位置。
- closedir 用来关闭一个 directory stream ,在遍历目录时,为了保证当前打开的目录数不达到系统限制,一般需要即使关闭打开的 directory stream 。
下面是一个相关函数的使用实例:
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
void printdir(char *dir, int depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dir)) == NULL) {
fprintf(stderr,"cannot open directory: %s\n", dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL) {
lstat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore . and .. */
if(strcmp(".",entry->d_name) == 0 ||
strcmp("..",entry->d_name) == 0)
continue;
printf("%*s%s/\n",depth,"",entry->d_name);
/* Recurse at a new indent level */
printdir(entry->d_name,depth+4);
}
else printf("%*s%s\n",depth,"",entry->d_name);
}
chdir("..");
closedir(dp);
}
/* Now we move onto the main function. */
int main()
{
printf("Directory scan of /home:\n");
printdir("/home",0);
printf("done.\n");
exit(0);
}
p160 错误 error.h 中定义的常用错误包括:
- EPERM 没有操作权限
- ENOENT 操作的文件或者目录不存在
- EINTR 系统调用被中断
- EIO I/O 错误
- EBUSY 设置或者资源繁忙
- EEXIST 文件已经存在
- EINVAL 参数不合法
- EMFILE 打开文件 fd 太多
- ENODEV 没有需要打开的设备
- EISDIR 操作对象是目录
- ENOTDIR 操作对象不是目录
另外还有两个用有的函数用来解读 errno :
char *strerror(int errnum);
#include <stdio.h>
void perror(const char *s);
strerror 用来把错误号 errno 错误信息的文本描述。而 perror 则是自动获取当前的 errno 并用文本描述错误信息,在错误信息前面加上字符串 s 和一个空格。
p162 文件系统中的 /proc 通过系统调用访问 /dev 下的文件没有可以实现和硬件的交互。通常我们都会有一些实用工具能够访问系统硬件设备,比如 hdparm 用来控制磁盘参数, ifconfig 用来配置网络设备的参数。 linux 也提供特殊的文件系统 procfs ,也就是 /proc 下的文件,让我们能够从更高的层面访问驱动程序和系统内核。
/proc 下的某些文件中保存了一些只读的系统信息,比如 /proc/cpuinfo 中保存了系统 CPU 的情况, /proc/meminfo 和 /proc/version 保存了内存以及内核版本的情况。每次这些文件被读取时,系统会去获得最新的相关信息。比如 /proc/net/sockstat 中保存了当前的网络 socket 的使用情况,而其使用情况是变化比较频繁的。
/proc 下也有一些文件可以被写,比如系统同时运行的进程打开的总文件数可以直接从 /proc/sys/fs/file-max 中获得(为内核参数),我们也可以用 echo 80000 >/proc/sys/fs/file-max 的方式来临时修改该值。
/proc 目录下有很多数字命令的目录,每个目录代表一个正在运行的进程,目录名即进程 id,目录下保存了当前进程的相关执行信息,比如进程的路径,进程执行的”当前路径”等。下面的第四章会有详细探讨。注意 fd 子目录保存了当前进程打开的文件信息,具体来说,我能够看到 fd 数字,比如标准设备 0, 1, 2 。
p165 fcntl 和 mmap
fcntl 能够对原始的 fd 进行上面介绍以外的更多操作,比如复制 fd, 获得或设置 fd 的标记(flag),管理文件块等。
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);
不同的操作有不同的第三个参数。比如:
- fcntl(fildes, F_DUPFD, newfd) 用来复制一个 fd ,它根据 fildes 复制出一个 fd 数等于或者大于 newfd 的数,并返回一个新的 fd ,跟 filedes 指向同一个文件。也就是说,该功能很大程度上跟 dup 函数类似。
- fcntl(fildes, F_GETFD) 返回 fcntl.h 中定义的 fd 标记,比如 FD_CLOEXEC 表示 exec 系列函数调用成功以后,fd 是否被关闭。
- fcntl(fildes, F_SETFD, flags) 跟上面的函数对应,是用来设置 fd 标记的,一般常用的也是 FD_CLOEXEC 。
- fcntl(fildes, F_GETFL) 和 fcntl(fildes, F_SETFL, flags) 用类获得和设置文件状态标记以及访问方式(access mode),你可以使用 fcntl.h 中定义的 mask O_ACCMODE 来对比出文件访问方式。open 函数 O_CREAT 时第三个参数的相关 flag 也可以通过这里来设置,注意并不是所有的设置都能够在这里操作,典型的是文件访问权限不能够通过 fcntl 设置。
p166 mmap 是 memery map 的意思,是 linux 内核 2.0 以后支持的功能,它允许我们把文件映射到内存中,这样不同进程能够像操作文件一样来操作内存。 mmap 函数就是用来把一个打开的文件 fd 映射到内存:
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
可以通过指定 off 文件内容中映射到内存的开始位置;addr 为内存的开始地址,为 0 时,会自动分配可用的内存,为了防止一出现访问到使用中的内存,一般使用 0 作为该参数值; len 为分配的内存长度; filedes 为需要映射的文件的 fd; prot 为内存保护标志(访问模式),不能与文件的打开模式冲突,一般使用下列值的或运算:
- PROT_EXEC 内存段(segment)可执行
- PROT_READ 可以被读取
- PROT_WRITE 可以被写入
- PROT_NONE 不可访问
flags 参数控制程序修改内存段内容时影响文件的方式:
- MAP_PRIVATE 内存段为私有(改变只针对该进程)
- MAP_SHARED 内存段共享, 所有改变都会被刷到文件中
- MAP_FIXED 必须指定内存段的地址 addr(###)
msync 将映射内存中的数据写回磁盘。可以指定内存地址和长度以及写回模式:
int msync(void *addr, size_t len, int flags);
munmap 函数用来释放内存:
int munmap(void *addr, size_t len);

回复