1.文件系统
把一个磁盘分成一个或多个分区。每个分区可用包含一个文件系统
文件系统是,一组规则,规定对文件的存储及读取的一般方法。文件系统在磁盘格式化过程中指定。
常见的文件系统有:fat32 ntfs exfat ext2 、ext3 、ext4
inode是固定长度的记录项,包含有关文件的大部分信息
inode
其本质为结构体,存储文件的属性信息。如:权限、类型、大小、时间、用户、盘块位置……也叫作文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
- 每个inode中有一个链接数,其值为指向该inode的目录项数(上图中有两个目录项指向同一个inode)。只有当链接数减为0时,才删除该文件。链接数包含在stat结构的st_nlink成员中(POSIX.1常量LINK_MAX指定了一个文件链接数的最大值)。这种链接类型称为硬链接
- 另外一种链接类型称为符号链接。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字
- inode包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat结构中的大多数信息都取自inode。只有2项重要数据存放在目录项中:文件名和inode号
- 因为目录项中的inode编号指向同一文件系统中的相应inode,一个目录项不能指向另一个文件系统的inode
- 在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有inode的新目录项,并删除老的目录项。链接数不会改变
下图为在一个目录下创建一个目录testdir,注意testdir所在目录,以及新建目录testdir的inode链接数:
dentry
目录项,其本质依然是结构体,重要成员变量有两个 {文件名,inode,...},而文件内容(data)保存在磁盘盘块中。
也就是上一点inode图中包含inode结点编号和文件名的那一块
不同的文件有相同的inode号实现存储在同一块分区内,也就是硬链接
硬链接其中就要求到要处在同一块分区内,并且针对的是文件
2.文件操作
stat函数
使用stat函数最多的地方可能就是ls -l命令获得有关一个文件的所有信息
获取文件属性,存在buf中(从inode结构体中获取)
int stat(const char *path, struct stat *buf);
成返回0;失败返回-1 设置errno为恰当值。
参数1:文件路径
参数2:inode结构体指针,存放文件属性 (传出参数)
文件属性将通过传出参数返回给调用者。
练习:使用stat函数查看文件属性
【stat.c】
#include<syss/types.h>
#include<sys/stat.h>
#include<unistd.h>
文件信息结构用一个结构体stat表示,实际定义可能随具体实现有所不同,但基本形式是:
struct stat{
mode_t st_mode; /*文件模式字,包含有文件类型、ID和读写权限位信息*/
ino_t st_ino; /* inode号 */
dev_t st_dev; /* 设备号(文件系统) */
dev_t st_rdev; /* 特殊文件的设备号 */
nlink_t st_nlink; /* 链接数 */
uid_t st_uid; /* 所有者的用户ID */
gid_t st_gid; /* 组所有者的ID */
off_t st_size; /* 字节大小,用于一般文件 */
struct timespec st_atime; /* 最后一次访问时间 */
struct timespec st_mtime; /* 最后一次修改时间 */
struct timespec st_ctime; /* 最后一个文件状态改变的时间 */
blksize_t st_blksize; /* 磁盘块(block)大小 */
blkcnt_t st_blocks; /* 分配的磁盘块(block)数量 */
};
stat结构体中的st_size属性:
stat结构体中的st_mode属性:
st_mode属性:
- 普通(一般)文件
- 目录文件
- 字符特殊文件:这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变(系统中的所有设备要么是字符特殊文件,要么是块特殊文件)
- 块特殊文件:这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行
- FIFO:这种类型的文件用于进程间通信,有时也称为命名管道
- 符号链接:这种类型的文件指向另一个文件
- 套接字:这种类型的文件用于进程间的网络通信
提供了一些宏定义供于查看文件是否为目录等
stat函数在查看链接的时候会穿透,比如给一个文件起软链接后,去查看这个软链接对应的链接名,会把它当作一个文件,而不是显示它是一个链接
lstat函数
类似于stat。但是当pathname为一个符号链接时,返回符号链接(而不是由该符号链接引用的文件)的有关信息,存在buf中
【get_file_type.c】
int lstat(const char *path, struct stat *buf);
成返回0;失败返回-1 设置errno为恰当值。
文件类型判断方法:st_mode 取高4位。 但应使用宏函数:
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.)
穿透符号链接:stat:会;lstat:不会
不穿透符号链接其实在其他方面也见到过:
给一个文件 f.c 起符号链接 f.link 后,用cat f.link 或 vim f.link 都能对 f.c 进行操作(穿透符号链接),但是如果用 ls -l f.link 会发现不穿透符号链接,会把 f.c 的文件信息优先显示出来而不是显示 f.link 文件的信息
系统和命令其实就是实现与被实现的关系
另外一种方式去获取文件的类型:(了解)
用按位与的方式,而不是直接采用宏定义去判断
IFMT实际上就是16位的位图掩码,来表示文件的权限类型
特殊权限位置
黏住位
即S_ISVTX
- 在UNIX尚未使用请求分页式技术的早期版本中,S_ISVTX位被称为粘着位
- 后来的UNIX版本称它为保存正文位
用途
- 以前,如果一个可执行文件设置了该位,当程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区。这使得下次执行该程序时能较快地将其装载入内存(原因是:通常的UNIX文件系统中,文件的各数据块很可能是随机存放的,相比较而言,交换区是被作为一个连续文件来处理的)
- 现在,系统扩展了粘着位的使用范围,Single UNIX Specification允许针对目录设置粘着位。如果对一个目录设置了该位,只有满足下列2个情况,才能删除或重命名该目录下的文件:
对该目录具有写权限
- 满足下列条件之一:
- 拥有此文件
- 拥有此目录
- 是超级用户
目录/tmp和/var/tmp就是设置粘着位的典型候选者
setUID位
进程有两个ID:
- EID(有效用户ID),表示进程履行哪个用户的权限。
- UID(实际用户ID),表示进程实际属于哪个用户。
多数情况下,EID和UID相同。但是,当文件的setID被设置后两个ID则有可能不一样。
例如:当进程执行一个root用户的文件,若该文件的setID位被设置为1, 那么执行该文件时,进程的UID不变。EID变为root,表示进程开始履行root用户权限。
access函数
测试指定文件是否存在/拥有某种权限。
int access(const char *pathname, int mode);
返回值:成功/具备该权限:0;失败/不具备 -1 设置errno为相应值。
参数2:R_OK、W_OK、X_OK
通常使用access函数来测试某个文件是否存在。F_OK
chmod函数
修改文件的访问权限
int chmod(const char *path, mode_t mode);
成功:0;失败:-1设置errno为相应值
int fchmod(int fd, mode_t mode);
truncate函数
截断文件长度成指定长度。常用来拓展文件大小,代替lseek。
int truncate(const char *path, off_t length);
成功:0;失败:-1设置errno为相应值
int ftruncate(int fd, off_t length);
link函数
思考,为什么目录项要游离于inode之外,画蛇添足般的将文件名单独存储呢??这样的存储方式有什么样的好处呢?
其目的是为了实现文件共享。Linux允许多个目录项共享一个inode,即共享盘块(data)。不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件。
link函数,可以为已经存在的文件创建目录项dentry(硬链接)。
int link(const char *oldpath, const char *newpath);
成功:0;失败:-1设置errno为相应值
注意:由于两个参数可以使用“相对/绝对路径+文件名”的方式来指定,所以易出错。
如:link("../abc/a.c", "../ioc/b.c")
若a.c,b.c都对, 但abc,ioc目录不存在也会失败。
mv命令既是修改了目录项,而并不修改文件本身,可以通过link和unlink来实现mv
硬链接实际上就是创建了一个目录项,与原文件具有相同的inode节点号,指向了同一盘块位置
unlink函数
删除一个文件的目录项;
int unlink(const char *pathname);
成功:0;失败:-1设置errno为相应值
练习:编程实现mv命令的改名操作
【imp_mv.c】
注意Linux下删除文件的机制:不断将st_nlink -1,直至减到0为止。无目录项对应的文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定)
因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
【unlink_exe.c】
实现mv:
比如想实现像mv将a.c改名为b.c:mv ./a.c ./b.c
link("./a.c", "./b.c");
unlink("./a.c");
unlink后目录项被释放掉,此时打开另一个终端去cat查看下该文件是查找不到的,但是unlink后的write是仍可以写的,只不过写入到stat相关的缓冲区当中,因此不会将if语句中的内容打印出来,此时进程还在继续当中,temp.txt文件还没被释放掉
隐形回收
当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。
symlink函数
创建一个符号链接
int symlink(const char *oldpath, const char *newpath);
成功:0;失败:-1设置errno为相应值
readlink函数
读取符号链接文件本身内容,得到链接所指向的文件名。
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
成功:返回实际读到的字节数;失败:-1设置errno为相应值。
在终端窗口中也适用:
ln -s test.c test.soft
cat test.soft
>>>这是一个目录
readlink test.soft
>>>(显示软链接所指向的目录位置)
rename函数
重命名一个文件。
int rename(const char *oldpath, const char *newpath);
成功:0;失败:-1设置errno为相应值
3.目录操作
工作目录:“./”代表当前目录,指的是进程当前的工作目录,默认是进程所执行的程序所在的目录位置。
目录操作的函数是在man的第三卷,是库函数
getcwd函数
获取进程当前工作目录(卷3,标库函数)
char *getcwd(char *buf, size_t size);
成功:buf中保存当前进程工作目录位置。失败返回NULL。
chdir函数
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)
用户登录到UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第6个字段——用户的起始目录
- 当前工作目录是进程的一个属性(所以如果调用chdir修改当前目录,只影响调用函数的进程本身)
- 起始目录则是登录名的一个属性
改变当前进程的工作目录
int chdir(const char *path);
成功:0;失败:-1设置errno为相应值
文件、目录权限
目录文件也是“文件”,其文件内容是该目录下所有子文件的目录项dentry,可以尝试vim打开一个目录。
R | W | X | |
文件 | 文件的内容可以被查看 cat、more、less... | 内容可以被修改 vi、>... | 可以运行产生一个进程 ./文件名 |
目录 | 目录可以被浏览 ls、tree... | 创建、删除、修改文件 mv、touch、mkdir... | 可以被打开、进入 cd |
目录设置黏住位:若有w权限,创建不变,删除、修改只能由root、目录所有者、文件所有者操作。
查看目录的详情:
ls -ld test.dir/
opendir函数
根据传入的目录名打开一个目录 (库函数)
DIR * 类似于 FILE *
DIR *opendir(const char *name);
# 成功返回指向该目录结构体指针,失败返回NULL
# 参数支持相对路径、绝对路径两种方式:例如:打开当前目录:① getcwd() , opendir() ② opendir(".");
closedir函数
关闭打开的目录
int closedir(DIR *dirp);
# 成功:0;失败:-1设置errno为相应值
readdir函数
读取目录(库函数)
struct dirent *readdir(DIR *dirp);
# 成功返回目录项结构体指针;失败返回NULL设置errno为相应值
# 需注意返回值,读取数据结束时也返回NULL值,所以应借助errno进一步加以区分。
struct 结构体:
struct dirent {
ino_t d_ino; inode编号
off_t d_off;
unsigned short d_reclen; 文件名有效长度
unsigned char d_type; 类型(vim打开看到的类似@*/等)
char d_name[256];文件名
};
实现ls不打印隐藏文件。每5个文件换一个行显示。【imp_ls2.c】
. 和 .. 是隐藏文件
rewinddir函数
回卷目录读写位置至起始。
void rewinddir(DIR *dirp);
返回值:无
telldir/seekdir函数
获取目录读写位置
long telldir(DIR *dirp);
成功:与dirp相关的目录当前读写位置。失败-1,设置errno
修改目录读写位置
void seekdir(DIR *dirp, long loc);
返回值:无
参数loc一般由telldir函数的返回值来决定。
4.递归遍历目录
查询指定目录,递归列出目录中文件,同时显示文件大小
ls -R
ls-R.c --> 通过命令行在运行的时候直接传入要递归的目录名
1.判断命令行参数,获取用户要查询的目录。
如果用户没有输入目录名(ls -R ==> ls -R .),进行转化, argc == 1 --> ./
2.判断用户指定的是否是目录。stat S_ISDIR() --> 封装函数 isFile
3.读目录
opendir(dir)
while(reddir(dir)){
普通文件:直接打印
目录:
拼接目录访问绝对路径。sprintf(path,"%s/%s",dir,d_name)
# 递归调用自己传入的路径应当是绝对路径比较稳妥
递归调用自己。 --> opendir(path)
readdir closedir
}
#include<unistd.h>
#include<sys/stat.h>
#include<dirent.h>
#include<stdio.h>
#include<stdblib.h>
#include<string.h>
#define PATH_LEN 256
void fetchdir(const char *dir, void (*fcn)(char *)) //该函数被调用则表示输入的是目录
{
char name[PATH_LEN];
struct dirent *sdp;
DIR *dp;
if((dp = opendir(dir)) == NULL){
fprintf(stderr, "fetchdir:can't open %s\n",dir);
return;
}
while((sdp = readdir(dp)) != NULL){
if(strcmp(sdp->name,".") == 0 || strcmp(sdp->name,"..") == 0){ // 防止出现死循环
continue;
}
if(strlen(dir) + strlen(sdp->d_name) + 2 > sizeof(name)){
//要求文件名+路径长度不大于256
fprintf(stderr, "fetchdir: name %s %s too long\n",dir, sdp_d_name);
}else{
sprintf(name, "%s/%s",dir,sdp->d_name); //进行拼接成绝对路径
(*fcn)(name); //回调函数调用进入下一论判断
}
}
}
void ifFile(char *name)
{
struct stat sbuf;
if(stat(name, &sbuf) == -1) //文件名无效
{
fprintf(stderr, "isfile:can't access %s\n", name);
exit(-1);
}
if((sbuf.st_mode & S_IFMT) == S_IFDIR) // 判定是否为目录
{
fetchdir(name,isfile); //回调函数
}
printf("%10ld %s\n", sbuf.st_size,name); //普通文件,直接打印文件名
}
int main(int argc, int *agrv[])
{
if(argc == 1)
isFile(".");
else
while(--argc > 0) //可一次查询多个目录
isFile(*++argv); //循环调用该函数处理各个命令传入的目录
return 0;
}
5.重定向
dup 和 dup2函数
int dup(int oldfd);
成功:返回一个新文件描述符;失败:-1设置errno为相应值
int dup2(int fd1, int fd2);
# 将旧的描述符拷贝到新的,指向同一块空间,成功则返回一个新的描述符fd2
o 如果 fd2已经是被打开的文件描述符且不等于fd,则先将其关闭,然后再打开(注意关闭再打开是一个原子操作)
o 如果 fd2等于fd,则直接返回fd2(也等于fd),而不作任何操作
cat test.c > new.c
# 将test.c内容重定向输入到new.c文件中
---> dup2(fd,STDOUT_FILENO)
dup:
dup2:
fcntl实现dup: