unix编程以及xv6系统浅谈(二)文件和目录

411 阅读6分钟

2. 文件和目录

2.1 文件类型

​ 文件类型主要有下面几种

​ (1)普通文件

​ (2)目录文件

​ (3)块特殊文件

​ 提供对设备带缓冲的访问,每次访问固定长度

​ (4)字符特殊文件

​ 提供对设备不带缓冲的访问,每次访问非固定长度

​ (5)FIFO

​ 管道

​ (6)套接字

​ 用于网络间通信

​ (7)符号链接

​ 软链接,里面存放的是指向的文件路径

访问文件信息的主要函数是下面几个,主要的操作方式是从文件指向的inode中提取出需要的信息来写入一个struct stat结构

struct stat{
    mode_t st_mode;	//文件类型&文件访问权限
    ...
    nlink_t st_nlink;	//(硬)链接的数量
    uid_t st_uid;	//用户id
    gid_t st_gid;	//组id
    off_t st_size;	//文件的大小
    ...
}
//出错返回-1,否则返回0
int stat(const char* pathname,struct stat* buf)
int fstat(int fd,struct stat* buf)
    以上两个函数是会跟踪符号链接的
int lstat(const char* pathname,struct stat* buf)
    这个函数不跟踪符号链接
int fstatat(int fd,const char* pathname,struct stat* buf,int flag)
    若path为绝对路径,则fd失效,否则fd为指定目录,fd为AT_FDCWD时为当前进程的工作目录
    flag:AT_SYMLINK_NOFOLLOW标志设置的话则不会跟随标志

​ 使用一些宏可以确定文件类型,对stat结构中的st_mode进行判断的宏主要有

文件类型
S_ISREG() 普通文件
S_ISDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 管道或者FIFO
S_ISLNK() 符号链接
S_ISSOCK() 套接字

​ 同时,一些IPC也可以表示为文件,对其的判断直接对stat的指针操作

对象的类型
S_TYPEISMQ() 消息队列
S_TYPEISSEM() 信号量
S_TYPEISSHM() 共享存储对象

2.2 文件访问权限

​ 一个文件的访问权限分为root用户权限,组访问权限,当前用户访问权限

​ 通常,读权限表示为4,写为2,执行为1

​ rwx=7,rw=6,x=1,通过并的方式来给出文件访问权限

​ 注意,新的文件创建的时候,系统会根据当前的文件模式创建屏蔽字来屏蔽相关的文件权限,即根据umask值来取反并与。

​ st_mode中也包含了文件的访问权限,可以对它&上一个相关的权限来判断是否拥有该权限

​ 还有一些规则是需要注意的

​ (1)打开任意类型的文件时,对必须对它的祖先目录具有执行的权限

​ (2)在一个目录中创建一个文件,必须对该文件具有写权限和执行权限

​ (3)删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限,对该文件本身并不需要有读写权限。

​ (4)创建的新的文件一般情况下是拥有进程的相关权限

mode_t uamsk(mode_t cmask)
    返回值:之前的文件模式创建屏蔽字
    cmask:一般是由若干的文件权限字‘或’起来
int chmod(const char* pathname,mode_t mode)
int fchmod(int fd,mode_t mode)
int fchmodat(int fd,const char *pathname,mode_t mode,int flag)
    返回值:成功则返回0,否则返回-1
      	fd:	AT_FDCWD
      	flag:	AT_SYMLINK_NOFOLLOW
int chown(const char* pathname,uid_t owner,gid_t group)
int fchown(int fd,uid_t owner,gid_t group)
	上面两个跟随符号链接
int fchownat(int fd,const char* pathname,uid_t owner,gid_t group,int flag)
    fd:	AT_FDCWD
    flag:	AT_SYMLINK_NOFOLLOW
int lchown(const char* pathname,uid_t owner,gir_t group)
    会修改符号链接

2.3 文件系统

​ 目录项->inode->块

​ 删除一个文件使用的是unlink,即删除一个目录项,只有当指向这个文件的目录项全部删除,才会彻底删除这个文件,即st_nlink为0时

