Linux文件系统

本文详细介绍了Linux文件系统中的系统调用、一切皆文件的概念、文件描述符的作用、文件的创建与打开、读写方法、标准IO与系统IO的对比、文件同步、文件锁、复制文件描述符、文件属性、权限管理、链接文件以及工作目录和目录操作等关键概念。
摘要由CSDN通过智能技术生成

Linux文件系统

一、系统调用

什么是系统调用:

​ 在操作系统的内核中定义了一系列内核函数,供应用程序调用,但是为了操作系统的安全,这些函数不能被直接调用,而是向应用程序提供了一系列API,应用程序调用API切换到内核态调用内核函数就是系统调用。

​ 系统调用负责把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,然后将处理结果返回给应用程序。

​ UNIX/Linux大分部的系统功能是通过系统调用实现的,这些系统调用被封装成了C函数的形式,但它们并不是真正的函数。

​ 标准库函数大部分工作在用户态,一部分函数会使用系统调用进入内核态(fopen/malloc…)。

二、一切皆文件

​ 在UNIX和Linux系统下,把操作系统提供的服务和设备都抽象成了文件,因为这样可以给各种设备控制提供了一个简单而统一的接口,程序完全可以象访问普通磁盘文件一样,访问串行口、网络、打印机或其它设备。

​ 在UNIX和Linux系统下中的文件具有特别重要的意义,因为它在Linux中,(几乎)一切皆文件,大多数情况下只需要使用五个基本系统调用 open/close/read/write/ioctl,即可实现对各种设备的输入和输出,Linux中的任何对象都可以被视为某种特定类型的文件,可以访问文件的方式访问之。

三、文件描述符

1、什么是文件描述符:

​ 文件描述符是一种非负的整数,表示一个打开的文件,由系统调用(open/creat)返回,在后续操作文件时可以被内核空间引用(操作文件的凭证),内核默认为每个进程打开三个文件描述符:

  • stdin 0 - 标准输入

  • stdout 1 - 标准输出

  • stderr 2 - 标准出错

在unistd.h中被定义为如下三个宏:

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
2、文件描述符与文件指针:

​ 在Linux系统中打开文件后,就会产生一个内核对象,也就是记录该文件相关信息的结构体变量,但内核为了自己的安全不能把它的地址返回给用户,而且由于内核空间和用户空间的原因,返回也无法访问、使用。

​ 而且一个进程可能会同时打开多份文件,所以操作系统就在内核空间创建了一张索引表,表的每一项都有一个指向已打开文件的内核对象,文件描述符就是索引表的主键,如果把索引表看作数组,那么文件描述符就是数组的下标,不同进程之间交换文件描述符没有意义。

​ C语言中使用文件指针代表打开的文件,文件指针指向进程的用户空间中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是文件描述符。

int fileno(FILE *stream);
功能:把文件指针转换成文件描述符
    
FILE *fdopen(int fd, const char *mode);
功能:把文件描述符转换成文件指针

四、文件的创建与打开

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打开文件
pathname:文件路径
flags:打开文件的方式
返回值:文件描述符,失败返回负值

int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
pathname:文件路径
flags:打开方式
mode:创建文件时的权限 //0777
返回值:文件描述符,失败返回负值
    
int creat(const char *pathname, mode_t mode);
功能:专门用来创建文件,但基本不使用,因为open函数完全具备它的功能。
    
注意:open/creat所返回的一定是当前未被使用的,最小文件描述符。
一个进程可以同时打开的文件描述符个数,受limits.h中定义的OPEN_MAX宏的限制,POSIX要求不低于16,传统UNIX是63,现代Linux是255。

flags:
	O_APPEND 打开文件后位置指针指向末尾
    O_CREAT 文件不存在时创建
	O_RDONLY 只读权限
	O_WRONLY 只写权限
	O_RDWR 读写权限
	O_TRUNC 清空文件内容
	O_EXCL 如果文件存在则创建失败
        
	O_NOCTTY 若pathname指向控制终端,则不将该终端作为控制终端。
	O_NONBLOCK 若pathname指向FIFO//字符文件,则该文件的打开及后续操作均为非阻塞模式。
	O_SYNC	write等待数据和属性,被物理地写入底层硬件后再返回。
	O_DSYNC	write等待数据,被物理地写入底层硬件后再返回。
	O_RSYNC	read等待对所访问区域的所有写操作,全部完成后再读取并返回。
	O_ASYNC	当文件描述符可读/写时,向调用进程发送SIGIO信号。
mode:
	S_IRWXU  00700
	S_IRUSR  00400
	S_IWUSR  00200
	S_IXUSR  00100
  
	S_IRWXG  00070 
	S_IRGRP  00040
	S_IWGRP  00020
	S_IXGRP  00010
        
	S_IRWXO  00007
	S_IROTH  00004
	S_IWOTH  00002
	S_IXOTH  00001
        
