1、引言
上一章说明了执行IO操作的基本函数,基本讨论的是围绕普通文件的IO进行--打开一个文件或读写一个文件,这一章将观察文件系统的其他特征和文件的性质。
2、stat、fatat和lstat函数
本章讨论的是下面三个函数以及他们返回的信息:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
给定一个pathname,stat函数返回一个与此命名文件有关的信息结构。fatat函数获得已在描述符fd上打开的文件有关信息,lstat函数类似于stat,但当命名文件是一个符号连接时,lstat返回该符号连接的有关信息。第二个参数是个指针,指向一个结构,以下为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_OFFSIZE_BITES=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函数最多的可能就是ls -l命令。显示一个文件的所有信息。
3、文件类型
之前学linux时接触过文件类型了,这里就简单介绍即可。除了普通文件和目录,还有一些文件类型:
(1)普通文件,常见的文件类型,这种文件包含某种形式的数据,不管其是文本数据还是二进制数据。
(2)目录文件,这种文件包含其他文件的名字以及指向这些文件有关信息的指针。
(3)字符特殊文件,用于系统中某种类型的设备。
(4)块特殊文件,用于磁盘设备。
(5)FIFO,用于进程间通信,也叫管道。
(6)套接口,用于进程间网络通信。
(7)符号链接,这种文件指向另一个文件。
系统提供了一些宏操作来判断一个文件具体类型:使用时只需要将stat结构里的mode传进去即可,方便在if语句中使用。
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
与一个进程有关的ID有六个甚至更多如下:
设置用户ID位和组ID位都包含st_mode值中,这两位可以用S_ISUID和SISGID测试。
5、文件存取许可权
st_mode中也包含了对文件的存取许可权,前面提到的文件类型都有其存取许可权。每个文件有9个存取许可位,因为在linux中一个文件的权限分为三个组,文件属主权限、文件属组权限、其他用户权限,而每个组又有三个权限:分别是读取(r)写入(w)和执行(x)所以使用ls -l时可以得到文件的权限标志。系统也提供了宏方便我们从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
6、新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID,关于组ID,标准允许选择下列之一作为新文件的组ID:
(1)新文件的组ID可以是进程的有效组ID。
(2)新文件的组ID可以是它所在目录的组ID。
7、access函数
#include <unistd.h>
int access(const char *pathname, int mode);
测试当前进程对pathname文件是否有mode权限,成功返回0,失败返回1,并设置具体的errno。如果pathname是符号链接则不会展开而是测试符号链接文件本身。mode可以选择如下:
m o d e 说 明
R _ O K 测试读许可权
W _ O K 测试写许可权
X _ O K 测试执行许可权
F _ O K 测试文件是否存在
8、umask函数
#include <sys/types.h>
#include <sys/stat.h>
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 |
其他读 其他写 其他执行 |
9、chmod和fchmode函数
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
chmod函数族的函数用于更改现有文件的访问权限,chmod在指定的文件上操作,fchmod在已经打开的文件上操作。为了改变一个文件的许可权位,进程的有效用户ID必须等于文件的所有者,或者该进程必须具有超级用户许可权。参数mode如下的常数的逐位或运算。chmod命令就是用chmod函数封装的。
10、粘住位
在unix早期版本,有一个位称为粘住位,如果一个可执行文件这一位被设置,那么该程序第一次执行并结束时,程序的正文的一个文本保存在交换区。这使得下次执行该程序时能较快的将其装入内存区。但是unix现在都使用page cache技术,可以使常用的数据驻留在内存中,所以粘住位的作用越来越弱了。
11、chown、fchown、lchown函数
chown用来更改文件的用户ID和组ID。
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
chown函数族用来修改文件所有者,修改成功返回0,失败返回-1,并设置errno。修改文件所有者只能是超级用户做。三个函数操作类似,只是针对对象不一样。防止用户改变文件所有者从而拜托磁盘空间对其的限制。
12、文件长度
stat结构成员st_size包含以字节为单位的改文件的长度,此字段只对普通文件、目录文件和符号链接有意义。对于普通文件文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。对于目录文件,文件长度通常是一个数,对于符号链接,文件长度是文件名中的实际字节数。
13、文件截短
有时候需要在文件尾端截去一些数据以缩短文件,就需要函数truncate和ftruncate。
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
truncate函数族文件用于截断path所指定的文件到length个字节,如果想要清空一个文件可以在open时指定flags为O_TRUNC。如果length参数小于文件之前的长度那么length之后的数据被丢弃,如果大于,是系统而定,一般是在文件末尾用\0填充,使文件达到length的长度。
14、文件系统
不详细看了,之前看过。大致都是用node存文件信息,用block存文件实际内容。
15、link、unlink、remove和rename函数
#include <unistd.h>
#include <stdio.h>
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
int remove(const char *pathname);
int rename(const char *oldpath, const char *newpath);
linux中ln命令是用来创建文件连接的,ln产生硬链接。ln -s产生符号链接。两者的区别,硬链接的inode没有改变,inode号是文件的唯一标识,所以硬链接没有产生新文件。符号链接产生了新的inode号,产生了新的文件,但是不分配硬盘块。符号链接可以跨分区,可以为目录文件建立符号链接。link和ulink函数用于创建和删除符号链接。ulink删除目录项,并且将pathname所引用的文件连接计数减1,如果该文件还有其他连接,仍可以通过其他链接获取文件的数据。只有当文件的连接计数达到0时,该文件的内容才可以被删除,另外一个最值删除文件内容的条件-只要有进程打开了该文件,其内容也不能删除。ulink这种特性经常被程序用来确保即使是在程序崩溃时,它创建的临时文件也不会遗留下来。用open或creat创建一个文件,然后立即调用unlink,因为文件是打开的,所以不会删除内容,只有进程关闭改文件会终止时,该文件内容才会被删除。
remove相当于rm命令,是使用unlink,rmdir函数封装的,它在删除文件的时候其实没有将文件的数据块从磁盘上移除,而是在被删除的文件没有任何进程引用时才将数据块释放。rename函数用于重命名文件或目录。对于文件remove的功能与unlink相同,对于目录remove的功能与rmdir相同。
文件或目录用rename函数更名。
16、符号链接
符号链接是对一个文件的间接指针,与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点,引进符号链接是为了避免一些硬链接的限制:a)硬链接通常要求连接和文件处于同一文件系统。b)只有超级用户才能创建到目录的硬链接。而符号链接以及它指向什么没有文件系统限制,任何用户都能创建指向目录的符号链接。
当使用以名字引用一个文件的函数时,应该了解该函数是否处理符号链接功能,也就是是否跟随符号链接连接到它所连接的文件。
17、symlink和readlink函数
#include <unistd.h>
int symlink(const char *target, const char *linkpath);
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
symlink函数创建一个指向target的新目录项linkpath,创建此符号连接时,并不要求target存在,并且两个也不需要在同一个文件系统中。readlink函数组合了open read close 所有操作,如果此函数成功,则返回读入buf的字节数。在buf中返回的符号链接的内容不以null字符终止。
18、文件的时间
每个文件有三个时间字段,如下表:
19、utime函数
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
一个文件的存取和修改时间可以用utime函数更改,所用结构如上图。
20、mkdir和rmdir函数
//mkdir - create a directory
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
//rmdir - delete a directory
#include <unistd.h>
int rmdir(const char *pathname);
与mkdir和rmdir命令一样,用来创建和删除目录,rmdir只能删除空目录,如果要删除费空目录,需要自行递归实现。对目录而言至少需要有执行权限。
21、读目录
对目录的写和执行权限决定了能否在该目录中创建新文件以及删除文件,并不表示能写目录本身。所谓的读目录其实是读出目录中文件列表,对目录的访问分两种方式:glob和xxxdir函数族,这里主要介绍xxxdir函数族。
//opendir, fdopendir - open a directory
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
//readdir - read a directory
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
//closedir - close a directory
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
//telldir - return current location in directory stream
#include <dirent.h>
long telldir(DIR *dirp);
//seekdir - set the position of the next readdir() call in the directory stream.
#include <dirent.h>
void seekdir(DIR *dirp, long loc);
opendir函数用于打开一个目录,返回一个指向目录的结构体指针。
readdir函数用于读取目录项,每次调用返回一个目录项,循环调用就可以读出一个目录中所有目录项,返回NULL标书目录中目录项读取完毕。
closedir用于回收资源和open成对使用。而telldir和seekdir用来定位目录项位置指针。
22、chdir、fchdir和getcwd函数
// chdir, fchdir - change working directory
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
char *getcwd(char *buf, size_t size);
chdir和fchdir用于改变进程的工作目录,参数path和fd表示要修改的目标目录。cd命令就是用这个函数封装的。getcwd函数用于获取当前工作路径的绝对路径,pwd命令就是用该函数封装的。
23、特殊块设备
st_dev和st_rdev字段很容易引起混淆。每个文件系统都是由主次设备号为人所知,设备号所用的数据类型是基本系统数据类型dev_t。通常可以使用定义的宏,major和minor来获取主次设备号。系统中每个文件名的st_dev是文件系统的设备号,只有字符特殊文件和块特殊文件才有st_rdev值,这个值包含实际设备的设备号。
24、sync和fsync函数
#include <unistd.h>
void sync(void);
int fsync(int fd);
sync将所有修改过的块的缓存排入写队列,然后返回,并不等待实际IO操作结束。系统精灵进程一般隔30秒调用一次sync函数,保证定期刷新内核的块缓存,命令sync也调用sync函数。fsync只引用单个文件,有文件描述符指定。等待IO结束返回。可用于数据库这样的应用,确保修改过的块立即写到磁盘上。
25、文件存取位小结
已经说明了所有文件的存取许可位,某些位有多种用途。如下表:
26、小结
围绕stat函数,介绍stat结构体中的每一个成员。对unix文件的各个属性有所了解。