-
一个简单文件系统的实现
2012-11-26 10:45:11分类: 嵌入式系统开发 软件源码 Linux相关开发 c/c++开发2009-07-20 ...花了将进两个月的时候阅读完内核文件系统,对于文件系统是如何运行的还是有点模糊,所以想通过写一个简单的文件系统来使自己对文 件系/*转载请注明作者
author goter
email prowlman@gmail.com
*/
花了将进两个月的时候阅读完内核文件系统,对于文件系统是如何运行的还是有点模糊,所以想通过写一个简单的文件系统来使自己对文
件系统有个深入的了解。经过拷贝抄袭ext2和minix文件系统后,写了一个简单的文件系统,我把这个简单的文件系统叫作GTFS,估计还
有很多BUG,欢迎大家修正这些BUG
GTFS不支持磁盘配额和acl。链接,读写,删除,新建,改名等都支持,有时候会有莫名其妙的错误,希望大家谅解
下面先附上代码和使用信息
使用信息
在fs/Makefile中添加一行
[Copy to clipboard] [ - ]
CODE:
obj-$(CONFIG_GT_FS) += gt/
在fs/Kconfig中添加
[Copy to clipboard] [ - ]
CODE:
config GT_FS
tristate "GOTER fs support"
help
GTFS is a simple Linux file system for hard disk.
在include/linux/Kbuild中添加一行
[Copy to clipboard] [ - ]
CODE:
unifdef-y += gt_fs.h
在include/linux/magic.h中添加一行
[Copy to clipboard] [ - ]
CODE:
#define GT_SUPER_MAGIC 0x0601
为了迎接六一儿童节,所以GTFS的魔数就设为0x0601了
然后将附件中的压缩包解压,可以看到有两个目录
一个目录是mkfs.gt,进入mkfs.gt目录,里边有4个文件,mkfs.gt为生成的二进制文件,可以使用gcc -o mkfs.gt mkfs.gt.c生成mkfs.gt
工具,这个工具用来创建GT文件系统,只支持mkfs.gt /dev/sdax(设备名)命令。
另一个目录是gtfs,进入gtfs目录,里边有8个文件,将gtfs/gt_fs.h移动到内核include/linux/下,然后在内核fs/ 下新建目录gt,将剩
余的7个文件移动到gt下
然后make menuconfig,到filesystem下选中gtfs,然后编译即可
GT-FS.tar.gz (19.69 KB)
我会在下面为大家分析GTFS代码的
因为GTFS只是用来学习而不是用于生产环境的,所以GTFS的设计很简单也很简陋。GTFS的主要数据结构和其他文件系统相似,主要的区别
是组织块的方式
下面是GTFS的主要数据结构
gt_inode(索引节点,在gt_fs.h中定义)
[Copy to clipboard] [ - ]
CODE:
struct gt_inode {
__le16 i_mode;
__le16 i_uid;
__le16 i_gid;
__le32 i_size;
__le32 i_atime;
__le32 i_ctime;
__le32 i_mtime;
__le32 i_dtime;
__le16 i_nlinks;
__le32 i_flags;
__le32 i_start_block;//索引节点开始块
__le32 i_end_block;//素引节点结束块
__le32 i_blocks;//索引节点占用的块数,另外作为索引节点是否是最后一个的flag
__le16 i_dev;
__le32 i_reserved;//为该索引节点预留的块数
__u8 i_nouse[10];
}
很简单是吧,i_blocks的用途有点特别,GTFS默认每个索引节点都会占用一个块,所以新建一个索引节点时,
i_start_block=i_end_block,但此时i_blocks和i_reserved为0,因为GTFS对磁盘的使用是顺序使用的,所以当新建一个索引节点时,上
一个索引节点就需要设置下预留块数,以便文件的增长。新建的索引节点的开始块等于结束块,但并不需要设置预留块数,因为它可以一
直使用到最后一个磁盘块(它没有下一个索引节点,我管这叫没有封顶),那么当操作系统打开一个文件时,如何判断这个文件对应的索
引节点是不是新建的呢,这就是i_blocks的作用,当i_blocks为0时,说明这个索引节点是新建的。当新建一个索引节点时,会设置上一
个索引节点的 i_blocks,
[Copy to clipboard] [ - ]
CODE:
i_blocks=i_end_blocks-i_start_blocks+1
所以,每个索引节点都至少占用一个块
gt_inode_info(内存中的i节点信息,在gt.h中定义)
[Copy to clipboard] [ - ]
CODE:
struct gt_inode_info{
__le32 i_start_block;
__le32 i_end_block;
__le32 i_blocks;
__le32 i_reserved;
__u32 i_flags;
__u32 i_dtime;
struct mutex truncate_mutex;
struct inode vfs_inode;
};
gt_super_block(超级块,在gt_fs.h中定义)
[Copy to clipboard] [ - ]
CODE:
struct gt_super_block {
__le32 s_inodes_count;
__le16 s_inode_size;
__le32 s_blocks_count;
__le32 s_free_blocks_count;
__le32 s_free_inodes_count;
__le32 s_first_data_block;
__le32 s_first_ino;
__le32 s_link_max;
__le32 s_log_block_size;
__le32 s_mtime;
__le32 s_wtime;
__le16 s_magic;
};
超级块数据结构没什么特别的,超级块的内容在挂载文件系统时从磁盘超级块读入
gt_sb_info(内存中的超级块,在gt_fs.h中定义)
[Copy to clipboard] [ - ]
CODE:
struct gt_sb_info{
struct gt_super_block * s_gs;//指向GT文件系统的超级块
struct buffer_head * s_sbh;//指向超级块所在的缓冲区
};
gt_dir_entry(目录项,在gt_fs.h中定义)
[Copy to clipboard] [ - ]
CODE:
#define GT_NAME_LEN 60 //目录项名字长度
struct gt_dir_entry {
__le32 ino;
char name[GT_NAME_LEN];
};
文件系统的实现还需要对超级块的操作,对文件的操作,索引节点操作等等
我们就先从挂载一个文件系统开始
挂载文件系统时候,VFS会通过mount命令中的文件系统类型或超级块魔数来寻找file_system_type结构
下面就是GTFS的file_system_type结构(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static struct file_system_type gt_fs_type ={
.owner =THIS_MODULE,
.name ="gt",//文件系统名称
.get_sb =gt_get_sb,//读取超级块方法
.kill_sb =kill_block_super,
.fs_flags =FS_REQUIRES_DEV,
};
我们只需要完成gt_get_sb(定义在super.c中)方法即可
[Copy to clipboard] [ - ]
CODE:
static int gt_get_sb(struct file_system_type *fs_type,
int flags,const char *dev_name,void *data,struct vfsmount *mnt){
return get_sb_bdev(fs_type,flags,dev_name,data,gt_fill_super,mnt);
}
从gt_get_sb函数可以看出,这个函数调用了get_sb_bdev函数,get_sb_bdev又调用了gt_fill_super函数
gt_fill_super函数才是正真用来读取超级块的方法
[Copy to clipboard] [ - ]
CODE:
static int gt_fill_super(struct super_block *sb,void *data,int silent){
struct buffer_head *bh;
struct gt_super_block *gs;
struct gt_sb_info *sbi;
struct inode *root;//设备的根目录
unsigned long sb_block=1;//超级块的块号为1,第0块为启动块
long ret=-EINVAL;
int blocksize=BLOCK_SIZE;//BLOCK_SIZE为1024
sbi=kzalloc(sizeof(struct gt_sb_info),GFP_KERNEL);
if(!sbi)
return -ENOMEM;
if(!sb_set_blocksize(sb,BLOCK_SIZE))//设置VFS超级块的块大小
goto out_bad_hblock;
if(!(bh=sb_bread(sb,sb_block))){//将GTFS超级块所在的块读入内存
printk("GT-fs:unable to read superblock/n");
goto failed_sbi;
}
gs=(struct gt_super_block *)(bh->b_data);
sbi->s_sbh=bh;//指向从磁盘读入的GTFS超级块所在的缓冲区
sbi->s_gs=gs;//将内存中的GTFS超级块和从磁盘读入的GTFS超级块联系起来
sb->s_fs_info=sbi;//将VFS超级块和GTFS的超级块联系起来
sb->s_magic=gs->s_magic;//设置魔数
if(sb->s_magic !=GT_SUPER_MAGIC)
goto cantfind_gt;
blocksize=GT_BLOCK_SIZE;
sb->s_op=>_sops;
root=gt_iget(sb,GT_ROOT_INO);//GT_ROOT_INO为1,读入文件系统的根目录
if(IS_ERR(root)){
ret=PTR_ERR(root);
printk(KERN_ERR "GT-fs: can't find root inode/n");
goto failed_mount;
}
if (!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size) {
iput(root);
printk(KERN_ERR "isdir?%d,root->i_blocks=%d,root->i_size=%d/n",S_ISDIR(root->i_mode) , root->i_blocks,
root->i_size);
printk(KERN_ERR "GT-fs: corrupt root inode/n");
goto failed_mount;
}
sb->s_root = d_alloc_root(root);//设置超级块的根目录
if (!sb->s_root) {
iput(root);
printk(KERN_ERR "GT: get root inode failed/n");
ret = -ENOMEM;
goto failed_mount;
}
return 0;
cantfind_gt:
printk("VFS: Can't find an gt filesystem on dev %s./nmagic on dev is %d and magic of GT is %d/n",sb->s_id,sb-
>s_magic,GT_SUPER_MAGIC);
failed_mount:
brelse(bh);
out_bad_hblock:
printk("GT-fs:blocksize too small for device/n");
failed_sbi:
sb->s_fs_info=NULL;
kfree(sbi);
return ret;
}
这段函数主要是从磁盘读入文件系统的超级块,用来填充内存中的GTFS超级块,和VFS超级块
调用gt_iget函数读入指定文件系统的根目录。
文件系统在kernel中被当作一个模块实现
也有module_init和module_exit(定义在super.c)
[Copy to clipboard] [ - ]
CODE:
module_init(init_gt_fs)
module_exit(exit_gt_fs)
init_gt_fs(定义在super.c)
[Copy to clipboard] [ - ]
CODE:
static int __init init_gt_fs(void){
int err=init_inodecache();
if(err)
return err;
err=register_filesystem(>_fs_type);//向内核注册GT文件系统
if(err)
goto out;
return 0;
out:
destroy_inodecache();
return err;
}
init_gt_fs中调用init_inodecache创建GTFS的内存索引节点缓冲池(我瞎猜的...对这个不了解..)
init_inodecache(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static struct kmem_cache *gt_inode_cachep;
static int init_inodecache(void){
gt_inode_cachep=kmem_cache_create("gt_inode_cache",sizeof(struct gt_inode_info),0,
(SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD),init_once);
if(gt_inode_cachep==NULL)
return -ENOMEM;
return 0;
}
然后就是exit_gt_fs(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static void __exit exit_gt_fs(void){
unregister_filesystem(>_fs_type);//注销文件系统
destroy_inodecache();//销毁缓冲池
}
接下来是超级块操作(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static const struct super_operations gt_sops={
.alloc_inode =gt_alloc_inode,//分配一个GTFS的索引节点
.destroy_inode =gt_destroy_inode,//销毁索引节点
.write_inode =gt_write_inode,//写入索引节点
.delete_inode =gt_delete_inode,//删除索引节点
.write_super =gt_write_super,//将超级块写入磁盘
.put_super =gt_put_super,//释放超级块
.statfs =gt_statfs,//获取文件系统状态
.write_inode =gt_write_inode,//将索引节点写入磁盘
};
下面我们挨个介绍
gt_alloc_inode(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static struct inode *gt_alloc_inode(struct super_block *sb){
struct gt_inode_info *gi;
gi=(struct gt_inode_info *)kmem_cache_alloc(gt_inode_cachep,GFP_KERNEL);//在缓冲池分配一个GTFS的内存索引节点
if(!gi)
return NULL;
return &gi->vfs_inode;
}
函数gt_destroy_inode(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static void gt_destroy_inode(struct inode *inode){
kmem_cache_free(gt_inode_cachep,GT_I(inode));
}
从缓冲池中释放GTFS的内存中索引节点
GT_I是定义在gt.h中的一个inline函数,根据VFS索引节点返回GTFS的内存索引节点
[Copy to clipboard] [ - ]
CODE:
static inline struct gt_inode_info *GT_I(struct inode *inode){
return container_of(inode,struct gt_inode_info,vfs_inode);
}
然后是gt_write_inode(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static int gt_write_inode(struct inode *inode,int wait){
brelse(gt_update_inode(inode));
return 0;
}
可见,gt_write_inode函数调用gt_update_inode来将内存中的索引节点写入磁盘
gt_update_inode(定义在inode.c中)
[Copy to clipboard] [ - ]
CODE:
struct buffer_head *gt_update_inode(struct inode *inode){
struct gt_inode_info *gi=GT_I(inode);
struct super_block *sb=inode->i_sb;
ino_t ino=inode->i_ino;
uid_t uid=inode->i_uid;
gid_t gid=inode->i_gid;
struct buffer_head *bh;
struct gt_inode *raw_inode =gt_raw_inode(sb,ino,&bh);//根据超级块和索引节点号从磁盘读入GTFS的磁盘索引节点
if(!raw_inode)
return NULL;
/*更新*/
raw_inode->i_mode=inode->i_mode;
raw_inode->i_uid=uid;
raw_inode->i_gid=gid;
raw_inode->i_nlinks=inode->i_nlink;
raw_inode->i_size=inode->i_size;
raw_inode->i_atime=inode->i_atime.tv_sec;
raw_inode->i_mtime=inode->i_mtime.tv_sec;
raw_inode->i_ctime=inode->i_ctime.tv_sec;
//raw_inode->i_dtime=inode->i_dtime.tv_sec;
if(S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
raw_inode->i_dev=old_encode_dev(inode->i_rdev);
else{
raw_inode->i_start_block=gi->i_start_block;
raw_inode->i_end_block=gi->i_end_block;
raw_inode->i_blocks=gi->i_blocks;
raw_inode->i_reserved=gi->i_reserved;
}
mark_buffer_dirty(bh);//将磁盘索引节点所在的缓冲块标记为脏
return bh;
}
gt_update_inode 很简单,通过调用gt_raw_inode来读入GTFS的磁盘索引节点,然后根据GTFS内存索引节点去设置磁盘索引节点,最后将
磁盘索引节点所在的缓冲块标记为脏,等待机会写入磁盘。gt_raw_inode将在后面介绍,我们回到超级块操作来
接下来是gt_delete_inode(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static void gt_delete_inode(struct inode *inode){
truncate_inode_pages(&inode->i_data,0);
GT_I(inode)->i_dtime=get_seconds();
inode->i_size=0;
gt_truncate(inode);//清空文件
gt_free_inode(inode);//释放索引节点
}
让我们来看看gt_truncate(定义在inode.c中)
[Copy to clipboard] [ - ]
CODE:
void gt_truncate(struct inode *inode){
if(!(S_ISREG(inode->i_mode)||S_ISDIR(inode->i_mode)||S_ISLNK(inode->i_mode)))
return;
struct gt_inode_info *gi=GT_I(inode);
block_truncate_page(inode->i_mapping,inode->i_size,gt_get_block);
gi->i_reserved+=gi->i_end_block-gi->i_start_block+1;//设置预留块数
gi->i_end_block=gi->i_start_block;//清空
inode->i_mtime=inode->i_ctime=CURRENT_TIME_SEC;
mark_inode_dirty(inode);
}
这个函数主要是设置内存索引节点的块使用状况
还有gt_free_inode(定义在inode.c中)
[Copy to clipboard] [ - ]
CODE:
void gt_free_inode(struct inode *inode){
struct super_block *sb=inode->i_sb;
struct gt_super_block *gs=GT_SB(inode->i_sb)->s_gs;
struct buffer_head *bh;
unsigned long ino;
ino=inode->i_ino;
struct gt_inode *raw_inode=NULL;
if(ino<1||ino>gs->s_inodes_count){
printk("gt_free_inode: inode 0 or nonexistent inode/n");
return;
}
raw_inode=gt_raw_inode(sb,ino,&bh);
if(raw_inode){
raw_inode->i_nlinks=0;//设置磁盘索引节点的连接数
raw_inode->i_mode=0;
}
if(bh){
mark_buffer_dirty(bh);
brelse(bh);
}
clear_inode(inode);//调用VFS函数清理VFS索引节点
}
这个函数也很简单,只是读取磁盘索引节点,然后设置连接数和模式,然后标记磁盘索引节点所在的缓冲块为脏
接着写超级块操作
接下来就是gt_write_super(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static int gt_write_super(struct super_block *sb){
struct gt_super_block *gs;
lock_kernel();
gs=GT_SB(sb)->s_gs;
gs->s_free_blocks_count=cpu_to_le32(gt_count_free_blocks(sb));
gs->s_free_inodes_count=cpu_to_le32(gt_count_free_inodes(sb));
gs->s_mtime=cpu_to_le32(get_seconds());
gs->s_wtime = cpu_to_le32(get_seconds());
mark_buffer_dirty(GT_SB(sb)->s_sbh);
sync_dirty_buffer(GT_SB(sb)->s_sbh);
sb->s_dirt=0;
unlock_kernel();
}
这个很简单很简单,简单到我都懒的讲了..
让我们来看看gt_count_free_blocks和gt_count_free_inodes
这俩函数都定义在inode.c中
[Copy to clipboard] [ - ]
CODE:
unsigned long gt_count_free_inodes(struct super_block *sb){
struct buffer_head *bh;
struct gt_inode *gt;
char *p;
unsigned long block=2; //索引节点表所在块
unsigned long count=0;//使用了的索引节点数
//然后遍历索引节点表
while(bh=sb_bread(sb,block)){
p=bh->b_data;
while(p<=(bh->b_data+GT_BLOCK_SIZE-GT_INODE_SIZE)){
gt=(struct gt_inode *)p;
if(gt->i_nlinks)
count++;//已经使用的索引节点数加一
p+=GT_INODE_SIZE;
}
brelse(bh);
if(block>GT_INODE_BLOCK_COUNT(sb))//如果到了索引节点表结尾则跳出
break;
block++;
}
return GT_SB(sb)->s_gs->s_inodes_count-count;//返回未使用的索引节点数
}
[Copy to clipboard] [ - ]
CODE:
unsigned long gt_count_free_blocks(struct super_block *sb){
struct gt_super_block *gs;
char *p;
int block=2;
gs=GT_SB(sb)->s_gs;
unsigned long used=0;//已经使用的块数
struct buffer_head *bh;
struct gt_inode * gt;
//遍历索引节点表,已经使用的块数其实就等于最后一个索引节点的i_end_block
while(bh=sb_bread(sb,block)){
p=bh->b_data;
while(p<=(bh->b_data+GT_BLOCK_SIZE-GT_INODE_SIZE)){
gt=(struct gt_inode *)p;
if(!gt->i_blocks)
used=gt->i_end_block;
}
brelse(bh);
}
return GT_BLOCKS(sb)-used;
}
gt_put_super(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static void gt_put_super(struct super_block *sb){
struct gt_sb_info *sbi=GT_SB(sb);
brelse(sbi->s_sbh);
sb->s_fs_info=NULL;
kfree(sbi);
}
这个函数释放掉磁盘超级块所在的缓冲块,释放掉内存超级块
gt_statfs(定义在super.c中)
[Copy to clipboard] [ - ]
CODE:
static int gt_statfs(struct dentry *dentry,struct kstatfs *buf){
struct gt_sb_info * sbi=GT_SB(dentry->d_sb);
struct gt_super_block *gs=sbi->s_gs;
buf->f_type=dentry->d_sb->s_magic;
buf->f_bsize=dentry->d_sb->s_blocksize;
buf->f_blocks=(gs->s_blocks_count-gs->s_first_data_block);
buf->f_bfree=gt_count_free_blocks(sbi);
buf->f_bavail=buf->f_bfree;
buf->f_ffree=gt_count_free_inodes(sbi);
buf->f_namelen=GT_NAME_LEN;
return 0;
}
这个函数获取文件系统状态
到这里超级块的操作就完成了,接下来是普通索引节点操作,只需要一个清空函数,其他的调用vfs默认的函数
[Copy to clipboard] [ - ]
CODE:
const struct inode_operations gt_file_inode_operations ={
.truncate =gt_truncate,
};
普通文件操作(在file.c中定义),只需要实现一个gt_sync_file
[Copy to clipboard] [ - ]
CODE:
const struct file_operations gt_file_operations ={
.llseek =generic_file_llseek,
.read =do_sync_read,
.write =do_sync_write,
.aio_read =generic_file_aio_read,
.aio_write =generic_file_aio_write,
.mmap =generic_file_mmap,
.open =generic_file_open,
.fsync =gt_sync_file,
};
来看看gt_sync_file(在file.c中定义)
[Copy to clipboard] [ - ]
CODE:
int gt_sync_file(struct file *file,struct dentry *dentry,int datasync){
struct inode *inode =dentry->d_inode;
int err,ret;
ret=sync_mapping_buffers(inode->i_mapping);
if(!(inode->i_state&I_DIRTY))
return ret;
if(datasync && !(inode->i_state&I_DIRTY_DATASYNC))
return ret;
err=gt_sync_inode(inode);
if(ret==0)
ret=err;
return ret;
}
函数调用gt_sync_inode(在inode.c中定义)
[Copy to clipboard] [ - ]
CODE:
int gt_sync_inode(struct inode *inode){
int ret=0;
struct buffer_head *bh;
bh=gt_update_inode(inode);//获取到磁盘索引节点所在的缓冲区
if(bh && buffer_dirty(bh)){//如果为脏,则同步
sync_dirty_buffer(bh);
if(buffer_req(bh)&&!buffer_uptodate(bh)){
printk("IO error syncing gt inode/n");
ret=-1;
}
}else if(!bh)
ret=-1;
brelse(bh);
return ret;
}
一个简单文件系统的实现
然后就是目录操作(在dir.c中定义)
[Copy to clipboard] [ - ]
CODE:
const struct file_operations gt_dir_operations ={
.llseek =generic_file_llseek,
.read =generic_read_dir,
.readdir =gt_readdir,
.fsync =gt_sync_file,
};
gt_readdir(在dir.c中定义)
linux文件系统对文件的读写是以页为单位的。
[Copy to clipboard] [ - ]
CODE:
static int gt_readdir(struct file *filp,void *dirent,filldir_t filldir){
loff_t pos=filp->f_pos;//目前读到的位置,文件内偏移
struct inode *inode=filp->f_path.dentry->d_inode;//目录文件的inode
unsigned int offset=pos & ~PAGE_CACHE_MASK;
unsigned long n=pos >> PAGE_CACHE_SHIFT;//目前读到第几页
unsigned long npages=gt_dir_pages(inode);//该文件在内存中占用的页数
unsigned chunk_size=sizeof(struct gt_dir_entry);
lock_kernel();
pos=(pos+chunk_size-1)&~(chunk_size-1);//将目前读到的位置调整到目录项开始
if(pos>=inode->i_size)
goto done;
for(;n<npages;n++,offset=0){//遍历文件所占用的页
char *kaddr,*limit;
char *p;
struct page *page=gt_get_page(inode,n);//获取到第n页
if(IS_ERR(page)){
continue;
printk("page is error/n");
}
kaddr=(char *)page_address(page);//第n页在内存中的地址
p=(kaddr+offset);//指向当前该读入的目录项的地址
limit=kaddr+gt_last_byte(inode,n)-chunk_size;//边界
for(;p<=limit;p=gt_next_entry(p)){
struct gt_dir_entry * de=(struct gt_dir_entry*)p;
if(de->ino){//目录项有对应的索引节点号
offset=p -kaddr;//在页内的偏移
unsigned name_len=strnlen(de->name,GT_NAME_LEN);
unsigned char d_type=DT_UNKNOWN;
int over=filldir(dirent,de->name,name_len,(n<< PAGE_CACHE_SHIFT)|offset,le32_to_cpu(de-
>ino),d_type);//调用VFS函数填充 dirent结构,有兴趣的朋友可以自己去研究
if(over){
gt_put_page(page);//如果错误就释放该页,跳到done标号处
goto done;
}
}
}
gt_put_page(page);
}
done:
filp->f_pos=(n<<PAGE_CACHE_SHIFT)|offset;//调整当前文件内偏移
unlock_kernel();
return 0;
}
这段函数调用了几个新函数,主要是使用作为参数传递进来的filldir函数来完成gt_readdir
下面来看看这段函数中调用的新函数
gt_dir_pages(在dir.c中定义)
[Copy to clipboard] [ - ]
CODE:
static inline unsigned long gt_dir_pages(struct inode *inode){
return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
}
很简单,就不解释了
gt_get_page(在dir.c中定义)
[Copy to clipboard] [ - ]
CODE:
static struct page *gt_get_page(struct inode *dir,unsigned long n){
struct address_space *mapping=dir->i_mapping;
struct page *page=read_mapping_page(mapping,n,NULL);
if(!IS_ERR(page)){
kmap(page);
if(!PageUptodate(page))
goto fail;
}
return page;
fail:
gt_put_page(page);
return ERR_PTR(-EIO);
}
gt_get_page主要通过调用read_mapping_page实现,另外还加了点错误判断等
gt_last_byte(在dir.c中定义)
[Copy to clipboard] [ - ]
CODE:
static unsigned gt_last_byte(struct inode *inode, unsigned long page_nr){
unsigned last_byte = inode->i_size;
last_byte -= page_nr << PAGE_CACHE_SHIFT;
if (last_byte > PAGE_CACHE_SIZE)
last_byte = PAGE_CACHE_SIZE;
return last_byte;
}
gt_last_byte函数用来返回文件在第n个页中的偏移,在前n-1个页中偏移都是PAGE_CACHE_SIZE
gt_next_entry(在dir.c中定义)
[Copy to clipboard] [ - ]
CODE:
static inline void *gt_next_entry(void *de){
return (void *)((char *)de+sizeof(struct gt_dir_entry));
}
这个函数也很简单,主用用来遍历目录项
gt_sync_file在前边已经分析完
这样目录操作也分析完了,下边是地址空间操作gt_aops
因为地址空间操作的操作对象是内存,与磁盘的组织方式无关,所以这个我们可以直接使用VFS的地址空间操作,这样就简单多了
gt_aops(在inode.c中定义)
[Copy to clipboard] [ - ]
CODE:
const struct address_space_operations gt_aops ={
.readpage =gt_readpage,
.writepage =gt_writepage,
.sync_page =block_sync_page,
.write_begin =gt_write_begin,
.write_end =generic_write_end,
.bmap =gt_bmap,
};
我就直接贴代码了,这部分对于我们实现一个文件系统来说很简单,如果你想继续深入下去,可以跟踪这些函数读下去
这些函数都是在inode.c中定义实现
[Copy to clipboard] [ - ]
CODE:
static int gt_writepage(struct page *page,struct writeback_control *wbc){
return block_write_full_page(page,gt_get_block,wbc);
}
static int gt_readpage(struct file *file,struct page *page){
return block_read_full_page(page,gt_get_block);
}
int __gt_write_begin(struct file *file,struct address_space *mapping,loff_t pos,unsigned len,unsigned flags,struct page
*pagep,void **fsdata){
return block_write_begin(file,mapping,pos,len,flags,pagep,fsdata,gt_get_block);
}
static int gt_write_begin(struct file *file,struct address_space *mapping,
loff_t pos,unsigned len,unsigned flags,struct page **pagep,void **fsdata){
*pagep=NULL;
return __gt_write_begin(file,mapping,pos,len,flags,pagep,fsdata);
}
static sector_t gt_bmap(struct address_space *mapping,sector_t block){
return generic_block_bmap(mapping,block,gt_get_block);
}
地址空间操作就是这样,接下来是对目录索引节点的操作 -
Linux下一个简单的文件系统实现
2012-04-18 10:21:36本代码实现了Linux下的一个简单的文件系统XORFS,针对Linux2.6版本。XORFS,是它“或许很强大”的意思。 -
api 文件长度_使用Rust实现一个简单的文件系统
2020-12-28 04:15:50来试试一起根据文件系统的基本概念实现一个简单的文件系统吧!为什么说它是一个简单的文件系统呢?因为本文中将要讲解和实现的文件系统仅仅实现了基本的读取写入和列目录操作,并且是基于内存和文件的,相对于真实...0x01 前言
你是否好奇一个文件系统是如何工作的?你是否见过 Electron 的 ASAR 文件?来试试一起根据文件系统的基本概念实现一个简单的文件系统吧!
为什么说它是一个简单的文件系统呢?因为本文中将要讲解和实现的文件系统仅仅实现了基本的读取写入和列目录操作,并且是基于内存和文件的,相对于真实世界中 NTFS、EXT系列 等在内核中实现并将数据存储在磁盘上的操作系统有较大的区别。
虽然 Unix 中可以通过 FUSE 实现一个文件系统,但考虑到它将会引入许多与文件系统概念无关的信息并且不是跨平台实现,我们将不会使用它,而是选择模仿真实世界中的文件系统的构造在一个文件中存储数据,并实现基本的编程 API。
本文将一本正经的指引你设计一个类似文件系统但又类似压缩文件,基于内存并同时可以保存到单个文件中的假文件系统。
前排提示:本文并非实现一个真正的文件系统,其中的代码及其理论也未经验证和测试,仅用于展示及学习文件系统的基本概念
0x02 了解文件系统结构
以 NTFS 为例,文件系统的结构主要分为四部分,分别为: Partition Boot Sector、Master File Table、System File、User Data [1],它们的作用如下:
- Partition Boot Sector,分区引导扇区,记录了分区的基本信息如:OEM ID、BPB 等
- Master File Table,主文件标,记录了分区中的目录结构、文件位置等信息
- System File,系统文件,包含 NTFS 日志、配额 等信息 ( NTFS 是一个日志式文件系统 )
- User Data,用户数据,用户存储在分区中的文件数据
0x03 文件结构提取和设计
很幸运我们并不需要设计一个高可用并且可以商用的文件系统,所以接下来将要确定一个简单的文件系统到底要包含哪些信息,以及如何利用手上现有的技术简化以避免直接处理二进制数据。
首先我们的文件系统并不会用于安装一个操作系统,所以 Partition Boot Sector 这一部分可以去掉。
而 MFT 这一部分则是必须的,虽然平常在图形化以及命令行中可以清晰的看到目录结构和文件位置,但文件真正存储于硬盘中的时候是一个扁平化且连续的结构,所以仍然需要一个位置来记录文件系统到底包含了哪些文件,以及它们的结构和尺寸。
系统文件这一步也同样是可以省略的部分,实现一个简单的学习用途的文件系统不需要考虑到性能和安全等问题。
所以最终我们的文件系统只需要两部分信息:MFT 长度、MFT 数据和用户数据部分。
接下来需要确定的是 MFT 数据如何存储以及包含哪些信息,仍然是考虑到我们的文件系统讲仅用于学习用途,MFT 的数据格式可以定为 JSON,为了简化遍历的过程和数据结构,在存储文件时将直接使用文件的完整路径作为文件的索引,同样 MFT 中常见的偏移位置将不会被记录。
根据以上思路可以得出 MFT 中可以只包含文件路径、文件长度两个信息。
0x04 代码中的数据结构
本文的目的是演示一个最简单的,只包含读写的文件系统,所以目前将不会考虑 MFT 存储到文件后再次读取时反序列化所对应的数据结构定义。
所以只需要确定两个数据类型:存储所有文件的数据结构、存储单个文件及它的信息的数据结构
鉴于无需考虑性能,以及前文中提到过因为一些原因这个文件系统将不会出现文件树,并且一般文件系统并不会允许同一个目录中出现同名的文件,使用 HashMap 存储文件列表将会是一个方便快捷的选择。
同时为了给整体的设计增加一些趣味性以及减少加载后的内存,文件系统还加入了压缩特性,所以现在我们需要的数据类型以及基本实现看起来类这样:
// 内部使用的数据类型 #[derive(Clone)] struct InternalFile { path: String, // Deflate 压缩后的数据 pub data: Vec<u8>, // 压缩前的尺寸 pub actual_size: usize } impl InternalFile { pub fn size(&self) -> usize { self.data.len() } } // API 返回给外部的数据类型 pub struct File { pub data: Vec<u8>, pub compressed_size: usize } impl File { pub fn size(&self) -> usize { self.data.len() } } pub struct FileSystem { files: HashMap<String, InternalFile> }
0x05 实现
基于前文提到的技术,例子中使用了 JSON 和 Libflate 两个外部库来帮助我们实现功能并减少工作量。
"Talk is cheap, show me the code",所以先来看看我们将要实现的代码,再来讲解思路吧!
extern crate libflate; extern crate json; use json::object; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::io::{Read, Write}; use std::fs; use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; use fs::libflate::deflate::{Encoder, Decoder}; // 内部使用的数据类型 #[derive(Clone)] struct InternalFile { path: String, // Deflate 压缩后的数据 pub data: Vec<u8>, // 压缩前的尺寸 pub actual_size: usize } impl InternalFile { pub fn size(&self) -> usize { self.data.len() } } // API 返回给外部的数据类型 pub struct File { pub data: Vec<u8>, pub compressed_size: usize } impl File { pub fn size(&self) -> usize { self.data.len() } } pub struct FileSystem { files: HashMap<String, InternalFile> } impl FileSystem { pub fn new() -> FileSystem { FileSystem { files: HashMap::new(), } } // 写入单个文件 pub fn write(&mut self, path: &str, data: &[u8]) { // 载入内存时使用 Deflate 算法压缩以便节省内存空间 let mut encoder = Encoder::new(Vec::new()); encoder.write_all(&data).unwrap(); self.files.insert( calculate_hash(&path.to_string().as_bytes()), InternalFile { path: path.to_string(), data: encoder.finish().into_result().unwrap(), actual_size: data.len() }, ); } // 读取单个文件 pub fn read(&self, path: &str) -> Result<File, &str> { let res = self.files.get(&calculate_hash(&path.to_string().as_bytes())); match res { Some(file) => { // 使用 Deflate Decoder 解压缩 let mut decoder = Decoder::new(&file.data[..]); let mut buf = Vec::new(); decoder.read_to_end(&mut buf).unwrap(); Ok(File { data: buf, compressed_size: file.data.len() }) }, None => Err("No such file"), } } // 获取压缩后的尺寸 pub fn get_size(&self) -> usize { let mut total_size = 0; for (_, file) in self.files.iter() { total_size += file.size(); } total_size } // 获取压缩前的尺寸 pub fn get_actual_size(&self) -> usize { let mut total_size = 0; for(_, file) in self.files.iter() { total_size += file.actual_size; } total_size } // 获取文件系统中的文件总数 pub fn get_file_count(&self) -> usize { self.files.len() } // 递归列出指定目录下的文件 pub fn dir(&self, directory: &str) -> Vec<String>{ let directory = Path::new(directory).join(""); let mut file = Vec::new(); for path in self.list() { // 我们的文件系统并非真实的树状结构,所以只需要判断文件路径以指定路径开始即可 if path.as_path().starts_with(&directory) { file.push(path.to_str().unwrap().to_string()) } } file } // 递归列出所有文件 pub fn list(&self) -> Vec<PathBuf> { let mut list = Vec::new(); for (_, file) in &self.files { list.push(PathBuf::from(&file.path)); } list } // 将文件系统数据转存到文件 pub fn save<T: AsRef<Path>>(&self, path: T) { let header_bytes = b"FSDUMP"; let mft_bytes = generate_mft(self.files.iter().map(|(_, file)| file.clone()).collect()); let mut f = fs::File::create(&path).unwrap(); f.write(header_bytes).unwrap(); // MFT length, usize to bytes = 8 bytes f.write(&(mft_bytes.len() as u64).to_le_bytes()).unwrap(); // MFT data f.write(&mft_bytes).unwrap(); // File contnt for (_, file) in &self.files { f.write(&file.data).unwrap(); } } pub fn load<T: AsRef<Path>>(&self, path: T) { // TODO: 尝试一下实现吧! } } fn calculate_hash<>(t: &[u8]) -> String { // 没有特殊需求,使用 HashMap 默认的 Hasher 即可 let mut h = DefaultHasher::new(); h.write(&t); format!("{:x}", h.finish()) } fn generate_mft (files: Vec<InternalFile>) -> Vec<u8> { let mut file_list = json::JsonValue::new_array(); for file in files.iter() { file_list.push(object!{ // 加载时所有文件均会被写入内存,所以记录尺寸连续读取即可 "length" => file.size(), "path" => file.path.clone() }).unwrap(); } file_list.dump().as_bytes().to_vec() }
0x06 实现中的思考
文件存储结构
为什么要使用字符串存储目录名,并使用 HashMap 作为存储所有文件的容器?这一点首先是考虑到一般文件系统使用的是 B-Tree 的方式来存储文件和目录结构,但引入 B-Tree 概念将会大幅度增加文章的概念数量、长度,以及代码复杂度。另外考虑到作为一个例子或是一个存储少量信息的文件系统,文件路径大概率是固定的,将数据扁平存储不会带来可见的性能问题和弊端,甚至有利于快速实现一个基本的列目录功能。
文件压缩算法
例子中使用的是 Deflate 算法,选择它的原因是在常见算法中解压缩速度相对较快[2],相比于 pithy、lz4f、lzg 等冷门的算法,Deflate 是 ZIP 文件背后最常见的算法,且常见于 Apache、Nginx 等主流软件当中。
MFT 中不记录偏移
在 MFT 中仅记录文件尺寸而不记录偏移量的原因是设计时不打算为这个简单的文件系统增加大文件的支持,MFT 实现中文件的顺序是有序的,并且也没有像真实的文件系统一样提供单独修改其中一部分数据的能力,所以实际上可以在读取 MFT 时根据顺序和之前的文件尺寸总和来确定下一个文件的偏移位置。
现实世界中的应用
除了了解文件系统以及压缩文件大概的工作方式,这个例子是否还有其他意义?实际上除了尝试编译代码,运行然后删除以外还可以基于类似的思路实现更多奇妙的功能,如:将多个动态链接库文件打包成为一个文件并在启动后从内存中调用它们,甚至将这个文件附加在可执行文件的末尾实现将调用多个外部库且无法静态编译的程序转化为单个可执行程序。
虽然自解压文件也可以做到,但是为什么不尝试一下更多的技术挑战呢?
使用 Rust 而不是 Javascript
显然在不考虑性能的场景下选择 Rust 单纯只是因为作者随手选择了 Rust。
0x07 测试代码
本文中提到的所有代码可在 Rust 1.42.0 中编译通过于执行
依赖库版本:- json: 0.11.13
- libflate: 0.1.18
代码
extern crate json; mod fs; use std::io::{stdin, Read}; use self::fs::*; struct FakeFile<'a> { path: &'a str, data: &'a [u8], } fn main() { let files = [ FakeFile { data: b"hello, world", path: "/file.txt" }, FakeFile { data: b"text file", path: "/folder/fileA" }, FakeFile { data: b"another text file", path: "/folder/fileB" }, FakeFile { data: b"", path: "/empty" }, FakeFile { data: &std::fs::read("C:Windowsexplorer.exe").unwrap(), path: "/explorer.exe" } ]; let mut fs = FileSystem::new(); for file in files.iter() { fs.write(file.path, &file.data) } println!("FileSystem has {} files with compressed size {} bytes, actual {} bytesn", fs.get_file_count(), fs.get_size(), fs.get_actual_size()); for path in fs.list() { let path = path.to_str().unwrap(); let file = fs.read(path).unwrap(); let len = if file.data.len() > 10 { 10 } else { file.data.len() }; let content_preview = format!("{:x?}", &file.data[..len]); println!("FileSystem has ({} bytes/{} bytes) file {} with content: "{}"", file.compressed_size, file.size(), path, &content_preview); } println!("n"); let dir = "/folder"; for path in fs.dir(dir) { println!("Directory "{}" have file {}", dir, path); } println!("nnPress enter key to exit..."); fs.save("./test.fsdump"); let _ = stdin().read(&mut [0u8]).unwrap(); }
编译和运行
- 使用 Cargo 创建一个新的 Rust 项目
- 将实现章节中的代码保存为 "src/fs/mod.rs"
- 将测试代码章节中的代码保存为 "src/main.rs"
- 添加 Crates 引用
- 使用 "cargo run" 编译并运行测试代码
上述代码演示了如何初始化前文中实现的 FileSystem,写入文本数据与本地读取到的文件,并调用 FileSystem 对象中实现的列目录与获取文件内容等功能。
0x08 附录
参考资料
[1] NTFS Basics, Active Data Recovery Software
[2] Squash Compression Benchmark, quixdb
Makeflow (makeflow.com) 是以流程为核心的项目管理工具,让研发团队能更容易地落地和改善工作流,提升任务流转体验及效率。如果你正为了研发流程停留在口头讨论、无法落实而烦恼,Makeflow 或许是一个可以尝试的选项。如果你认为 Makeflow 缺少了某些必要的特性,或者有任何建议反馈,可以通过 GitHub、语雀 或页面客服与我们建立连接。
-
简单文件系统的实现
2020-07-10 16:45:36简单文件系统的实现 夏斯华 xiasihua00001@hotmail.com 引言: 文件管理是操作系统中四大资源管理中重要一项。通过实现简单的文件系统的这样的方式,加深对其原理的理解。由于文件系统的博大和考虑的东西很多,再...**
简单文件系统的实现
夏斯华 xiasihua00001@hotmail.com
引言:
文件管理是操作系统中四大资源管理中重要一项。通过实现简单的文件系统的这样的方式,加深对其原理的理解。由于文件系统的博大和考虑的东西很多,再加上自己时间和能力的局限性,故实现一个较为简单的文件系统。概述:
文件系统主流的不乏有windows的FAT,FAT32,NTFS,以及Linux的等。由于多种原因,我不可能实现很复杂的。在此,规定如下:文件存储空间的管理:内存中开辟一块空间作为文件存储器,利用FAT表与位示图相结合的方式实现存储空间的分配和回收。文件系统的现场由真正的外存文件实现。
目录结构:原想树型结构较为复杂,想先采用二级目录结构(主文件目录、用户文件目录),结构包含:文件名、物理地址、长度等。但实际开发中采用了一种巧妙的方式,于是就采用了类似Linux系统树型目录结构。
物理结构:链接文件结构。
逻辑结构:流式文件(字符序列的集合,非“非结构的记录式文件”)。
存取方式:顺序存取。(有读写指针)。
实验内容:
算法和数据结构
FCB文件、目录控制信息struct fcb{
char szName[256];//文件、目录名 int iIndex;//索引 int iFatherIndex;//上级目录的索引 根目录为-1 //int iShared;//共享计数 int Type;//catalog、file两者中一个 //bool IsRoot;//是否为根目录 int iNextBlock;//所指第一块内存地址块 即首址 目录的话就为0 int iLength;//文件长度 目录为0 int state;//使用情况 int IsOpend;//是否为打开状态(只有在state为1,Type为 文件时才有效)打开为1
}FCB[FatLength];//文件或目录的控制信息
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> int Fat[FatLength];//FAT表和位示图的结合 用于分配物理块和空闲块的回收 **实现功能:** 该文件系统提供的操作: 格式化:对文件存储器进行格式化,即按照文件系统的结构对存储空间进行布局,并在其上创建根目录及其用于管理文件存储空间等的数据结构。 Format 创建子目录 mkdir 删除子目录 rmdir 显示目录 pwd 更改当前目录(回到上节目录) cd.. 创建文件 mk 打开文件 open 关闭文件 close 写文件 vi 读文件 cat 删除文件 rm **程序介绍:** 本文件系统采用了VC++6.0(MFC)开发。已经打上静态库(所以可执行文件Filesystem_static较大),假使执行机器应许(包含有MFC类库MFC42d.dll)即可用共享库方式Filesystem_share执行可执行文件。 **界面截图如下:** 界面风格有点象Linux的命令行。上面已经有所说明。执行可执行文件,进入界面如上所示。显示当前目录名/root之后有命令行提示符>.可以输入命令回车就可以。具体支持哪些命令可以help或?回车查看详情. 下面试一试多节目录管理: 建立如下结构 只要输入下列命令就可。 要想在文件f2中写写内容,然后显示; 还有其他的功能有待读者自己去尝试! **不足和改进:** 由于多种原因,没有解决文件目录同名应许问题。 文件内容修改功能没有增加。 **搬砖路上,希望对你有帮助!可以关注一下哟,持续更新!!** 参考文献: 操作系统教师讲义 原文链接:https://blog.csdn.net/xiasihua000001/article/details/274211 -
Node.js-node.js模拟实现一个简单的文件系统
2019-08-09 18:25:46node.js 模拟实现一个简单的文件系统 -
实现一个简单的aufs文件系统
2016-01-07 20:11:10http://book.51cto.com/art/201401/427823.htm 一个简单的aufs文件系统实现http://book.51cto.com/art/201401/427823.htm
-
简单文件系统的实现_300来行代码带你实现一个能跑的最小Linux文件系统
2021-01-07 18:57:36Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子:t@name-VirtualBox:/# lsbin boot cdrom dev etc home lib lib64 lost+found media mnt opt proc root run sbin snap ... -
使用ICE实现一个简单的文件系统
2013-07-20 19:11:57本章的目的是通过使用ICE来实现一个简单的文件系统应用,文件系统应用将实现一个简单的层次结构的文件系统,文件系统由目录和文件组成,目录是可以容纳目录或文件的容器。 二、文件系统的Slice 定义 文件和... -
实现简单的文件系统
2018-01-26 16:50:47•sfs:打开一个简单文件系统; •exit:退出打开的简单文件系统; •mkdir:创建子目录; •rmdir:删除子目录; •ls:显示目录; •cd:更改当前目录; •create:创建文件; •open:打开文件; •close:关闭... -
基于C语言的简单文件系统的实现
2019-07-23 17:33:01在内存中开辟一个虚拟磁盘空间作为文件存储分区,在其上实现一个简单的基于多级目录的单用户单任务系统中的文件系统。在推出该文件系统的使用时,应将虚拟磁盘上的内容以一个文件的方式保存到磁盘上,一遍下次可以... -
使用FAT实现一个简单的文件存储系统模拟磁盘存储
2010-01-04 11:38:34使用FAT实现一个简单的文件存储系统~能使Java 缓冲IO从这个文件系统中读写文件 -
操作系统 实验十 简单文件系统的实现
2020-12-18 10:08:53在内存中开辟一个虚拟磁盘空间作为文件存储分区,在其上实现一个简单的基于多级目录的单用户单任务系统中的文件系统。在推出该文件系统的使用时,应将虚拟磁盘上的内容以一个文件的方式保存到磁盘上,一遍下次可以将... -
简单文件系统的实现(经验分享)
2020-07-10 16:35:09实验内容: 通过对具体的文件存储空间的管理、文件的物理结构、目录结构和文件操作的实现,加深...•sfs:打开一个简单文件系统; •exit:退出打开的简单文件系统; •mkdir:创建子目录; •rmdir:删除子目录; •l -
一个简单的用户级文件系统的设计和实现
2012-09-03 18:55:54本实验用某个大文件,如c:\myDisk.img,文件存储整个文件卷中的所有信息。 一个文件卷实际上就是一张逻辑磁盘,磁盘中存储的信息以块为单位。 -
用c++中的文件处理的知识实现一个简单的学籍管理系统
2012-06-13 18:23:17一个应用C++的简单学籍管理系统,通过Visual Studio实现 具有简单的删除,修改等功能 -
一个简单的硬盘管理器的实现暨南京邮电大学操作系统——实验四:简单文件系统模拟实验
2020-12-01 23:01:06这次的实验要求写一个文件管理系统,使用FAT格式存储文件,但是我想既让要写,那就写好一点吧,所以这次实验,我的最终目的是创建一个虚拟硬盘,并编写一个符合UEFI规范的“驱动程序”来格式化这个硬盘。UEFI规范... -
一个简单的文件系统(操作系统课程设计)
2018-01-06 19:30:41一个简单的文件系统(操作系统课程设计)主要任务是对用户文件和系统文件进行管理,以方便用户使用,并保证文件的安全性。文件管理具有对文件存储空间的管理、目录管理、文件的读/写管理以及文件的共享与保护功能。... -
超级简单实用的ASP文件管理系统(一个ASP程序实现所有功能)
2021-02-18 19:30:18利用ASP单文件实现以下功能:<br>...br>2、文件系统浏览、删除、文本文件编辑。...4、整个系统由一个ASP文件完成,所有功能以涵数形式编写,主要利用FSO实现,没有数据库,超级简单实用,修改极其方便。 -
简单文件系统的实现_300来行代码实现最小Linux文件系统
2021-01-03 00:15:11Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子:root@name-VirtualBox:/# lsbin boot cdrom dev etc home lib lib64 lost+found media mnt opt proc root run sbin ... -
linux 创建一个简单的文件系统
2020-10-11 17:29:47图1显示了用户程序读取文件内容的过程,其中绿色框部分为一个文件系统最基本的东西,其余部分为一些为了提高性能而加的模块。 图1 建立一个小型文件系统 小型文件系统基本就是实现了图1的绿色部分;具体的代码... -
非常简单实用的ASP在线考试系统(一个文件实现)
2010-04-10 23:18:57一个asp文件实现,非常简单实用。ASP在线考试系统。
-
西南科技大学《电路分析》试题库(有答案).pdf
-
IDE---Intellij 创建maven工程没有提示SpringConfig的xml文件
-
西南科技大学《C语言》课后习题答案.pdf
-
清华大学历年考研复试机试真题 - 1454反序数
-
武汉理工大学《物理化学》期末考试试卷(含答案).pdf
-
PAT甲级-散列类型-1041 Be Unique解题思路
-
C51单片机学习日记:数码管
-
VMware vSphere ESXi 7 精讲/VCSA/VSAN
-
西南科技大学《模电》期末复习题(超全且含答案).pdf
-
浙江科技学院《流体力学》复习习题(含答案).pdf
-
企业数字化升级之路百家企业数字化转型发展分析报告.pdf
-
行政法与行政诉讼法--期末复习资料.pdf
-
1-算法leetcode 876 快慢指针
-
自动化测试Python3+Selenium3+Unittest
-
使用 Linux 平台充当 Router 路由器
-
浙江科技学院《概率论与数理统计》09-13历年期末考试试卷(含答案).pdf
-
深入【Get】与【Post】区别
-
app软件测试全栈系列精品课程
-
同济大学《线性代数B》期末考试试卷.pdf
-
NFS 实现高可用(DRBD + heartbeat)