<unistd.h>
int close (int fd);
功能:关闭文件,成功返回0,失败返回-1

五、文件读写

ssize_t write(int fd, const void *buf, size_t count);
功能:写入文件内容
fd:文件描述符
buf:要写入的数据的内存首地址
count:要写入的字节数
返回值:成功写入的字节数
    
ssize_t read(int fd, void *buf, size_t count);
功能:读取文件内容
buf:存储数据的内存首地址
count:想读取的内存字节数
返回值:成功读取到的字节数,出错-1,文件尾0

注意:它们与标准C的fwrite/fread很像,但更纯粹。

六、系统IO与标准IO

​ 1、当系统调用被执行时,需要从用户态切换到内核态,执行完毕后再从内核态切换到用户态,频繁的切换就会导致性能损失。

​ 2、标准IO在内部维护一个缓冲区(1k,1024字节),只有在满足特定条件才会把缓冲区中的数据调用write进入写入,因此降低了系统调用的使用频率,减少用户态和内核态的来回切换次数,因此标准IO的速度比系统IO更快

​ 3、如果想提高系统IO的速度,可以尝试维护一个更大的缓冲区,先把数据存储在缓冲区中,等缓冲区满了,再调用write写入,这样系统IO会比标准IO更快。

​ 4、普通情况建议使用标准IO,因为比直接使用系统IO要快,如果对速度有很高的要求,可以使用系统IO+大缓冲区。

​ 5、UNIX和Linux只有这一套读写函数,没有文本文件的读写方式,可以使用ssanf/sprintf配合缓冲区来实现文本内容的读写。

七、文件位置指针

​ 1、每个打开的文件都有一个记录读写位置的变量,它可能是整数,但习惯的称作位置指针,文件的读写操作都是从位置指针所指向地方进行的。

​ 2、lseek可以设置文件的位置指针,与标准C不一样的是它的返回值是它调整后的位置指针,所以系统调用中没有与ftell对应的函数,因为lseek就包含fseek和ftell的功能。

off_t lseek(int fd, off_t offset, int whence);
功能:调整文件位置指针,用法与标准C的fseek基本一致。
offset:偏移值
whence:基础位置
    SEEK_SET	The offset is set to offset bytes.
    SEEK_CUR	The offset is set to its current location plus offset bytes.
    SEEK_END	The offset is set to the size of the file plus offset bytes.
返回值:失败-1,成功返回文件位置指针现在的位置 相当于fseek->ftell
    
int fseek(FILE *stream, long offset, int whence);
返回值:成功返回0,失败返回-1

​ 3、在超过文件末尾的位置写入数据就会形成文件黑洞,黑洞会不会占用磁盘空间由操作系统决定,但会计算成文件的大小,而且也不会影响文件的读写。

八、文件同步

​ 大多数磁盘I/O都有缓冲机制,写入文件其实只是写入缓冲区,直到缓冲区满,才将其排入写队列。延迟写降低了写操作的次数,提高了写操作的效率,但可能导致磁盘文件与缓冲区数据不同步,可使用以下函数强制让磁盘缓冲区的内容同步磁盘。

#include <unistd.h>

void sync (void);		// 将所有被修改过的缓冲区排入写队列即返回,不等待写磁盘操作完成
int fsync (int fd);		// 只针对一个文件,且直到写磁盘操作完成才返回,成功返回0,失败返回-1。
int fdatasync (int fd); // 只同步文件数据,直到写磁盘操作完成才返回,不同步文件属性

注意:如果文件描述符的flags有SYNC相关标记,也能达到同步的效果。

九、文件描述符的状态标志

int fcntl (int fd, int cmd, long arg);
功能:设置或获取文件描述符的状态标志
注意:该函数主要用于获取来历不明的文件描述符有哪些功能,或者在原有基础上增加一些功能(有些标志无法获取,有些无法增加)。
cmd:
	F_GETFD - 获取文件描述符标志。
	F_SETFD - 设置文件描述符标志,目前仅定义了一个文件描述符标志位FD_CLOEXEC:
	  	  0 - 在通过execve()函数所创建的进程中,该文件描述符依然保持打开。
	  	  1 - 在通过execve()函数所创建的进程中,该文件描述符将被关闭。
    
	F_GETFL - 获取文件状态标志,不能获取O_CREAT/O_EXCL/O_TRUNC。
	F_SETFL - 追加文件状态标志,只能追加O_APPEND/O_NONBLOCK。

十、文件锁

1、文件锁的意义:

​ 当多个进程访问同一个文件时,就有可能造成文件的读写数据混乱,而解决这一问题的方法就是读写文件前给文件加锁。

2、文件锁的限制:

