上一篇:https://blog.csdn.net/weixin_42523774/article/details/103739139
· 上文我們解釋了文件系統加入Linux需要什麼內容,本文就以一個實際使用過的文件系統yaffs爲例,解釋一下一個文件系統如何融入Linux內核。
· 本文會結合這篇 yaffs2源代碼分析(最新版)。解釋yaffs實現的部分原理。
· 首先我們先看到 yaffs_vfs_multi.c 文件中的內容,與這個yaffs_vfs_single.c 有所區別,主要是前者支持所有的Linux版本,增加了很多的宏定義,而後者只支持某一個版本,我們可以通過前者的代碼對後者代碼進行修改來得到自己需要的版本。
1.初始化yaffs模塊
· 一個文件系統融入Linux的入口就是初始化函數,這裏的初始化函數比較簡單,大家看代碼中的增加的註釋即可。
static int __init init_yaffs_fs(void)
{
int error = 0;
struct file_system_to_install *fsinst;
yaffs_trace(YAFFS_TRACE_ALWAYS,
"yaffs built " __DATE__ " " __TIME__ " Installing.");
mutex_init(&yaffs_context_lock);
/* 創建proc文件系統的條目 */
my_proc_entry = create_proc_entry("yaffs",
S_IRUGO | S_IFREG, NULL);
/* 成功則初始化,否則退出 */
if (my_proc_entry) {
my_proc_entry->write_proc = NULL;
my_proc_entry->read_proc = yaffs_proc_read;
my_proc_entry->data = NULL;
} else {
return -ENOMEM;
}
/* 現在增加文件系統條目 */
fsinst = fs_to_install;/************ 關注此處,下節細講 ************/
/* 註冊文件系統 */
while (fsinst->fst && !error) {
error = register_filesystem(fsinst->fst);
if (!error)
fsinst->installed = 1;
fsinst++;
}
/* 有任何錯誤都會註銷這個文件系統 */
if (error) {
fsinst = fs_to_install;
while (fsinst->fst) {
if (fsinst->installed) {
unregister_filesystem(fsinst->fst);
fsinst->installed = 0;
}
fsinst++;
}
}
return error;
}
· exit_yaffs_fs 卸載yaffs函數是 init_yaffs_fs 的逆過程,各位同學可以自行分析;
2. yaffs的 super_block 結構
· yaffs文件系統在flash中是沒有存放super_block結構的,而是通過一個函數固定讀取這個結構,yaffs內部通過 yaffs_internal_read_super函數 獲取 super_block, 通過接口yaffs2_internal_read_super_mtd 傳遞給VFS。
· yaffs_internal_read_super 函數獲取super_block結構時,每次都是重新獲取。
· 上一節代碼中提到了 fs_to_install 這個沒有講,這個就是yaffs系統註冊的數組,可以通過如下代碼知道,這就是將yaffs和yaffs2 2個文件系統註冊到內核中所需的,內核通過這個數組知道存在哪些文件系統的。
/* Stuff to handle installation of file systems */
struct file_system_to_install {
struct file_system_type *fst;
int installed;
};
static struct file_system_to_install fs_to_install[] = {
{&yaffs_fs_type, 0},
{&yaffs2_fs_type, 0},
{NULL, 0}
};
· 通過路徑 init_yaffs_fs->fs_to_install->yaffs2_fs_type 找到這個yaffs2_fs_type,這裏最重要的就是 yaffs2_mount 函數,註冊到系統之後,就可以使用 mount 命令 將yaffs2文件系統 掛載到對應目錄下使用,mount系統調用最終就是調用yaffs2_mount函數,和我們做的open搜尋類似,大家可以自行搜尋這個過程,本文不細講。
static struct file_system_type yaffs2_fs_type = {
.owner = THIS_MODULE,
.name = "yaffs2",
.mount = yaffs2_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static struct dentry *yaffs2_mount(struct file_system_type *fs,
int flags, const char *dev_name, void *data)
{
return mount_bdev(fs, flags, dev_name, data,
yaffs2_internal_read_super_mtd);
}
static int yaffs2_internal_read_super_mtd(struct super_block *sb, void *data,
int silent)
{
return yaffs_internal_read_super(2, sb, data, silent) ? 0 : -EINVAL;
}
· 下面介紹 yaffs2_mount 如何實現,首先調用 mount_bdev函數,mount_bdev是針對塊設備掛載時使用的函數,主要實現就是設置其他的參數,然後調用 yaffs2_internal_read_super_mtd 來獲取文件系統的superblock,yaffs2_internal_read_super_mtd又直接調用了 yaffs_internal_read_super,下面專門詳細講解這個函數。
· 這個函數就幹3件事:
(1)從data中獲取yaffs屬性選項;
(2)依據屬性填充sb中的值,主要是sb->s_fs_info,就是struct yaffs_dev結構。
(3)最後創建根目錄節點。
· 這的確就是mount必須的步驟,通過參數設置,你可以理解yaffs文件系統的關鍵屬性是怎麼設置的,對於要移植yaffs的同學要仔細看一遍。我增加了很對註釋,方便大家理解。
/*
本函數用來獲得並初始化yaffs系統的super_block,主要通過對sb賦值,
參數1:yaffs_version 表示yaffs版本號,1表示yaffs1,2表示yaffs2;
參數2:struct super_block *sb是傳入的參數,後續會將其賦值之後返回;
參數3:void *data 用於傳遞屬性字符串,多個屬性用逗號隔開,通過yaffs_parse_options獲取屬性之後,會修改相應的參數項;
參數4:int silent 參數暫時沒有使用
返回值:返回修改以後的super_block
*/
static struct super_block *yaffs_internal_read_super(int yaffs_version,
struct super_block *sb,
void *data, int silent)
{
int n_blocks;
struct inode *inode = NULL;
struct dentry *root;
struct yaffs_dev *dev = 0;
char devname_buf[BDEVNAME_SIZE + 1];
struct mtd_info *mtd;//yaffs是mtd設備
int err;
char *data_str = (char *)data;//輸入的屬性字符串
struct yaffs_linux_context *context = NULL;//yaffs文件內容
struct yaffs_param *param;//yaffs參數
int read_only = 0;//是否只讀
struct yaffs_options options;//yaffs屬性
unsigned mount_id;
int found;
struct yaffs_linux_context *context_iterator;//
struct list_head *l;
if (!sb) {
printk(KERN_INFO "yaffs: sb is NULL\n");
return NULL;
}
sb->s_magic = YAFFS_MAGIC;//魔術字固定
sb->s_op = &yaffs_super_ops;/*****yaffs_super_ops是一個全局變量,已經定義完畢,賦值使用。下一節講解典型例子***/
sb->s_flags |= MS_NOATIME;
read_only = ((sb->s_flags & MS_RDONLY) != 0);//是否只讀
sb->s_export_op = &yaffs_export_ops;//yaffs的一些非標準操作,也是定義好的。
if (!sb->s_dev)//打印設備名
printk(KERN_INFO "yaffs: sb->s_dev is NULL\n");
else if (!yaffs_devname(sb, devname_buf))
printk(KERN_INFO "yaffs: devname is NULL\n");
else
printk(KERN_INFO "yaffs: dev is %d name is \"%s\" %s\n",
sb->s_dev,
yaffs_devname(sb, devname_buf), read_only ? "ro" : "rw");
if (!data_str)
data_str = "";
printk(KERN_INFO "yaffs: passed flags \"%s\"\n", data_str);
memset(&options, 0, sizeof(options));
// 解析yaffs選項,將data_str中的字符串解析成對於的flag
if (yaffs_parse_options(&options, data_str))
/* Option parsing failed */
return NULL;
sb->s_blocksize = PAGE_CACHE_SIZE;//PAGE大小,查找這個宏,你就可以知道yaffs的spare area大小的宏在哪,怎麼修改
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;//PAGE大小的二進制位數
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_read_super: Using yaffs%d", yaffs_version);
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_read_super: block size %d", (int)(sb->s_blocksize));
yaffs_trace(YAFFS_TRACE_ALWAYS,
"Attempting MTD mount of %u.%u,\"%s\"",
MAJOR(sb->s_dev), MINOR(sb->s_dev),
yaffs_devname(sb, devname_buf));
/* 檢查是否是MTD設備,這是由於yaffs是基於mtd設備的,不是mtd設備則直接退出。*/
if (MAJOR(sb->s_dev) != MTD_BLOCK_MAJOR)
return NULL; /* This isn't an mtd device */
/* 獲取此設備的設備信息 */
mtd = get_mtd_device(NULL, MINOR(sb->s_dev));
if (IS_ERR(mtd)) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device #%u doesn't appear to exist",
MINOR(sb->s_dev));
return NULL;
}
/* 檢查mtd設備是否是MTD_NANDFLASHCheck it's NAND */
if (mtd->type != MTD_NANDFLASH) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device is not NAND it's type %d",
mtd->type);
return NULL;
}
yaffs_trace(YAFFS_TRACE_OS, " erase %p", mtd->erase);
yaffs_trace(YAFFS_TRACE_OS, " read %p", mtd->read);
yaffs_trace(YAFFS_TRACE_OS, " write %p", mtd->write);
yaffs_trace(YAFFS_TRACE_OS, " readoob %p", mtd->read_oob);
yaffs_trace(YAFFS_TRACE_OS, " writeoob %p", mtd->write_oob);
yaffs_trace(YAFFS_TRACE_OS, " block_isbad %p", mtd->block_isbad);
yaffs_trace(YAFFS_TRACE_OS, " block_markbad %p", mtd->block_markbad);
yaffs_trace(YAFFS_TRACE_OS, " writesize %d", mtd->writesize);
yaffs_trace(YAFFS_TRACE_OS, " oobsize %d", mtd->oobsize);
yaffs_trace(YAFFS_TRACE_OS, " erasesize %d", mtd->erasesize);
yaffs_trace(YAFFS_TRACE_OS, " size %lld", mtd->size);
//自動選擇yaffs版本判斷,如果設備writesize >= 2K就可自動轉換 版本2
if (yaffs_auto_select && yaffs_version == 1 && mtd->writesize >= 2048) {
yaffs_trace(YAFFS_TRACE_ALWAYS, "auto selecting yaffs2");
yaffs_version = 2;
}
//自動選擇yaffs版本判斷,如果設備writesize == 512就可自動轉換 版本1
if (yaffs_auto_select && yaffs_version == 2 && !options.inband_tags &&
mtd->writesize == 512) {
yaffs_trace(YAFFS_TRACE_ALWAYS, "auto selecting yaffs1");
yaffs_version = 1;
}
if (yaffs_version == 2) {
/* 檢查類型函數是否支持Check for version 2 style functions */
if (!mtd->erase ||
!mtd->block_isbad ||
!mtd->block_markbad ||
!mtd->read ||
!mtd->write || !mtd->read_oob || !mtd->write_oob) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device does not support required functions");
return NULL;
}
//查看page size是否正確
if ((mtd->writesize < YAFFS_MIN_YAFFS2_CHUNK_SIZE ||
mtd->oobsize < YAFFS_MIN_YAFFS2_SPARE_SIZE) &&
!options.inband_tags) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device does not have the right page sizes");
return NULL;
}
} else {
/* Check for V1 style functions */
if (!mtd->erase ||
!mtd->read ||
!mtd->write || !mtd->read_oob || !mtd->write_oob) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device does not support required functions");
return NULL;
}
if (mtd->writesize < YAFFS_BYTES_PER_CHUNK ||
mtd->oobsize != YAFFS_BYTES_PER_SPARE) {
yaffs_trace(YAFFS_TRACE_ALWAYS,
"MTD device does not support have the right page sizes");
return NULL;
}
}
/* OK, so if we got here, we have an MTD that's NAND and looks
* like it has the right capabilities
* Set the struct yaffs_dev up for mtd
*/
//查看是否可讀不可寫
if (!read_only && !(mtd->flags & MTD_WRITEABLE)) {
read_only = 1;
printk(KERN_INFO
"yaffs: mtd is read only, setting superblock read only");
sb->s_flags |= MS_RDONLY;
}
//分配 yaffs_dev 和 yaffs_linux_context的空間, 後續就是操作填充dev信息。
dev = kmalloc(sizeof(struct yaffs_dev), GFP_KERNEL);
context = kmalloc(sizeof(struct yaffs_linux_context), GFP_KERNEL);
//檢查空間是否申請成功
if (!dev || !context) {
kfree(dev);
kfree(context);
dev = NULL;
context = NULL;
/* Deep shit could not allocate device structure */
yaffs_trace(YAFFS_TRACE_ALWAYS,
"yaffs_read_super failed trying to allocate yaffs_dev");
return NULL;
}
/* 後續大量都是在填充 dev 和其中的 param 和 os_context*/
memset(dev, 0, sizeof(struct yaffs_dev));
param = &(dev->param);
memset(context, 0, sizeof(struct yaffs_linux_context));
dev->os_context = context;
INIT_LIST_HEAD(&(context->context_list));
context->dev = dev;
context->super = sb;
dev->read_only = read_only;
sb->s_fs_info = dev;
dev->driver_context = mtd;
param->name = mtd->name;
/* Set up the memory size parameters.... */
/* 計算塊數 */
n_blocks =
YCALCBLOCKS(mtd->size,
(YAFFS_CHUNKS_PER_BLOCK * YAFFS_BYTES_PER_CHUNK));
param->start_block = 0; ///< 起始塊號
param->end_block = n_blocks - 1; ///< 結束塊號
param->chunks_per_block = YAFFS_CHUNKS_PER_BLOCK; ///< 每塊頁數
param->total_bytes_per_chunk = YAFFS_BYTES_PER_CHUNK; ///< 每頁字節數
param->n_reserved_blocks = 5; ///< 保留塊數
param->n_caches = (options.no_cache) ? 0 : 10; ///< 緩存個數
param->inband_tags = options.inband_tags;
param->disable_lazy_load = 1;///< 禁止延遲加載
param->enable_xattr = 1;///<使能擴展屬性
if (options.lazy_loading_overridden)
param->disable_lazy_load = !options.lazy_loading_enabled;
param->defered_dir_update = 1;///< 延遲目錄更新
if (options.tags_ecc_overridden)///<覆寫ECC標記
param->no_tags_ecc = !options.tags_ecc_on;
param->empty_lost_n_found = 1;///<自動將lost+found目錄置空
param->refresh_period = 500;///< 刷新週期爲500ms
if (options.empty_lost_and_found_overridden)
param->empty_lost_n_found = options.empty_lost_and_found;
/* 設置操作函數... and the functions. */
if (yaffs_version == 2) {
param->write_chunk_tags_fn = nandmtd2_write_chunk_tags;//寫chunk函數
param->read_chunk_tags_fn = nandmtd2_read_chunk_tags;//讀chunk函數
param->bad_block_fn = nandmtd2_mark_block_bad;//標記壞塊函數
param->query_block_fn = nandmtd2_query_block;//查詢塊狀態和序列號
yaffs_dev_to_lc(dev)->spare_buffer =
kmalloc(mtd->oobsize, GFP_NOFS);//申請一塊內存存放oob區
param->is_yaffs2 = 1;
param->total_bytes_per_chunk = mtd->writesize;//頁大小
param->chunks_per_block = mtd->erasesize / mtd->writesize;//塊大小/頁大小
n_blocks = YCALCBLOCKS(mtd->size, mtd->erasesize);//總大小/塊大小
param->start_block = 0;//起始大小爲0號
param->end_block = n_blocks - 1;//終止大小爲0號
} else {
/* use the MTD interface in yaffs_mtdif1.c */
param->write_chunk_tags_fn = nandmtd1_write_chunk_tags;
param->read_chunk_tags_fn = nandmtd1_read_chunk_tags;
param->bad_block_fn = nandmtd1_mark_block_bad;
param->query_block_fn = nandmtd1_query_block;
param->is_yaffs2 = 0;
}
/* 公共函數... and common functions */
param->erase_fn = nandmtd_erase_block;//擦除塊函數
param->initialise_flash_fn = nandmtd_initialise;//flash初始化函數
yaffs_dev_to_lc(dev)->put_super_fn = yaffs_mtd_put_super;
param->sb_dirty_fn = yaffs_touch_super;//標記superblock已經dirty函數
param->gc_control_fn = yaffs_gc_control_callback;//控制包收集回調函數
yaffs_dev_to_lc(dev)->super = sb;//dev與super相互綁定
param->use_nand_ecc = 1;
param->skip_checkpt_rd = options.skip_checkpoint_read;//是否忽略讀檢查點控
param->skip_checkpt_wr = options.skip_checkpoint_write;//是否忽略寫檢查點控
mutex_lock(&yaffs_context_lock);
/* 在yaffs_context_list中搜尋掛載ID,找到最小的mount_id. Get a mount id */
for (mount_id = 0, found = 0; !found; mount_id++) {
found = 1;
list_for_each(l, &yaffs_context_list) {
context_iterator =
list_entry(l, struct yaffs_linux_context,
context_list);
if (context_iterator->mount_id == mount_id)
found = 0;
}
}
context->mount_id = mount_id;
list_add_tail(&(yaffs_dev_to_lc(dev)->context_list),
&yaffs_context_list);//放入鏈表
mutex_unlock(&yaffs_context_lock);
/* Directory search handling... */
INIT_LIST_HEAD(&(yaffs_dev_to_lc(dev)->search_contexts));
param->remove_obj_fn = yaffs_remove_obj_callback;//刪除對象回調函數,如果有內容就不刪除
mutex_init(&(yaffs_dev_to_lc(dev)->gross_lock));
yaffs_gross_lock(dev);
err = yaffs_guts_initialise(dev);//dev參數初始化
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_read_super: guts initialised %s",
(err == YAFFS_OK) ? "OK" : "FAILED");
if (err == YAFFS_OK)
yaffs_bg_start(dev);
if (!context->bg_thread)
param->defered_dir_update = 0;
sb->s_maxbytes = yaffs_max_file_size(dev);
/* Release lock before yaffs_get_inode() */
yaffs_gross_unlock(dev);
/* 調用yaffs_get_inode創建根節點inode,並初始化設置,Create root inode */
if (err == YAFFS_OK)
inode = yaffs_get_inode(sb, S_IFDIR | 0755, 0, yaffs_root(dev));
if (!inode)
return NULL;
//設置目錄的操作函數
inode->i_op = &yaffs_dir_inode_operations;
inode->i_fop = &yaffs_dir_operations;
yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: got root inode");
root = d_alloc_root(inode);//給inode分配一個根目錄
yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: d_alloc_root done");
if (!root) {
iput(inode);
return NULL;
}
sb->s_root = root;//配置根目錄
sb->s_dirt = !dev->is_checkpointed;
yaffs_trace(YAFFS_TRACE_ALWAYS,
"yaffs_read_super: is_checkpointed %d",
dev->is_checkpointed);
yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: done");
return sb;
}
· 通過以上的理解,我們很清楚的能知道,Linux的inode,在yaffs中是以obj的方式來存儲的,相互之間轉化用的是 yaffs_inode_to_obj,這個函數告訴我們,obj 放在inode的i_private處存放,每個obj有一個obj_id作爲它的唯一標識,通過yaffs2源代碼分析(最新版)我們可以知道,這個並不是對應flash上的位置,只是一個文件編號。每一個文件系統對象都被賦予一個唯一的編號,作爲對象標識,也用於將該對象掛入一個散列表,加快對象的搜索速度。該文件頭在 flash 上的 chunk 序號纔是對應的物理位置。
· 我們是如何使用yaffs文件系統的呢?首先是yaffs_dev的cache申請一段空間來存放對應obj的緩存,修改之後會繼續使用,但是會標記成dirty,當後續多個dirty之後會一起寫進flash。
2.1 yaffs 的 super_block 結構中的操作函數
· super_block 結構的操作函數集合是將yaffs文件系統加入的關鍵操作,這裏能我們重點講解。
static const struct super_operations yaffs_super_ops = {
.statfs = yaffs_statfs,
#ifndef YAFFS_USE_OWN_IGET
.read_inode = yaffs_read_inode,
#endif
#ifdef YAFFS_HAS_PUT_INODE
.put_inode = yaffs_put_inode,
#endif
.put_super = yaffs_put_super,
#ifdef YAFFS_HAS_EVICT_INODE
.evict_inode = yaffs_evict_inode,
#else
.delete_inode = yaffs_delete_inode,
.clear_inode = yaffs_clear_inode,
#endif
.sync_fs = yaffs_sync_fs,
#ifdef YAFFS_HAS_WRITE_SUPER
.write_super = yaffs_write_super,
#endif
.remount_fs = yaffs_remount_fs,
};
· 其中 yaffs_put_super 和 yaffs_write_super 就是讀寫super_block,yaffs_sync_fs 就是同步文件系統。
· 我們取其中的一個函數來講解一下內部基本實現,yaffs_sync_fs 和 yaffs_write_super 的核心都是 yaffs_do_sync_fs。yaffs_do_sync_fs 的核心就是 調用 yaffs_flush_super 函數。下面簡單講一下這個函數:
static void yaffs_flush_super(struct super_block *sb, int do_checkpoint)
{
struct yaffs_dev *dev = yaffs_super_to_dev(sb);
if (!dev)
return;
yaffs_flush_inodes(sb);/*將緩存中的inode一個一個更新到flash中*/
yaffs_update_dirty_dirs(dev);/*檢查目錄是否dirty並更新*/
yaffs_flush_whole_cache(dev, 1);/*由於更新了inode,需要刷新緩存中的inode*/
if (do_checkpoint)
yaffs_checkpoint_save(dev);/*檢查是否inode層次結構是否變化*/
}
static void yaffs_flush_inodes(struct super_block *sb)
{
struct inode *iptr;
struct yaffs_obj *obj;
list_for_each_entry(iptr, &sb->s_inodes, i_sb_list) {
obj = yaffs_inode_to_obj(iptr);
if (obj) {
yaffs_trace(YAFFS_TRACE_OS,
"flushing obj %d",
obj->obj_id);
yaffs_flush_file(obj, 1, 0, 0);/*將緩存中的file更新到flash中*/
}
}
}
3. yaffs的 inode 結構
· yaffs文件系統的內部是obj爲單位的,這對應着 VFS 的 inode,這纔有 yaffs_fill_inode_from_obj 函數 和 yaffs_inode_to_obj 將 object 和 inode 的轉化。
上一節我們用到了yaffs_super_ops裏面剛好存放了一些inode操作函數,
我們下面簡單介紹幾個inode操作函數:
/*讀inode函數,爲了防止死鎖,需要加個鎖*/
static void yaffs_read_inode(struct inode *inode)
{
struct yaffs_obj *obj;
struct yaffs_dev *dev = yaffs_super_to_dev(inode->i_sb);
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_read_inode for %d", (int)inode->i_ino);
/* 依據當前狀態是不是在讀目錄,是就要鎖上 */
if (current != yaffs_dev_to_lc(dev)->readdir_process)
yaffs_gross_lock(dev);
/* 依據當前的inode號,查找對於的obj對象 */
obj = yaffs_find_by_number(dev, inode->i_ino);
/* 依據找到的obj對象,填充信息到inode對象中 */
yaffs_fill_inode_from_obj(inode, obj);
if (current != yaffs_dev_to_lc(dev)->readdir_process)
yaffs_gross_unlock(dev);
}
/* 清除inode被調用,是通知文件系統去釋放預緩存的inode數據,對象obj可能一直存在於flash中,而且只能把它從緩存中刪除,要不然這個對象obj可能已經被刪除了,通過如下調用做到這些 yaffs_delete_inode() -> clear_inode()->yaffs_clear_inode() */
static void yaffs_clear_inode(struct inode *inode)
{
struct yaffs_obj *obj;
struct yaffs_dev *dev;
/* 找到對應的obj對象 */
obj = yaffs_inode_to_obj(inode);
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_clear_inode: ino %d, count %d %s",
(int)inode->i_ino, atomic_read(&inode->i_count),
obj ? "object exists" : "null object");
/* obj如果存在,就可以清除它 */
if (obj) {
dev = obj->my_dev;
yaffs_gross_lock(dev);
/*真正的清除對象函數,見下面詳解*/
yaffs_unstitch_obj(inode, obj);
yaffs_gross_unlock(dev);
}
}
/* 拆除obj函數 */
static void yaffs_unstitch_obj(struct inode *inode, struct yaffs_obj *obj)
{
/* 首先讓obj和inode互不關聯 */
obj->my_inode = NULL;
yaffs_inode_to_obj_lv(inode) = NULL;
/* 如果之前對象obj釋放被延遲,那麼真正的釋放現在就開始。這將解決inode不一致的問題。*/
yaffs_handle_defered_free(obj);
}
void yaffs_handle_defered_free(struct yaffs_obj *obj)
{
if (obj->defered_free)
yaffs_free_obj(obj);
}
/* 釋放一個對象並將其放回空閒列表中 */
static void yaffs_free_obj(struct yaffs_obj *obj)
{
struct yaffs_dev *dev;
if (!obj) {
BUG();
return;
}
dev = obj->my_dev;
yaffs_trace(YAFFS_TRACE_OS, "FreeObject %p inode %p",
obj, obj->my_inode);
if (obj->parent)
BUG();
if (!list_empty(&obj->siblings))
BUG();
if (obj->my_inode) {
/* 如果obj仍然連接到一個緩存的inode。不要現在刪除,但標記以後刪除 */
obj->defered_free = 1;
return;
}
/* 將此對象obj從hash表中刪除 */
yaffs_unhash_obj(obj);
/* 將此對象obj添加到空閒鏈表中 */
yaffs_free_raw_obj(dev, obj);
dev->n_obj--;//設備使用對象數--
dev->checkpoint_blocks_required = 0; /* force recalculation */
}
· 通過 以上的函數調用
yaffs_clear_inode() -> yaffs_unstitch_obj() -> yaffs_handle_defered_free() -> yaffs_free_obj(),我們看到最終他只是把內容在鏈表中釋放掉了,並沒有真實的從flash中刪除。那麼什麼時候真實刪除obj呢,就是垃圾回收的時候,具體可以見 yaffs源代碼解析。
· 上文中 yaffs_fill_inode_from_obj 函數是通過obj創建一個inode對象,這裏也將對inode的操作函數也賦給了inode,我們可以看看inode操作如何實現:
static const struct inode_operations yaffs_dir_inode_operations = {
.create = yaffs_create,
.lookup = yaffs_lookup,
.link = yaffs_link,
.unlink = yaffs_unlink,
.symlink = yaffs_symlink,
.mkdir = yaffs_mkdir,
.rmdir = yaffs_unlink,
.mknod = yaffs_mknod,
.rename = yaffs_rename,
.setattr = yaffs_setattr,
.listxattr = yaffs_listxattr,
#ifdef YAFFS_USE_XATTR
.setxattr = yaffs_setxattr,
.getxattr = yaffs_getxattr,
.removexattr = yaffs_removexattr,
#endif
};
這裏,我們通過幾個例子理解一下inode操作函數:
3.1 lookup函數
我們之前將open函數所看到過lookup,最終搜尋可能調用到這裏來。
static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *n)
{
struct yaffs_obj *obj;
struct inode *inode = NULL; /* NCB 2.5/2.6 needs NULL here */
struct yaffs_dev *dev = yaffs_inode_to_obj(dir)->my_dev;
if (current != yaffs_dev_to_lc(dev)->readdir_process)
yaffs_gross_lock(dev);
yaffs_trace(YAFFS_TRACE_OS, "yaffs_lookup for %d:%s",
yaffs_inode_to_obj(dir)->obj_id, dentry->d_name.name);
/* 在此目錄dir的子文件下找到對應名字的obj對象。 */
obj = yaffs_find_by_name(yaffs_inode_to_obj(dir), dentry->d_name.name);
/* 如果是硬鏈接文件,就需要拷貝原obj的內容拷貝過來。 */
obj = yaffs_get_equivalent_obj(obj);
/* 調用 yaffs_get_inode() 的時候,不能加鎖 */
if (current != yaffs_dev_to_lc(dev)->readdir_process)
yaffs_gross_unlock(dev);
if (obj) {
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_lookup found %d", obj->obj_id);
/* 在obj緩存中,獲取對於obj_id的obj */
inode = yaffs_get_inode(dir->i_sb, obj->yst_mode, 0, obj);
} else {
yaffs_trace(YAFFS_TRACE_OS, "yaffs_lookup not found");
}
/* 如果 inode 爲 NULL, 就要重新創建dentry hash */
d_add(dentry, inode);
return NULL;
}
3.2 yaffs_mknod函數
· yaffs_mknod是 yaffs_create, yaffs_mkdir 實現的實質函數,因爲都是要創建一個obj對象,我們重點介紹一下。
· yaffs_mknod實現分爲2個部分,(1)獲取創建對象的內容信息,(2)依據不同的文件類型,創建相應的對象obj,(3)處理obj和inode、dentry之間的關係。
(1)獲取創建對象的內容信息, 這裏獲取了uid,gid,parent 和 dev這幾個創建obj必須的量。
static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode,
dev_t rdev)
{
struct inode *inode;
struct yaffs_obj *obj = NULL;
struct yaffs_dev *dev;
/*(1)獲取創建對象的內容信息 */
struct yaffs_obj *parent = yaffs_inode_to_obj(dir);
int error = -ENOSPC;
uid_t uid = YPROC_uid(current);
gid_t gid =
(dir->i_mode & S_ISGID) ? EXTRACT_gid(dir->i_gid) : YPROC_gid(current);
if ((dir->i_mode & S_ISGID) && S_ISDIR(mode))
mode |= S_ISGID;
if (parent) {
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_mknod: parent object %d type %d",
parent->obj_id, parent->variant_type);
} else {
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_mknod: could not get parent object");
return -EPERM;
}
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_mknod: making oject for %s, mode %x dev %x",
dentry->d_name.name, mode, rdev);
dev = parent->my_dev;
(2)依據不同的文件類型,創建相應的對象obj, 這裏調用的創建函數有些不同,但是實際都是調用的 yaffs_create_obj() 函數.
yaffs_gross_lock(dev);
switch (mode & S_IFMT) {
default:
/* 特殊文件(socket、 fifo, device ...) */
yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making special");
obj =
yaffs_create_special(parent, dentry->d_name.name, mode, uid,
gid, old_encode_dev(rdev));
break;
case S_IFREG: /* file 文件類型 */
yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making file");
obj = yaffs_create_file(parent, dentry->d_name.name, mode, uid,
gid);
break;
case S_IFDIR: /* directory 目錄類型*/
yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making directory");
obj = yaffs_create_dir(parent, dentry->d_name.name, mode,
uid, gid);
break;
case S_IFLNK: /* symlink 連接文件類型*/
yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making symlink");
obj = NULL; /* Do we ever get here? */
break;
}
/* Can not call yaffs_get_inode() with gross lock held */
yaffs_gross_unlock(dev);
· yaffs_create_special()等函數都是調用 yaffs_create_obj() 函數,然後 通過如下序列獲得內存空間的 yaffs_new_obj() -> yaffs_get_tnode() -> yaffs_alloc_raw_tnode() -> yaffs_create_tnodes() -> kmalloc() 通過這個調用序列查到 kmalloc,我們可以知道,yaffs存儲文件內容其實是在內存中,存入flash是在後續。
· 然後 yaffs_create_obj() 中是通過 yaffs_new_obj_id() 獲取對應的 obj_id ,obj_id 是通過某種規則制定的,核心是桶排序,提高搜索效率,存在yaffs_dev 下的 obj_bucket。
[yaffs_new_obj => yaffs_new_obj_id ]
static int yaffs_new_obj_id(struct yaffs_dev *dev)
{
int bucket = yaffs_find_nice_bucket(dev);
int found = 0;
struct list_head *i;
u32 n = (u32) bucket;
/*
* Now find an object value that has not already been taken
* by scanning the list, incrementing each time by number of buckets.
*/
while (!found) {
found = 1;
n += YAFFS_NOBJECT_BUCKETS;
list_for_each(i, &dev->obj_bucket[bucket].list) {
/* Check if this value is already taken. */
if (i && list_entry(i, struct yaffs_obj,
hash_link)->obj_id == n)
found = 0;
}
}
return n;
}
(3)處理obj和inode、dentry之間的關係。先過去對應的inode,然後對其說實話。
if (obj) {
inode = yaffs_get_inode(dir->i_sb, mode, rdev, obj);
d_instantiate(dentry, inode);
update_dir_time(dir);
yaffs_trace(YAFFS_TRACE_OS,
"yaffs_mknod created object %d count = %d",
obj->obj_id, atomic_read(&inode->i_count));
error = 0;
yaffs_fill_inode_from_obj(dir, parent);
} else {
yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod failed making object");
error = -ENOMEM;
}
return error;
}
4. 總結
· 通過以上的分析,我們基本弄清楚了yaffs是如何連接linux的,主要是 superblock 和inode的對應接口如何實現。這裏的內容需要和 yaffs源碼分析 結合起來才能深入明白這是如何實現。
· 如果你想修改相關參數,superblock的如何產生,你是必須知道的。
然後我們需要知道,yaffs文件系統是建立在mtd設備之上的,而mtd設備又是對flash和rom等設備的抽象。mtd設備的架構我們後續在分析。
· inode和obj之間相互轉化也是需要了解的,obj就是yaffs的真實對象,這和inode 有部分的差異,obj_id是obj的唯一標識,使用的是桶排序的索引。
· 到此,yaffs如何和linux連接起來依舊基本明白了,如果對yaffs具體是什麼時候將內容寫入flash 的,請看yaffs2源代碼分析(最新版)
如果覺得我的文章還有點收穫,就點個贊吧! d=====( ̄▽ ̄*)b
下一篇:https://blog.csdn.net/weixin_42523774/article/details/106914777