find_entry
前置知识
这部分需要了解目录项的结构以及i节点的结构,目录项包括一个文件节点号和文件名,见下方代码中的定义。比较重要的是要知道i节点中包含7个直接块和1个一级间接块和1个二级间接块,直接块中存放物理盘块号。
代码
/* from fs.h */
163 struct dir_entry {
164 unsigned short inode;
165 char name[NAME_LEN];
166 };
98 struct m_inode {
99 unsigned short i_mode;
100 unsigned short i_uid;
101 unsigned long i_size;
102 unsigned long i_mtime;
103 unsigned char i_gid;
104 unsigned char i_nlinks;
105 unsigned short i_zone[9]; /*包括7个直接块和1个一级间接块和1个二级间接块*/
106 /* these are in memory also */
107 struct task_struct * i_wait;
108 struct task_struct * i_wait2; /* for pipes */
109 unsigned long i_atime;
110 unsigned long i_ctime;
111 unsigned short i_dev;
112 unsigned short i_num;
113 unsigned short i_count;
114 unsigned char i_lock;
115 unsigned char i_dirt;
116 unsigned char i_pipe;
117 unsigned char i_mount;
118 unsigned char i_seek;
119 unsigned char i_update;
120 };
/*from namei.c */
86 /*
87 * find_entry()
88 *
89 * finds an entry in the specified directory with the wanted name. It
90 * returns the cache buffer in which the entry was found, and the entry
91 * itself (as a parameter - res_dir). It does NOT read the inode of the
92 * entry - you'll have to do that yourself if you want to.
93 *
94 * This also takes care of the few special cases due to '..'-traversal
95 * over a pseudo-root and a mount point.
96 */
97 static struct buffer_head * find_entry(struct m_inode ** dir,
98 const char * name, int namelen, struct dir_entry ** res_dir)
99 {
100 int entries;
101 int block,i;
102 struct buffer_head * bh;
103 struct dir_entry * de;
104 struct super_block * sb;
105
106 #ifdef NO_TRUNCATE
107 if (namelen > NAME_LEN)
108 return NULL;
109 #else
110 if (namelen > NAME_LEN)
111 namelen = NAME_LEN;
112 #endif
113 entries = (*dir)->i_size / (sizeof (struct dir_entry));
114 *res_dir = NULL;
115 /* check for '..', as we might have to do some "magic" for it */
116 if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
117 /* '..' in a pseudo-root results in a faked '.' (just change namelen) */
118 if ((*dir) == current->root)
119 namelen=1;
120 else if ((*dir)->i_num == ROOT_INO) {
121 /* '..' over a mount-point results in 'dir' being exchanged for the mounted
122 directory-inode. NOTE! We set mounted, so that we can iput the new dir */
123 sb=get_super((*dir)->i_dev);
124 if (sb->s_imount) {
125 iput(*dir);
126 (*dir)=sb->s_imount;
127 (*dir)->i_count++;
128 }
129 }
130 }
131 if (!(block = (*dir)->i_zone[0]))
132 return NULL;
133 if (!(bh = bread((*dir)->i_dev,block)))
134 return NULL;
135 i = 0;
136 de = (struct dir_entry *) bh->b_data;
137 while (i < entries) {
138 if ((char *)de >= BLOCK_SIZE+bh->b_data) {
139 brelse(bh);
140 bh = NULL;
141 if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
142 !(bh = bread((*dir)->i_dev,block))) {
143 i += DIR_ENTRIES_PER_BLOCK;
144 continue;
145 }
146 de = (struct dir_entry *) bh->b_data;
147 }
148 if (match(namelen,name,de)) {
149 *res_dir = de;
150 return bh;
151 }
152 de++;
153 i++;
154 }
155 brelse(bh);
156 return NULL;
157 }
讲解
113行,得到目录文件下的目录项数,i_size
即是文件大小
116-130行,对如果以及是根目录还要进入上级目录的特殊处理,不详细讲代码,只讲下功能。如用户sjc已经在家目录或者已经在根目录还要进入上级目录,前者还是进入当前目录,后者则使用被安装的文件系统的目录i节点上。前者的用意是限制用户不能访问权限以外的目录,后者则是为了处理挂载了多个文件系统的情况,如/tmp/test
下挂载了一个文件系统ext2,那么test
目录就是这个文件系统的根i节点,此时如果访问test
的..
目录,则使用/tmp
的i节点,这样写的原因是对于根节点来说,已经没有目录来包含它的目录项了,只好取得超级块,再取超级块中记录的挂载目录,这个挂载目录是含有根节点的目录项的,于是使用挂载目录的i节点结构。
131行取直接块块号
133行从高速缓冲区读取直接块的内容,如果没读到则出错退出。bread的具体过程参见【linux0.12】文件高速缓冲区管理
137-154行是读取目录项的主要逻辑。138行判断是否以及读完了当前块的内容,如果当前块以及读完了,那么调用bmap
得到下一块块号,然后再读取下一块的内容。148行进行对比。bmap
的具体过程参见【linux0.12】i节点操作。其实功能很简单,如果传入的逻辑块号是直接块就直接返回,如果是一级间接块,则读取间接块中的内容,返回相应的块号。如果是二级间接块,则读取二级间接块内容,再根据读到的内容读取一级间接块,最后根据一级间接块的内容返回。
总结
find_entry
的主要功能也很清晰,根据块号读取对应块中的内容,然后进行对比。只不过因为有了直接块和间接块的区分,所以把取块号的操作单独抽象为一个函数bmap
new_entry
知道了如何查找目录项,其实新建目录项也就清晰了,本质就是在目录文件中新增一个struct entry
结构。下面来看代码
代码
159 /*
160 * add_entry()
161 *
162 * adds a file entry to the specified directory, using the same
163 * semantics as find_entry(). It returns NULL if it failed.
164 *
165 * NOTE!! The inode part of 'de' is left at 0 - which means you
166 * may not sleep between calling this and putting something into
167 * the entry, as someone else might have used it while you slept.
168 */
169 static struct buffer_head * add_entry(struct m_inode * dir,
170 const char * name, int namelen, struct dir_entry ** res_dir)
171 {
172 int block,i;
173 struct buffer_head * bh;
174 struct dir_entry * de;
175
176 *res_dir = NULL;
177 #ifdef NO_TRUNCATE
178 if (namelen > NAME_LEN)
179 return NULL;
180 #else
181 if (namelen > NAME_LEN)
182 namelen = NAME_LEN;
183 #endif
184 if (!namelen)
185 return NULL;
186 if (!(block = dir->i_zone[0]))
187 return NULL;
188 if (!(bh = bread(dir->i_dev,block)))
189 return NULL;
190 i = 0;
191 de = (struct dir_entry *) bh->b_data;
192 while (1) {
193 if ((char *)de >= BLOCK_SIZE+bh->b_data) {
194 brelse(bh);
195 bh = NULL;
196 block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
197 if (!block)
198 return NULL;
199 if (!(bh = bread(dir->i_dev,block))) {
200 i += DIR_ENTRIES_PER_BLOCK;
201 continue;
202 }
203 de = (struct dir_entry *) bh->b_data;
204 }
205 if (i*sizeof(struct dir_entry) >= dir->i_size) {
206 de->inode=0;
207 dir->i_size = (i+1)*sizeof(struct dir_entry);
208 dir->i_dirt = 1;
209 dir->i_ctime = CURRENT_TIME;
210 }
211 if (!de->inode) {
212 dir->i_mtime = CURRENT_TIME;
213 for (i=0; i < NAME_LEN ; i++)
214 de->name[i]=(i<namelen)?get_fs_byte(name+i):0;
215 bh->b_dirt = 1;
216 *res_dir = de;
217 return bh;
218 }
219 de++;
220 i++;
221 }
222 brelse(bh);
223 return NULL;
224 }
讲解
186行取得直接块的块号
188行拿到从高速缓冲区中拿到对应的缓冲头
191行拿到缓冲区内容
192-221行完成主要逻辑。首先判断是否读到了该块的末尾,如果是拿到下一个块的块号或者新建一个块,注意这里的create_block
并不是真正的新建块,而是如果下个块为空的话才新建,如果不为空则返回块号,create_block
的讲解也放在【linux0.12】i节点操作。213-214行设置文件名。注意,这个函数中并不设置文件的inode号,而是在客户调用完这个函数后手动设置。
总结
以在一个前2个直接块都被占满了的目录文件中新建目录项为例。读取第一个目录块时while循环中三个if判断总是走不到的,于是一直de++;i++;
。然后第一个数据块读完发现目录项全被占用,于是走到第一个if分支内,因为第二个直接块是存在的,于是create_block
直接返回第二个数据块块号,再读取第二个数据块的内容,读完第二个数据块发现还是第二个数据块的目录项还是全被占用了的,于是下次循环又走到第一个if分支,因为第三个直接块不存在,于是create_block
新建一个块并返回块号,接下来再调用reada
把新建块的内容读进来。这是突然发现满足了第二个if判断的要求,于是进入代码块执行。因为新建块的inode初始都为0,所以满足第三个if判断,然后设置新目录项的文件名返回。最后调用方再设置新目录项的inode号。