​ 一般情况下,操作系统提供的文件锁都是劝诫锁,如果需要强制锁,则需要自己定制,也就是重新配置、编译内核。

#include <fcntl.h>

int fcntl (int fd, int cmd, struct flock* lock);

struct flock {
	short int l_type;   // 锁的类型:
    	// F_RDLCK读锁
    	// F_WRLCK写锁
    	// F_UNLCK解锁
	short int l_whence; // 偏移起点:
    	// SEEK_SET文件头
    	// SEEK_CUR当前位置 
    	// SEEK_END文件尾
	off_t l_start;  	// 锁区偏移,从l_whence开始
	off_t l_len;    	// 锁区长度,0表示锁到文件尾
	pid_t l_pid;    	// 加锁进程,-1表示自动设置
};

cmd取值:
	F_GETLK  - 测试lock所表示的锁是否可加。
		若可加则将lock.l_type置为F_UNLCK,否则通过lock返回当前锁的信息。

	F_SETLK  - 设置锁定状态为lock.l_type,
	成功返回0,失败返回-1,若因其它进程持有锁而导致失败,则errno为EACCES或EAGAIN。
	
	F_SETLKW - 设置锁定状态为lock.l_type,
	成功返回0,否则一直等待,除非被信号打断返回-1

十一、复制文件描述符

int dup(int oldfd);
功能:复制文件描述符
oldfd:已经打开了的文件描述符
返回值:系统返回一个新的文件描述符newfd,当前可用文件描述中的最小数值
注意:防止文件描述符被意外关闭,备份文件描述符
    
int dup2(int oldfd, int newfd);
功能:按指定的newfd复制oldfd文件描述符,如果newfd已经被占用,则把它关闭重新复制。
返回值:成功返回0,失败返回-1。
注意:由于dup2会先把newfd关闭再复制,为了避免误关闭其他文件,所以一般取值为012,这样我们可以使用stdio.h中的函数操作文件     
//newfd:0输入1输出2错误 printf->sprintf->stdout(1)   
    
int fcntl (int fd, int cmd, long arg);
功能:复制fd为不小于arg的文件描述符,若arg文件描述符已用,该函数会选择比arg大的最小未用值,而非如dup2函数那样关闭。
cmd:取值为F_DUPFD
    
注意:当对oldfd和newfd进行读写时,访问的是同一个文件。

十二、获取文件的属性

int stat(const char *path, struct stat *buf);
功能:根据文件的路径来获取文件的属性

int fstat(int fd, struct stat *buf);
功能:根据文件描述符来获取文件的属性

int lstat(const char *path, struct stat *buf);
功能:获取链文件的属性,如果不使用该函数而是上面两个,获取到的是链接文件的目标文件的属性,该函数获取到的是链接文件自己的文件属性

struct stat {
	dev_t     st_dev;   文件的设备ID
	ino_t     st_ino;   文件的节点号
	mode_t    st_mode;  文件的类型和权限
	nlink_t   st_nlink; 硬链接数量
	uid_t     st_uid;   属主ID(文件的拥有者的用户ID)
	gid_t     st_gid;   属组ID(文件的拥有者的用户组ID)
	dev_t     st_rdev;  特殊设备ID
	off_t     st_size;  文件的总字节数
	blksize_t st_blksize; I/O块数
	blkcnt_t  st_blocks;512字节为一块,占多少块
    time_t    st_atime;   最后访问时间
    time_t    st_mtime;   最后的修改时间
    time_t    st_ctime;   最后状态修改时间
};
    
st_mode
	S_IFSOCK   套接字文件
	S_IFLNK    软链接文件
	S_IFREG    普通文件
	S_IFBLK    块设备文件
	S_IFDIR    目录
	S_IFCHR    字符设备文件
	S_IFIFO    管道文件
	S_ISUID    属主沾滞位标志(一般合并在执行权限上)
	S_ISGID    属组沾滞位标志
	S_ISVTX    其它沾滞位标志  
    
	S_IRWXU    属主的读写执行权限
	S_IRUSR    属主的读权限
	S_IWUSR    属主的写权限
	S_IXUSR    属主的执行权限
	S_IRWXG    属组的读写执行权限
	S_IRGRP    属组的读权限
	S_IWGRP    属组的写权限
	S_IXGRP    属组的执行权限
	S_IRWXO    其它的读写执行权限
	S_IROTH    其它的读权限
	S_IWOTH    其它的写权限
	S_IXOTH    其它的执行权限

帮助判断文件类型的宏函数:
    S_ISREG(m)  普通文件
    S_ISDIR(m)  目录文件
    S_ISCHR(m)  字符设备文件
    S_ISBLK(m)  块设备文件
    S_ISFIFO(m) 管道文件
    S_ISLNK(m)  软链接文件
    S_ISSOCK(m) 套接字文件

十三、文件的权限