​ 硬链接:一个目录项指向inode这种称为硬链接

​ 软链接(符号链接):类似于快捷方式,里面实际存放的时一个文件路径

​ 对于st_size,对于非符号链接,实际表明的是一个文件的大小,而对于符号链接,则包含的时指向的文件的路径的长度,即"文件名"的长度

​ 任何一个文件可以有多个目录项指向其i节点,创建一个指向现有文件的链接(硬)的方法如下

int link(const char* existpath,const char* newpath )
int linkat(int efd,const char* existpath,int nfd,const char* newpath,int flag)
    efd,nfd:	AT_FDCWD
    flag:	AT_SYMLINK_FOLLOW
    		符号链接跟随,即新建立一个链接指向(老的符号链接指向的文件)
//删除一个硬链接,如果文件名为一个符号链接,则不跟随符号链接!
int unlink(const char* pathname)
        当文件计数为0时,则彻底删除这个文件(不可以为目录)
int unlinkat(int fd,const char* pathname,int flag)
        fd:	AT_FDCWD
        flag:	AT_REMOVEDIR	可删除目录,但是必须为空目录(带. ..)
//可以删除文件和目录都可以,但是目录必须为空
int remove(const char* pathname)

​ unlink只有在文件计数为0时并且没有进程打开这个文件的时候才会立即删除这个文件,否则会等待到进程结束的时候再删除

​ 利用这种特性,可以创建临时文件,比如creat一个文件之后再unlink它,然后可以保证如果进程崩溃也不会留下这个文件

​ 至于文件的重命名操作,一般是新建一个目录项,再指向inode节点,然后删除那个目录项(老)

int rename(const char* oldname,const char* newname)
int renameat(int oldfd,const char *oldname,int newfd,const char* newname)
  	oldname:
			文件:
					newname不能为目录,并且如果newname重名,则删除重名的那新建一个出来
			目录:
					newname必须为一个目录,并且如果newname重名,则重名的那个只有为空才可以删除,同时newname不可以包含老的oldname为祖先路径
			符号链接不跟随!!!		

2.4 符号链接

​ 硬链接要求必须在同一个文件系统中

​ 符号链接不要求在同一个文件系统中

​ 只有超级用户才能创建指向目录的硬链接,但是任何用户都可以创建指向目录的符号链接

创建和读取符号链接的函数如下

//创建符号链接
int symlink(const char *actualpath,const char* sympath)
    并不要求actualpath已经存在,并且不要求在同一个文件系统中
int symlinkat(const char* actualpath,int fd,const char* sympath)
    fd:		AT_FDCWD
//读取符号链接(open会跟随,所以不可以!)
ssize_t readlink(const char* pathname,char* buf,size_t bufsize)
ssize_t readlinkat(int fd,const char* pathname,char* buf,size_t bufsize)
       	成功则返回读取的字节数,注意读入buf的内容不以NULL结尾!

2.5 目录

​ 创建删除目录的函数如下

int mkdir(const char* pathname,mode_t mode)
int mkdirat(int fd,const char* pathname,mode_t mode)
    创建一个新的空目录,会自动创建.和..,文件访问权限会由进程的文件模式创建屏蔽字修改
int rmdir(const char* pathname)	//删除空目录
    链接计数为0并且无进程打开才可以彻底删除,否则若有进程打开则先删除其.和..,并在此目录中无法创建文件,否则会出问题!

​ 打开和读取目录的函数如下

DIR* opendir(const char *pathname)
DIR* fdopendir(int fd)
    返回一个”目录描述符“,注意fd中的偏移量会起作用
struct dirent{
	...
    char d_name[];
}
struct dirent* readdir(DIR* dp)
	读取目录中的一个目录项,主要用到的还是dirent结构中的d_name[]文件名

​ 更改当前的工作目录的操作如下

int chdir(const char* pathname)
int fchdir(int fd)
//获得当前的工作目录
char* getcwd(char *buf,size_t size)
    其实是递归的调用读取..,找到..中的目录项中与自己的inode相同的目录项