文件和目录
前言
本章讨论文件属性和文件系统内容。除了上一章讨论的普通文件,Linux的文件概念还包括:目录、设备等。在Linux系统中,文件的种类包括:普通文件、目录、符号链接、块设备、字符设备、管道、套接字。
本章讨论的主要内容为普通文件、目录和符号链接。它们的公共特点是,真实的保存在了硬盘中,而其它类型的文件是内核产生的文件,在硬盘中并不存在。
文件属性
通过stat函数或者stat命令可以获得文件属性。
文件属性 | 解释 |
---|---|
dev_t st_dev | 设备号 |
ino_t st_ino | inode编号 |
mode_t st_mode | 访问权限相关 |
nlink_t st_nlink | 硬链接数量 |
uid_t st_uid | 拥有该文件的用户 |
gid_t st_gid | 拥有该文件的组 |
dev_t st_rdev | 设备号 |
off_t st_size | 文件尺寸 |
blksize_t st_blksize | 文件系统的IO尺寸 |
blkcnt_t st_blocks | 占用的block数量,一个block为512字节 |
time_t st_atime | 最后访问时间 |
time_t st_mtime | 最后修改时间 |
time_t st_ctime | 最后文件状态修改时间 |
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { struct stat buf; int ret = stat(".", &buf); if(ret < 0) { perror("stat"); return 0; } if(S_ISREG(buf.st_mode)) { printf("this is regular file "); } else if(S_ISDIR(buf.st_mode)) { printf("this is dir "); } // 测试拥有该文件的账户是否有读权限 if(S_IRUSR & buf.st_mode) { printf("user can read "); } open("a.txt", O_CREAT|O_RDWR, 0777); // access("./a.out", R_OK|W_OK|X_OK); printf("file size is %d ", (int)buf.st_size); getchar(); return 0; }
3.3 文件类型
在前言中,提到文件类型包括七种,在stat结构题中,保存了文件的文件类型属性,它的文件类型属性保存在st_mode中。但是七种类型,只需要3位即可,而st_mode是一个整数,因此它还保存其它内容,如果需要判断一个文件属于何种类型,需要一些宏的帮助。
文件类型属性是只读的属性,无法修改。
3.4 用户和组
Linux是一个多用户操作系统,因此每个文件都有属性,记录着这个文件属于哪个用户/组。
用户/组信息可以被修改,可以通过chown来修改文件所属的用户和组信息。
修改文件所属用户和组,需要root权限。
新文件所属用户和组,是创建该文件的进程所属用户和组。
-
实际账户和有效账户
账户 | 解释 |
---|---|
实际账户 | 登陆系统时的账户 |
有效账户 | 决定进程的访问资源的账户 |
3.5 文件访问权限
文件使用了9个位来表示访问权限,和文件类型一起,保存在st_mode中。此9位分成3组,每组3个位,分别表示读/写/执行权限,而三个组分别表示拥有该文件的账户,拥有该文件的组,其它用户组的权限。如果对应位是1,表示有权限,如果是0表示没有权限。
1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
---|
文件访问权限经常用8进制来表示,比如上表的权限位可以表示为0755,意思是拥有它的账户对这个文件有读/写/执行权限,而拥有它的组有读/执行权限,其它账户对它有读/执行权限。
Linux提供一些宏,来测试文件的权限位
-
可以通过access函数来测试程序是否有访问某文件的权限。
-
创建文件时,可以指定文件的访问权限位,但是会收到umask位影响。
-
可以通过chmod来修改文件的权限位
3.6 其它权限位
3.6.1 SUID
只能对文件设置,如果文件设置了该位,那么该文件被运行时,对应的进程的权限,不是运行该程序账户的权限,而是拥有该用户的权限。
在对文件
未设置SUID的情况下:
如果对文件
设置了SUID,那么:
可以通过chmod u+s
或者chmod u-s
来设置获取去除SUID。
设置SUID可以让一个普通账户拥有它不该有的权限,容易产生安全漏洞。
3.6.2 SGID
可以对文件和目录设置,如果对文件设置,那么它的作用类似SUID,不过影响的是组。
如果对目录设置,那么拷贝到该目录下的文件都会被置位,除非拷贝是带
-p
参数。 在Ubuntu下测试并不如此。
在Ubuntu下设置了目录的SGID之后,在那个目录下创建的文件,拥有者是有效账户,而拥有组是该目录的拥有组。
命令:chmod g+s
chmod g-s
3.6.3 StickyBit
可以对文件或者目录设置,如果对文件设置,那么当这个文件运行并退出后,系统依旧保留该文件对应的映象,这样这个程序再次运行时,就不需要加载再加载了。这个属性的作用并不大,因为它占用了内存。
如果对目录设置,那么表示在该目录下,账户只能删除和修改它自己创建的文件,对于其它账户创建的文件,它不能修改和删除。这个位作用比较大,在一些公共目录,往往有这个属性,比如/tmp
命令:chmod o+t
chmod o-t
总结:
位 | 设置对象 | 设置方法 | 查看 | 效果 |
---|---|---|---|---|
SUID | 文件 | chmod u+s | 如果用户执行权限位为s或者S,则表示SUID有设置 | 当该文件被执行时,进程所拥有的权限是拥有该文件的账户权限 |
SUID | 目录 | 不可设置 | ||
SGID | 文件 | chmod g+s | 如果组执行权限位为s或者S,则表示GUID有设置 | 当执行该文件时,进程所属组是该拥有该文件的组 |
SGID | 目录 | chmod g+s | 同上 | 在该目录中创建文件时,该文件的所属组是目录的所属组 |
StickyBit | 文件 | chmod o+t | 如果其他执行权限位为t或者T,那么该文件有设置StickyBit | 执行该文件并退出后,系统保留该文件占用的一些内存,以便加快下一次的加载运行 |
StickyBit | 目录 | chmod o+t | 同上 | 账户只能修改和删除该目录下属于该账户的文件,不能修改该目录下其他账户创建的文件 |
3.7 文件长度
st_size保存文件的长度,write函数会修改该属性,也可以通过truncate修改文件大小,truncate可以扩大文件或者缩小文件,缩小文件时,文件内容会被删减。
文件大小可以通过ls
,wc -c
,stat
命令获取。
也可以通过fseek
和ftell
函数配合获取,或者直接通过stat
函数获取文件长度。
3.8 文件系统
3.8.1 文件管理
文件系统描述文件在硬盘中的组织,保存在硬盘中的文件只有普通文件、目录、软链接。
为了更加方便的管理持久性文件存储,操作系统一般对硬盘进行有规划的管理,规划包括:
-
分区
-
格式化
文件系统指一个分区内,文件存储的组织方式。
在Linux下,通过mount命令将分区挂载到虚拟文件系统。
3.8.2 inode
一个硬盘分区,被格式化之后,可以认为硬盘被划分成两部分:管理数据和数据。管理数据部分保存着这个分区的分区信息,以及inode表。
inode保存文件的属性信息,stat命令能看到的信息,大部分都是保存在inode里的,一个inode占用128或者256字节,这个依赖具体的文件系统,每当在硬盘上创建一个文件/目录时,系统为这个文件/目录分配一个inode。值得注意的是,文件名,不存在inode中,而是存在文件所在目录的文件内容部分。
3.8.3 数据块
数据部分被简单的、按照等大尺寸的划分成n块,一般每块数据块的尺寸为1024-4096,由具体文件系统决定。
3.8.4 文件
当创建一个文件时,系统为该文件分配一个inode。如果往该文件写数据,那么系统为该文件分配数据块,inode会记录这个数据块位置,当一个数据块不够用时,系统会继续为它分配数据块。
3.8.5 目录
当创建一个目录时,系统为该目录分配一个inode,同时分配一个数据块,并且在该数据块中,记录文件.
和..
对应的inode。
如果在该目录下创建文件newfile
,那么参考上一节内容,会为该文件创建inode,最后将newfile
文件名和它的inode,作为一条记录,保存在目录的数据块中。
如果一个inode被别人引用,那么它的引用计数器会加1。
3.8.6 路径和寻址
Linux系统采用以/划分的路径字符串来寻址文件。
比如命令mkdir testdir
,寻址和操作过程如下图:
思考:为什么mv命令很快,而cp命令很慢,rename如何实现的
补充:分区
查看磁盘信息
磁盘名字 sda sdb ..
分区名字 sda1 sda2 ...
分区
n 创建新分区
p 输出分区信息
w 保存分区信息并退出
分区和挂载
挂载成功之后,对xxyy目录的读写,其实是在/dev/sdb1文件系统中。
开机自动挂载
通过mount挂载的目录是临时的。如果希望开酒就挂载,那么可以将挂载命令写入到/etc/profile。或者修改/etc/fstab文件,/etc/fstab描述了开机需要挂载的文件系统信息。
去除挂载
通过手动umount去除挂载。
3.8.7 硬链接和软链接
硬链接不占用inode,只占用目录项。
软链接占用inode。
创建链接命令ln,硬链接只将对应的inode在目录总增加一个名字,并且将inode的引用计数器+1。
为了可以跨文件系统和对目录进行链接,创建了软链接这种方式。ln -s
思考:为什么硬链接不能跨文件系统,而且不能对目录进行硬链接
3.8.8 虚拟文件系统VFS
内存无法加载硬盘所有内容,因为一般内存比硬盘小,但是在Linux内核中,维护了一个虚拟文件系统,将硬盘的目录结构映射到内存中。这个映射一般只包含已经被打开的文件。
3.9 文件删除
使用unlink命令和函数可以删除一个文件。
如果此时文件已经打开,那么该文件也可以被unlink,但是删除操作不会立即执行,而会被保留到文件关闭时执行。
3.10 文件时间
对文件的访问,会导致文件时间发生变化。系统会自动记录用户对文件的操作的时间戳,以便将来可以查询文件修改时间。
如果需要故意修改,那么可以通过utime函数,修改文件的访问时间和修改时间。
touch
命令也可以将文件的时间修改为当前时间。touch
命令的副作用是,如果参数所指文件不存在,那么创建一个空文件。
当用户进行大规模拷贝时,cp
操作会修改文件的访问时间,如果想提高效率,可以使用-p
选项,避免文件属性的修改,同时加快速度。
#include <sys/types.h> #include <utime.h> int main() { struct utimbuf buf; buf.actime = 0; buf.modtime = 0; utime("a.out", &buf); }
利用utime来修改文件的访问时间和修改时间
3.11 目录操作
3.11.1 创建和删除目录
mkdir
和rmdir
3.11.2 遍历目录
opendir
,closedir
,readdir
,rewinddir
,telldir
,seekdir
遍历目录
seekdir和telldir
#include <dirent.h> #include <sys/types.h> #include <dirent.h> #include <stdio.h> int main(int argc, char* argv[]) { DIR* dir = opendir(argv[1]); struct dirent* entry; while(1) { entry = readdir(dir); if(entry == NULL) break; // linux下,.开头是隐藏文件 if(entry->d_name[0] == '.') continue; printf("%s ", entry->d_name); } closedir(dir); }
3.12 练习
3.12.1 实现文件拷贝,保留文件属性
3.12.2 实现目录打包到文件,将文件解包成目录
3.13 函数和命令
3.13.1 函数
stat/lstat:查看文件属性
chmod:修改文件权限
chown:修改文件的所有者
utime:修改文件时间
unlink:删除文件
link:创建硬链接
symlink:创建软链接
rmdir:删除空目录
mkdir:创建空目录
opendir:打开目录
closedir:关闭目录
readdir:读取一个目录项,并且将目录项指针移到下一项
seekdir:修改目录项指针
rewainddir:重置目录项指针
telldir:获得当前目录向指针
判断权限位宏 S_IRUSR(stat.st_mode)
判断文件类型宏S_ISDIR(stat.st_mode)
3.13.2 命令
stat:查看文件属性
chmod:修改文件权限
chown
unlink:删除文件(不会跟随)
ln:创建链接
mkdir
rmdir
rm
cp
dd:拷贝数据(可以拷贝文件,也拷贝块设备)
wc:计算文件内容的行数、单词数、字节数
which:查找非内置的命令位置
fdisk:查看磁盘信息、分区
mkfs:在分区中创建文件系统(ext2,ext3,ext4, fat32, ntfs, xfs,nfs)
mount:挂载
umount:取消挂载