1、测试文件的权限:
#include <unistd.h>

int access (const char* pathname, int mode);
pathname:文件的路径
mode:
    R_OK/W_OK/X_OK 测试调用进程对该文件,是否可读/可写/可执行
    F_OK,测试该文件是否存在
返回值:成功返回0,失败返回-1
2、修改文件的权限:
#include <sys/stat.h>

int chmod (const char* path,mode_t mode);
int fchmod (int fd, mode_t mode);

mode为以下值的位或(直接写八进制整数形式亦可,如07654)
------------------------------
S_IRUSR			- 属主可读
S_IWUSR			- 属主可写
S_IXUSR			- 属主可执行
------------------------------
S_IRGRP         - 属组可读
S_IWGRP         - 属组可写
S_IXGRP         - 属组可执行
------------------------------
S_IROTH         - 其它可读
S_IWOTH         - 其它可写
S_IXOTH         - 其它可执行
3、文件的权限屏蔽码:

​ 当使用creat、open创建文件时,无论权限设置为什么都会创建成功,但操作系统会根据权限屏蔽码对用户设置的权限进行过滤,文件最终的权限是系统过滤后的权限。

可以用umask命令查看/修改当前shell的文件权限屏蔽码:
# umask
0022
# umask 0033
# umask
0033
#include <sys/stat.h>
mode_t umask (mode_t cmask);
功能:为当前进程设置文件权限掩码,并返回旧的权限屏蔽码,此函数永远成功。
注意:chmod函数和命令都不受权限屏蔽码的影响。

十四、修改文件的大小

#include <unistd.h>

int truncate (const char* path,off_t length);
int ftruncate (int   fd, off_t length);
// 成功返回0,失败返回-1,截短丢弃,加长添零

十五、链接文件

1、硬链接文件的创建与删除:
#include <unistd.h>

int link (const char* path1,const char* path2);
功能:创建硬链接文件,对应ln命令的功能

int unlink (const char* path);
功能:删除文件的硬链接,只有当文件的硬链接数降为0时,文件才会真正被删除。
int remove (const char* pathname);

注意:若某文件正在被某个进程打开,其内容直到该文件被关闭才会被真正删除。
2、软件链接文件的创建与读取:
#include <unistd.h>

int symlink (const char* oldpath, const char* newpath);
功能:创建软链接文件,目标文件可以不存在,对应的是ln -s命令的功能

ssize_t readlink (const char* restrict path,char* restrict buf,size_t bufsize);
功能:获取软链接文件本身的内容,open函数打开的是链接目标,不能打开软链接文件本身。
    
注意:硬链接只是一个文件名,即目录中的一个条目,它链接的是文件的内容,被链接的文件不存在时,硬链接文件依然能正常使用,而软链接则是一个独立的文件,其内容是另一个文件的路径信息,被链接的文件不存在时,软链接文件无法再被open函数打开,目录文件只能创建软链接。

十六、工作目录

#include <unistd.h>

int chdir (const char* path);
int fchdir (int fildes);
功能:修改工作目录,功能相当于cd命令

char* getcwd (char*  buf,size_t size);
功能:获取当前进程的工作目录,功能相当于pwd命令

十七、创建、删除、读取目录

#include <sys/stat.h>

int mkdir (const char* path,mode);
功能:创建空白目录,目录必须有执行权限才可进入

#include <unistd.h>
int rmdir ( const char* path // 目录路径);
功能:删除目录,但只能删除空目录

#include <sys/types.h>
#include <dirent.h>

DIR* opendir (const char* name);
DIR* fdopendir (int fd);
功能:打开目录文件
返回值:成功返回目录流指针(链表),失败返回NULLstruct dirent* readdir (DIR* dirp);
功能:从目录流中读取一个条目
返回值:成功返回下一个目录条目结构体的指针,到达目录尾或失败返回NULLstruct dirent {
    ino_t          d_ino;       // i节点号
    off_t          d_off;       // 下一条目的偏移量,注意是磁盘偏移量,而非内存地址偏移
    unsigned short d_reclen;    // 记录长度
    unsigned char  d_type;      // 文件类型
    char           d_name[256]; // 文件名
};
d_type取值:
	DT_DIR     - 目录
	DT_REG     - 普通文件
	DT_LNK     - 软链接
	DT_BLK     - 块设备
	DT_CHR     - 字符设备
	DT_SOCK    - Unix域套接字
	DT_FIFO    - 有名管道
	DT_UNKNOWN - 未知
           
void rewinddir(DIR *dirp);
功能:复位目录流
           
long telldir(DIR *dirp);
功能:获取目录流当前位置

void seekdir(DIR *dirp, long loc);
功能:设置目录流当前位置
           
int closedir (DIR* dirp);
功能:关闭目录文件
成功返回0,失败返回-1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值