分类:
2012-07-24 14:58:54
原文地址:UBIFS文件系统分析(四):重要数据结构 作者:mournjust
用leeming的话来说,一个大的工程中,最最核心的往往是数据结构体的定义。所以看代码不急着看c文件,而是主要看document和h文件,来理解设计者的思路,这样才能走对路。
1. struct ubi_device
UBI中对于一个UBI设备的抽象是以struct ubi_device来定义,其中包括了该UBI设备的各种信息。
struct ubi_device {
struct cdev cdev;
struct device dev;
int ubi_num;//UBI设备的标号,在ubiattach用户程序时以-d选项来输入
char ubi_name[sizeof(UBI_NAME_STR)+5];//ubi设备的名称
int vol_count;//在该UBI设备中有多少个volume
struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];
spinlock_t volumes_lock;
int ref_count;
int image_seq;
int rsvd_pebs;//保留的LEB数目
int avail_pebs;//可用的LEB数目
int beb_rsvd_pebs;//为坏块处理而保留的LEB数目
int beb_rsvd_level;//为坏块处理而保留的LEB的正常数目
int autoresize_vol_id;
int vtbl_slots;
int vtbl_size;//volume表的大小(bytes)
struct ubi_vtbl_record *vtbl;//内存中volume表的拷贝
struct mutex device_mutex;
int max_ec;//最大的erase counter
/* Note, mean_ec is not updated run-time - should be fixed */
int mean_ec;//平均erase counter
/* EBA sub-system's stuff */
unsigned long long global_sqnum;
spinlock_t ltree_lock;
struct rb_root ltree;
struct mutex alc_mutex;
/* Wear-leveling sub-system's stuff */
struct rb_root used;//一个红黑树,其中是已用的blcok
struct rb_root erroneous;// RB-tree of erroneous used physical eraseblocks
struct rb_root free;//红黑树的根,其中是没有用到的block
struct rb_root scrub;//需要擦除的blcok
struct list_head pq[UBI_PROT_QUEUE_LEN];
int pq_head;
spinlock_t wl_lock;
struct mutex move_mutex;
struct rw_semaphore work_sem;
int wl_scheduled;
struct ubi_wl_entry **lookuptbl;// a table to quickly find a &struct ubi_wl_entry object for any physical eraseblock,,一个struct ubi_wl_entry类型的数组,以pnum为下标,记录该UBI设备的每一个block
struct ubi_wl_entry *move_from;// physical eraseblock from where the data is being moved
struct ubi_wl_entry *move_to;// physical eraseblock where the data is being moved to
int move_to_put;//标志位,用于标志目的LEB是否被put
struct list_head works;// list of pending works
int works_count;// count of pending works
struct task_struct *bgt_thread;//UBI的后台进程
int thread_enabled;
char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2];//后台进程的名字
struct notifier_block reboot_notifier;//内核通知链
/* I/O sub-system's stuff */
long long flash_size;//MTD分区的大小
int peb_count;//LEB的数目
int peb_size;//LEB的大小(每一个block的大小)
int bad_peb_count;//坏块数目
int good_peb_count;//能使用的LEB数目
int erroneous_peb_count;
int max_erroneous;
int min_io_size;//最小操作单元的大小,也就是一个page的大小
int hdrs_min_io_size;
int ro_mode;
int leb_size;//逻辑块的大小,一般等于peb_size
int leb_start;//逻辑块块从物理块中那一块开始算,也就是之前的物理块保留用于其他目的
int ec_hdr_alsize;// size of the EC header aligned to @hdrs_min_io_size
int vid_hdr_alsize; //size of the VID header aligned to @hdrs_min_io_size
int vid_hdr_offset;//VID头部在一块之中的偏移量。一般是一个pagesize
int vid_hdr_aloffset;// starting offset of the VID header aligned to @hdrs_min_io_size
int vid_hdr_shift// contains @vid_hdr_offset - @vid_hdr_aloffset
unsigned int bad_allowed:1;
unsigned int nor_flash:1;// non-zero if working on top of NOR flash
struct mtd_info *mtd;//指向MTD分区信息,我们知道,UBI层是构建在MTD层之上的。
void *peb_buf1;//一个缓冲区,大小为一个block的大小
void *peb_buf2; //一个缓冲区,大小为一个block的大小
struct mutex buf_mutex;
struct mutex ckvol_mutex;
#ifdef CONFIG_MTD_UBI_DEBUG_PARANOID
void *dbg_peb_buf;
struct mutex dbg_buf_mutex;
#endif
};
2. struct ubi_vtbl_record
下一个重要的结构体struct ubi_vtbl_record,在认识这个结构体之前我们先看一副截图,
这幅截图是我们在attach一个设备时候的打印内容,红色的划线部分是我们要注意的内容:internal volume
什么是internal volume?它是与下面的user volume相区别的。
internal volume是内核使用来保持相应的信息的,那么它保持的是什么呢?它保持的是volume table。它是以struct ubi_vtbl_record数据结构的格式来保持的。
struct ubi_vtbl_record {
__be32 reserved_pebs;// how many physical eraseblocks are reserved for this volume
__be32 alignment;// volume alignment
__be32 data_pad;// how many bytes are unused at the end of the each physical eraseblock to satisfy the requested alignment
__u8 vol_type;//volume的类型,分为动态和静态两种,动态volume可以动态的改变它的大小
__u8 upd_marker;
__be16 name_len;//volume name length
__u8 name[UBI_VOL_NAME_MAX+1]; //volume name
__u8 flags;
__u8 padding[23];
__be32 crc;
} __attribute__ ((packed));
3. struct ubi_volume
struct ubi_volume是对UBI设备上每一个volume的抽象。
struct ubi_volume {
struct device dev;
struct cdev cdev;
struct ubi_device *ubi;//该volume在哪一个UBI设备上
int vol_id;//volume标号
int ref_count;//引用次数(不知道什么用途)
int readers;// number of users holding this volume in read-only mode
int writers;// number of users holding this volume in read-write mode
int exclusive;// whether somebody holds this volume in exclusive mode
int reserved_pebs;//该volume中保留的peb数
int vol_type;//volume类型
int usable_leb_size;// logical eraseblock size without padding
int used_ebs//可用PEB数目
int last_eb_bytes;// how many bytes are stored in the last logical eraseblock
long long used_bytes;//已用空间大小
int alignment;
int data_pad;
int name_len;//volume名字的长度
char name[UBI_VOL_NAME_MAX + 1];
int upd_ebs;
int ch_lnum; LEB number which is being changing by the atomic LEB change operation(这样在后面修改LEB数据的操作中可以看到)
int ch_dtype;
long long upd_bytes;
long long upd_received;
void *upd_buf;
int *eba_tbl;// EBA table of this volume,极其重要,LEB到PEB得影射关系需要查该表来获得
unsigned int checked:1;
unsigned int corrupted:1;
unsigned int upd_marker:1;
unsigned int updating:1;
unsigned int changing_leb:1;
unsigned int direct_writes:1;
};
4. struct ubi_scan_info
这个结构体是在attach的过程中使用的。在attach的过程中,UBIFS需要获知该设备上每一个PEB的状态,然后为重新挂载文件系统做准备。
struct ubi_scan_info {
struct rb_root volumes;//volume的红黑树的根节点
//下面是4个链表,是在扫描的过程将扫描的block进行分类,然后连接到下面4个链表中的其中一个。
struct list_head corr;
struct list_head free;
struct list_head erase;
struct list_head alien;
int bad_peb_count;//坏块数
int vols_found;//volume数
int highest_vol_id;//volume 的最高标号
int alien_peb_count;
int is_empty;//标志位,用于表示该UBI设备是否为空的,在上面所说的扫描过程被置位
int min_ec;//最小erase counter
int max_ec;//最大erase counter
unsigned long long max_sqnum;//64位的sqnum
int mean_ec;//平均erase counter
uint64_t ec_sum;
int ec_count;
int corr_count;
};
5 struct ubi_scan_leb
在上面的struct ubi_scan_info中我们说到了在attach操作中的扫描过程,并且说到了struct ubi_scan_info中的4个队列,是将扫描的每一个block的信息抽象,然后挂载到这些队列中去,下面就简单的说一下对于block扫描信息的抽象。
struct ubi_scan_leb {
int ec;//erase counter,用于均衡损耗目的,以后详细介绍
//每一个卷的eba_table就是由下面两个成员构成的。
int pnum;//物理块标号
int lnum;//逻辑块标号
int scrub;
unsigned long long sqnum;
union {
struct rb_node rb;
struct list_head list;
} u;
};
6. struct ubi_ec_hdr
我们知道UBIFS是一个Wear-level的文件系统,即均衡损耗。我们就以struct ubi_ec_hdr这个开始重要结构体的介绍。
struct ubi_ec_hdr {
__be32 magic;
__u8 version;
__u8 padding1[3];
__be64 ec; /* Warning: the current limit is 31-bit anyway! */
__be32 vid_hdr_offset;
__be32 data_offset;
__be32 image_seq;
__u8 padding2[32];
__be32 hdr_crc;
} __attribute__ ((packed));
我们注意其中的一个成员变量为_be64 ec,ec是什么,ec就是erase counter。我们知道NANDFLASH是的擦除是有次数限制的,当擦除的次数太多的时候,就会变成坏块。什么是均衡损耗,就是在文件系统的管理下,我们不能对其中的一块进行过多的擦除操作。
我们来看函数ensure_wear_leveling,它只要是来判断UBI设备是否需要进行均衡损耗的相关处理,这儿就有两个问题了。1.它判断的依据是什么。2.它会进行什么样的相关来避免对一个可擦出块进行过多的擦除操作。
那么我们先来回答第一个问题,在WL子系统中,所有的可擦出块都归WL子系统来进行管理。这是一个RB数,我们来其中的每一个结点。
struct ubi_wl_entry {
union {
struct rb_node rb;
struct list_head list;
} u;
int ec;
int pnum;
};
说白了,WL只关心一个东西,那么就是ec的数值。下面是wear_leveling_worker函数中的一段核心代码:
e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb);
e2 = find_wl_entry(&ubi->free, WL_FREE_MAX_DIFF);
if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD))
从used中队里中取出一个LEB,显然EC是最小的(每擦除一次,EC值加一),再从free队列中取出一个EC值最大的LEB。
如果两个LEB的ec差值大于了UBI_WL_THRESHOLD,那么就需要进行WL操作了。
那么多操作是什么呢?
err = ubi_eba_copy_leb(ubi, e1->pnum, e2->pnum, vid_hdr);
将内容从一个LEB搬到另外一个LEB中去。
6.struct ubi_vid_hdr
在上面EC头部中有一个成员变量是vid_hdr_offset,是指vid_hdr在FLASH中的偏移量,接着分析第二重要的数据结构struct ubi_vid_hdr。
struct ubi_vid_hdr {
__be32 magic;
__u8 version;
__u8 vol_type;
__u8 copy_flag;
__u8 compat;
__be32 vol_id;
__be32 lnum;
__u8 padding1[4];
__be32 data_size;
__be32 used_ebs;
__be32 data_pad;
__be32 data_crc;
__u8 padding2[4];
__be64 sqnum;
__u8 padding3[12];
__be32 hdr_crc;
} __attribute__ ((packed));
这其中最重要的成员变量是__be32 vol_id和__be32 lnum。vol_id是标示该LEB属于哪儿一个volume。Lnum是指与该PNUM相对于的lnum。对于上层而言,我们操作的是逻辑块,也就是lnum,但是最终需要将数据写进pnum中去,在ubi_eba_write_leb函数中有这样的一句:
pnum = vol->eba_tbl[lnum];
每一个volume都有一个eba_tbl,是在扫描的时候建立的。如果该lnum没有影射,那么调用ubi_wl_get_peb来获得一个pnum,并相应的修改volume的eba_tbl;
7. struct ubi_scan_volume
struct ubi_scan_volume {
int vol_id;//volume标号
int highest_lnum;//该volume中的最高逻辑块标号
int leb_count;//leb数目
int vol_type;//volume类型
int used_ebs;//已用PEB数
int last_data_size;
int data_pad;
int compat;
struct rb_node rb;//不清楚具体目的,猜想应该是一个节点的cache,缓存的是刚刚访问的结点
struct rb_root root;
};
这儿主要注意一下上面的struct rb_root root变量,这个成员是一个红黑树的根,用来链接在扫描的过程中发现的属于该volume的PEB。
这个结构体是在扫描的过程中读VID头部建立起来的关于volume的临时信息。
在ubifs-media.h中定义了很多的结构体,下面简单的解释一下。
在《A brief 。。。》中讲到了,UBIFS采用的node-structure。它的所有的数据都是以node的形式处理的。
struct ubifs_ch,其中ch的意思是指common header,是下面的所有结构体的共同部分。
struct ubifs_ino_node,是用来保存inode结点相关信息的。
struct ubifs_dent_node,用来来保存dent的相关信息。关于dent和inode请参考VFS部分的相关内容。
struct ubifs_data_node,用来保存具体数据的结点。
struct ubifs_trun_node,用来在trunk的时候写入journal区中的,只存在于journal区中。
struct ubifs_pad_node,用来进行数据填充的。
struct ubifs_sb_node,超结块结点, 用来记载superblock的相关信息,只存在在superblock 区中。
struct ubifs_mst_node,记录master结点的数据结构,记载node树的根结点以及其他信息。只存在在master area中。
struct ubifs_ref_node ,用于在更新数据的时候写入journal区,在commit的时候更新index树和LPT树,只存在在journal区中。
struct ubifs_idx_node,idx结点的头部,关于idx结点,请参考《a brief introduction to design of UBIFS》,只存在在main区中。
struct ubifs_branch,idx结点中的分支。
struct ubifs_cs_node,cs = commit start,用于在journal中表示一次commit的开始,只存在在journal区中。一次commit由一个ubifs_cs_node 和若干ubifs_ref_node 组成。
struct ubifs_orph_node ,用于在orphan区中记录相关信息的结点,关于orphan请参考《a brief introduction to design of UBIFS》。