一、文件与文件目录
1. 文件
1.1 概念
信息在磁盘上存取的一种抽象
1.2 命名
文件名 + 扩展名:文件名用来识别文件,扩展名用来识别文件类型
1.3 类型
普通文件:包含某种形式数据
目录文件:由文件目录所构成的用来维护文件系统结构的系统文件,普通文件的查找依赖于目录文件
块特殊文件:提供对设备带缓冲的访问,每次访问以固定长度为单位进行
字符特殊文件:提供对设备不带缓冲的访问,长度不定,与块特殊文件组成系统所有的设备文件
FIFO: 命名管道,用与进程间通信
套接字:进程间的网络通信
符号链接:包含对另一个文件的引用
图1 sys/stat.h 关于各文件的类型宏
1.4 存取方式
顺序存取
直接存取
索引存取
1.5 函数实现
1) 文件封装数据结构
文件信息包括文件类型、设置用户ID/设置组ID、文件访问权限
struct stat {
mode_t st_mode; // file type & mode (permissions)
ino_t st_ino; // i-node number(serial number)
dev_t st_dev; // device number(file system)
dev_t st_rdev; // device number for special file
nlink_t st_nlink; // number of links
uid_t st_uid; // user id of owner
gid_t st_gid; // group id of owner
off_t st_size; // size in bytes, for regular file
struct timespec st_atime; // time of last access(ex. read)
struct timespec st_mtime; // time of last modify (ex. write)
struct timespec st_stime; // time of last status change (ex. chmod chown)
blksize_t st_blksize; // best I/O block size
blkcnt_t st_blocks; // number of disk blocks allocated
};
2) 获取文件信息
#include <sys/stat.h>
int stat(const char * restrict pathname, struct stat *restrict buf); //返回pathname命名文件的信息结构
int fstat(int fd, struct stat *buf);
int istat(const char *restrict pathname, struct stat *restrict buf);
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag); // 相对当前fd指向目录返回文件统计信息,flag控制是否跟随一个符号链接
S_ISDIR(stat.st_mode)可用来测试stat结构的文件类型是否为目录,其他类型依次类推,返回值非0为是
3) 测试文件权限
// 新文件和新目录的所有者为创建进程的有效用户ID
#include <unistd.h>
int access(const char *pathname, int mode)
int faccessat(int fd, const char *pathname, int mode, int flag) // 按实际用户ID和实际用户组ID测试文件的访问权限
/* mode参数:
R_OK : 测试读权限
W_OK : 测试写权限
X_OK : 测试执行权限
*/
4) 修改文件权限
#include <sys/stat.h>
mode_t umask(mode_t cmask); // 为进程文件模式设置屏蔽字,并返回之前的值,cmask是S_IRUSR S_IRGRP等等或运算给定
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)
/* ex.
chmod(path, S_IRUSR | S_IWUSR | S_IXUSR)
*/
图2 chmod函数的mode常量
图3 文件访问权限位汇总
#include <unistd,h>
int chown(const char *pathname, uid_t owner, gid_t group) 修改文件的用户id和用户组id
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)
int lchown(const char *pathname, uid_t owner, gid_t group) 符号链接文件
5) 创建一个指向现有文件的链接
#include <unistd.h>
int link(const char *existingpath, const char *newpath)
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag)
6) 重命名文件
#include <stdio.h>
int rename(const char *oldname, const char *newname)
int renameat(int fd, const char *oldname, int newfd, const char *newname)
7) 创建和读取符号链接
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath)
int symlinkat(const char *actualpath, int fd, const char *sympath) 创建一个符号链接
ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
ssize_t readlinkat(int fd, const char *restrict pathname, char *restrict buf, size_t bufsize)
8) 文件时间操作
#include <sys/stat.h>
int futimens(int fd, const struct timepec times[2])
int utimensat(int fd, const char *path, const struct timespec times[2], int flag)更改文件的访问和修改时间
// 两个函数的times数组参数第一个元素包含访问时间,,第二元素包含修改时间
9) 创建目录
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);创建一个新的空目录-
黏着位:/tmp和/var/tmp权限都是黏着位扩展功能后的结果,除了拥有者和超级用户外,不能修改属于其他用户的文件,并且直到下次系统启动,任何用户都能在其目录下进行操作
2. 文件目录
1.1 文件控制块、文件目录和目录文件
文件控制块FCB是操作系统为文件建立的唯一数据结构,包含全部文件属性,一个文件由FCB和文件信息构成;
FCB汇集在一起形成文件目录,包含许多目录项,分为描述子目录和描述文件;
文件系统就是文件和目录的层次结构和集合;
FCB中的文件名和其他管理信息分开,其他信息组成索引节点inode;
1.2 层次目录结构
反映具有层次关系的数据集合,系统内部文件的分支结构,有利于系统维护和查找;
/* Linux 文件多级目录结构 */
/dev : 设备子目录
/usr : 命令程序问件和库程序子目录
/var : 变化的文件子目录
/etc : 基本数据和实用程序子目录
/home:用户文件主目录
1.3 文件目录检索
父目录包含子目录意味着包含一个指向子目录的inode链接。
目录查找例子:
应用需要打开文件/home/waitor/data.wav,文件系统开始搜索:
1)遇到根目录"/",根据活动Inode表中根目录的inode,作为当前工作索引节点并将其的一个物理块读入内存缓冲区;
2) 读入路径的第一个分量字符串home,文件系统对根目录的内容进行搜索,若未找到则进入第二第三个物理块;
3)找到home的inode号,检查活动inode表;若找不到则分配活动inode;
4)从磁盘home的inode中装入其内容,否则直接查找home的活动inode;
5)通过属性查明home为子目录,核对访问权限,把它作为当前工作索引节点,读入路径的第二个分量waitor;
6)从子目录物理块中找到waitor的inode号,在waitor的物理块中找到data.wav;
7)文件系统为此文件在活动Inode表中分配一个inode,从data.wav的磁盘inode里装入内容,修改活动inode的相关内容。
二、文件组织与数据存储
文件的组织: 文件的逻辑结构与组织、文件的物理结构与组织
Linux文件采用无结构字符流序列加上一个文件偏移指针
文件的逻辑结构:
流式文件 :打开文件的进程使用读写指针来访问文件的特定字节
记录性文件: 打开文件的进程使用读写指针来访问文件的特定记录
文件的物理结构
顺序文件 、连接文件、索引文件、直接文件(哈希)
三、文件系统功能及实现(以Linux系统为例)
1. 目标
操作系统必须考虑文件目录的建立和维护、存储空间的分配和回收、数据的保密和保护、监督用户存取和修改文件的权限、处理在不同存储介质上信息的表示方式、信息的编址方法、信息的存储次序、以及怎样检索用户信息等问题。
2. 功能
文件系统的功能就是要在逻辑文件与物理文件、逻辑地址与物理地址、逻辑结构与物理结构、逻辑操作与物理操作之间实现转换,保证存取速度快、存储空间利用率高、数据可共享、安全可靠性好。
主要实现功能包括:
1) 文件的按名存取
2) 文件目录的建立和维护
3) 实现从逻辑文件到物理文件的转换
4) 文件存储空间的分配和管理
5) 提供合适的文件存取方法
6) 实现文件的共享、保护和保密
7) 提供一组可供用户使用的文件操作
3. 内核表示打开文件的数据结构实现
磁盘: 超级块 存放文件系统结构和管理信息
索引节点区 存放inode表
数据区 用作数据区,文件内容保存在这个区域
用户打开文件表 :存放在进程PCM中,表项为fd,表中存放系统打开文件表的一个入口指针fp
系统打开文件表: 用于解决多线程共享文件,记录多个进程打开同一文件不同的偏移量
活动inode表:
4. 文件共享 : 减少文件复制次数
1) 静态共享 : 无论进程是否运行,其文件的链接关系都是存在的,因此称为静态共享
link(),将两个路径指向同一文件;解除unlink(),具体参数见1.5(5)
文件链接:物理上存储在一个地方,但从多个目录都可以到达
2) 动态共享:不同的用户或进程并发的访问同一文件,两种情形取决于:fork与打开文件的先后顺序
进程打开文件表 ===> 系统打开文件表(file) ==== > 活动inode表
注:linux没有使用v节点,而是使用了通用i节点结构
原子操作:由多步组成的一个操作,要么不执行,要么一次执行完成,主要是为了避免多进程之间函数调用不同步的问题,导致操作同一文件出错
3) 符号链接共享
解决问题:在整合的目录树中,inode号并不唯一标识一个文件,无法做到从不同文件系统生成指向同一文件的链接
硬链接:将文件名和自身的inode链接起来,不能用于目录共享,实现简单,访问速度快
软链接:(符号链接)只有文件名,不指向inode的链接,通过名称来引用文件;把bfile的路径链接到文件afile, 访问afile时被操作系统截获,操作系统根据链接的路径去读文件,实现通过afile共享bfile;搜索路径开销大,额外空间储存查找路径
5. 文件空间管理
职责:创建时分配内存块,减少I/O次数;收集文件删除后的空间;
文件外存空间管理方法:
1)位示图;2)空闲区表;3)空闲块链;4)空闲块列表 5)成组空闲块链
内存映射文件:结合虚存管理和文件管理技术首创的另一种文件访问方法 便于共享,易于通信
映射到虚拟地址空间,磁盘访问变为内存访问
系统调用:mmap() unmmap()
让进程所映射的进程虚存空间的页表项中的外存地址指向文件所在的磁盘块,很容易实现映射和去映射
多进程共享时,把进程的需页面指向相同的页框,页框中保存文件的页面副本
虚拟文件系统VFS : 把多种文件系统纳入同一框架,提供通用模型,处理底层细节,提供标准接口
应用层
虚拟层
实现层
6. 文件系统性能与可靠性问题
文件系统性能问题
提升措施:盘块高速缓存 数据预先读入 信息优化分布 磁盘驱动调度 内存映射文件
文件系统可靠性问题
措施:磁盘坏块处理、备份数据
文件系统一致性检查:磁盘块一致性检查: 文件一致性检查
四、Linux文件系统 ext 与VFS
1. 结构
1.1 应用层
位于用户空间的应用程序使用标准文件类系统调用 open、 close、 read 、write等进行不带缓冲的I/O操作
int open(const char *path, int oflag, ... /* mode_t mode */);// 返回最小的可用描述符值
表1 mode_t 参数位
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
O_EXEC | 只执行打开 |
O_SEARCH | 只搜索打开(目录) |
O_APPEND | 每次写都追加到文件尾端 |
O_CLOEXEC | 把FD_CLOEXEC设置为文件描述符标志 |
O_CREAT | 此文件不存在则创建它 |
O_DIRECTORY | 如果path不是目录,则报错 |
O_EXCL | 如果一个文件存在,且指定O_CREAT,则报错 |
O_NOCTTY | 如果引用的是终端设备,则不分配给此进程控制终端 |
O_NOFOLLOW | 若path是符号链接,则出错 |
O_NONBLOCK | I/O操作设置为非阻塞方式,适用于FIFO 块、字符特殊设备 |
O_SYNC | 每次write等待物理I/O完成 |
O_TRUNC | 若文件存在,且只写或可读写,则长度截断为0 |
O_TTY_INIT | 若打开一个还未打开的设备,设置非标准termios参数 |
O_DSYNC | SUS POSIX.1支持的同步输入、输出选项 |
O_RSYNC |
int creat(const char *path, mode_t mode);等效于open(path, O_WRONLY | O_CTEAT | O_TRUNC, mode); // 以只写方式创建并打开一个文件,
int close(int fd): // 关闭一个文件并释放该文件上的所有记录锁;当一个进程终止时,内核自动关闭它所打开的文件,而不是显式地用close()去关闭
off_t lseek(int fd, off_t offset, int whence); // 用open打开一个文件时,若不指定O_APPEND,文件偏移指针在文件开始偏移量0处 off_t抽象了不同平台的int
/* 可以用来确定当前文件指针位置以及文件是否可以设置偏移量,如文件描述符指向管道、FIFO、网络套接字(返回值-1,errno为ESPIPE(注:有些文件的偏移量可以为负数);
偏移量可以大于文件长度,此时文件扩展会形成空洞,但空洞不占用磁盘空间,这是文件系统的实现决定的 */
int read(int fd, void *buf, size_t nbytes); // 若成功,返回读取字节数,到达文件尾,返回0,出错返回-1
int dup(int fd);
int dup2(int fd, int fd2); // 复制一个现有的描述符,dup2可以指定一个新的描述符
#include <unistd.h>
int fsync(int fd) // 只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回
int fdatasync(int fd) // 类似与sync,但只对文件的数据部分其租用
void sync(void) // 将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写操作结束
#include <fcntl.h>
int fcntl(int fd, int cmd, /* int arg */)
#include <sys/ioctl.h> // linux
int ioctl(int fd, int request, ...); // I/O操作的杂物箱,终端I/O的ioctl都需要头文件<termios.h>
功能:写一个文件结束标志、倒带、越过指定个数的文件和记录等 获取和设置终端窗口大小,访问伪终端的高级功能
1.2 虚拟层
在内核空间使用内核函数sys_open sys_read sys_write sys_close,操作VFS隐藏具体文件系统的操作细节,实现文件系统管理的缓冲机制如索引节点缓存、目录高速缓存。
索引节点缓存(VFS inode cacle):为了增加inode链表线性搜索的速度,通过hash+cache的方式进行查找;
目录高速缓存(VFS directory cache):为了提高通过路径名查找该目录文件的inode的效率;
1.3 实现层
实现具体的文件系统,如ext,NTFS,FAT等。
1.4 物理层
底层设备I/O
2. VFS数据结构和管理
3. VFS文件系统调用实现
4. linux ext2文件系统
五、一些实现
1. 串口读写程序
#include "apue.h"
#include <termios.h>
#include <errno.h>
#define CBAUD 0010017
#define CBAUDEX 0010000
# define CRTSCTS 020000000000
int speed_arr[] = { B4000000, B3500000, B3000000, B2500000, B2000000, B1500000,B1152000, B1000000, B921600, B576000, B500000, B460800, B230400, B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200,};
int name_speed[] = { 4000000, 3500000, 3000000, 2500000, 2000000, 1500000, 1152000, 1000000, 921600, 576000, 500000, 460800, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200,};
int open_dev(char *dev)
{
int fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
if(fd == -1) {
sys_debug("====> open uart failed %s.\n",strerror(errno));
return -1;
}
else {
fcntl(fd, F_SETFL, 0);
sys_debug("====> open uart success\n");
return fd;
}
}
int set_speed(int fd, int speed)
{
struct termios opt;
int status ;
tcgetattr(fd, &opt);
for(int i=0; i<sizeof(speed_arr); i++) {
if(speed == name_speed[i]) {
tcflush(fd, TCIOFLUSH);
/* 设置输入波特率 */
cfsetispeed(&opt, speed_arr[i]);
/* 设置输出波特率 */
cfsetispeed(&opt, speed_arr[i]);
opt.c_cflag |= speed_arr[i] | CBAUDEX;
status = tcsetattr(fd, TCSANOW, &opt);
if(status) {
sys_debug("tcsetattr fd1\n");
return -1;
}
}
}
return 0;
}
int set_bit_option(int fd, int data_flag, int parity, int stop_flag)
{
struct termios opt;
memset(&opt, 0, sizeof(opt));
if(tcgetattr(fd, &opt) == -1) {
printf("error tcgetattr\n");
return -1;
}
/* 设置控制位 */
switch(stop_flag) {
case 1: // 不使用数据流控制
opt.c_cflag &= ~CSTOPB; // 1位停止位
opt.c_cflag &= ~CRTSCTS; // 不适用硬件流控
printf("\n Flow control disabled \n");
break;
case 2:
opt.c_cflag |= CSTOPB;
break;
default:
sys_debug("unsupported control flag : %d\n", stop_flag);
}
/* 设置校检位 */
switch(parity) {
case 'n':
case 'N': // 无校检
opt.c_cflag &= ~PARENB;
// opt.c_iflag &= ~INPCK;
break;
case 'o':
case 'O': // 奇校检
opt.c_cflag |= (PARODD | PARENB);
opt.c_iflag |= (INPCK | ISTRIP);
break;
case 'e':
case 'E': // 偶校检
opt.c_cflag |= ~PARENB;
opt.c_cflag &= ~PARODD;
opt.c_iflag |= (INPCK | ISTRIP);
break;
case 's':
case 'S': //空格
opt.c_cflag &= ~PARENB;
opt.c_cflag &= ~CSTOPB;
default:
sys_debug("skip check flag : %d\n", parity);
break;
}
// opt.c_cflag |= CLOCAL | CREAD;
/* 设置数据位 */
opt.c_cflag &= ~CSIZE; // 屏蔽其他标志 & 0060
switch(data_flag) {
case 5:
opt.c_cflag |= CS5;
break;
case 6:
opt.c_cflag |= CS6;
break;
case 7:
opt.c_cflag |= CS7;
break;
case 8:
opt.c_cflag |= CS8;
break;
default:
sys_debug("data flag exclude %d\n", data_flag);
break;
}
opt.c_cflag |= (CLOCAL | CREAD );
// if(parity != 'n') {
// opt.c_iflag |= INPCK;
// }
opt.c_oflag &= ~OPOST;
opt.c_lflag &= ~(ICANON | ECHO | IEXTEN | ISIG);
opt.c_iflag &= ~(ICRNL | INPCK | ISTRIP | BRKINT | IXON | IXOFF | IXANY);
opt.c_cc[VTIME] = 3;
opt.c_cc[VMIN] = 0;
if(tcflush(fd, TCIFLUSH) == -1) {
sys_debug("tcflush failed.\n");
return -1;
}
if(tcsetattr(fd, TCSANOW, &opt)) {
sys_debug("tcsetattr fd1 error \n");
return -1;
}
return 0;
}
/* 轮询方式写串口 */
int writen(int fd, char *buf, int len)
{
int left = bytes;
int n = 0;
while(left > 0) {
n = write(fd, buf, left);
if(n <= 0) {
/* 轮询,跳过Resource temprorily unavailibe 错误 */
if(n == -1 && errno == EAGAIN) {
n =0;
} else {
printf("Error while write: %s\n", strerror(errno));
return -1;
}
}
left -= n;
buf += n;
}
return 0;
}
int main()
{
int fd, fd2;
int ret;
char buf[255];
fd_set read_fds;
struct timeval timer;
char *dev = "/dev/ttyUSB0";
fd = open_dev(dev);
if(fd <=0 ) {
sys_debug("open device error\n");
exit(1);
}
// ret = set_speed(fd, 115200);
ret = set_speed(fd, 2000000);
if(ret) {
exit(1);
}
ret = set_bit_option(fd, 8, 'N', 1);
wtk_debug("reading \n");
fd2 = open("./recorder.pcm", O_WRONLY | O_CREAT, S_IRWXU | S_IRWXO);
if(fd2 == -1) {
sys_debug("cann't open %s\n", "recorder.pcm");
close(fd);
exit(1);
}
memset(buf, 0, 255);
// char *buf1 = "waitor";
int read_flag = 0;
while(1) {
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
timer.tv_sec = 10;
timer.tv_usec = 0;
ret = select(FD_SETSIZE, &read_fds, NULL, NULL, &timer);
if(ret == 0) {
if(read_flag == 0) {
sys_debug("wait timeout , no data from port, exiting...\n");
break;
} else {
sys_debug("this loop has no data, continue to wait for data come\n");
continue;
}
} else if(ret == -1) {
sys_debug("select error \n");
}
if(FD_ISSET(fd, &read_fds) != 0) {
if(read_flag == 0) {
read_flag = 1;
sys_debug("reading data\n");
}
ret = 0;
ret = read(fd, buf, 255);
if(ret < 0) {
if(errno == EAGAIN ) {
sys_debug("SERIAL EAGAIN ERROR\n");
ret = 0;
} else {
sys_debug("SERIAL read error %d %s\n", errno, strerror(errno));
ret = 0;
}
}
if(ret == 1 && buf[0] == 0x00) {
ret = 0;
}
if(ret == -1) {
wtk_debug("read error happen\n");
break;
}
}
if(ret == 0) {
break;
}
sys_debug("receive %d bytes.\n", ret );
write(fd2, buf, ret);
memset(buf, 0, 255);
}
FD_CLR(fd, &read_fds);
close(fd);
close(fd2);
return 0;
}