bmap和create_block
bmap完成文件的逻辑盘块号到物理盘块号的映射,create_block完成新建一个物理盘块并返回盘块号的操作。
为什么把这两个函数放在一块讲,因为这两个函数其实是同一个函数。请看定义
代码
142 int bmap(struct m_inode * inode,int block)
143 {
144 return _bmap(inode,block,0);
145 }
146
147 int create_block(struct m_inode * inode, int block)
148 {
149 return _bmap(inode,block,1);
150 }
都是调用的同一个函数_bmap
,下面我们来讲解_bmap函数
_bmap
代码
参见_bmap
讲解
这个函数看着一长串,但是功能也很清晰,就是读取或者创建一个数据块并返回块号。之所以写了这么多,是为了兼容处理直接块、一级间接块、二级间接块。
83-90行处理直接块的情况,如果指定要创建数据块的话,则调用new_block
,这个函数会在【linux0.12】盘块位图和i节点位图的管理与操作中来讲。其实直接块就是直接返回数据即可。
92-110行处理一级间接块的情况。第一次new_block
是新建间接块,间接块中存放直接块的块号。第二次new_block
是新建直接块,直接块中存放真实数据如目录项。可以看到有一次读取间接块的操作(100行),间接块中存放的就是一个个的块号,所以接下来以数组形式访问。
112-140处理二级间接块的情况。处理一级间接块是一样的思路,创建或者获取二级间接块,然后读入二级间接块,再根据二级间接块内容决定是创建一级间接块还是直接获取一级间接块的快号,获取到一级间接块块号之后读入,然后返回参数指定的块号。可以看到有两次读取块内容的操作,第一次是读取二级间接块,第二次是读取一级间接块。注意121行的block>>9
是为了除以512,因为每个一级间接块都有512个块号,这样可以算出指定块号是在二级间接块上的哪一个一级间接块。有点绕,但原理是清晰的。
iput
主要就是把inode的引用计数减一,并处理减一后的后果。之所以写了这么多,是为了兼容普通文件、块设备文件、管道文件等的处理。
代码
152 void iput(struct m_inode * inode)
153 {
154 if (!inode)
155 return;
156 wait_on_inode(inode);
157 if (!inode->i_count)
158 panic("iput: trying to free free inode");
159 if (inode->i_pipe) {
160 wake_up(&inode->i_wait);
161 wake_up(&inode->i_wait2);
162 if (--inode->i_count)
163 return;
164 free_page(inode->i_size);
165 inode->i_count=0;
166 inode->i_dirt=0;
167 inode->i_pipe=0;
168 return;
169 }
170 if (!inode->i_dev) {
171 inode->i_count--;
172 return;
173 }
174 if (S_ISBLK(inode->i_mode)) {
175 sync_dev(inode->i_zone[0]);
176 wait_on_inode(inode);
177 }
178 repeat:
179 if (inode->i_count>1) {
180 inode->i_count--;
181 return;
182 }
183 if (!inode->i_nlinks) {
184 truncate(inode);
185 free_inode(inode);
186 return;
187 }
188 if (inode->i_dirt) {
189 write_inode(inode); /* we can sleep - so do again */
190 wait_on_inode(inode);
191 goto repeat;
192 }
193 inode->i_count--;
194 return;
195 }
讲解
156行,当我们处理该inode时,可能有别的进程已经锁了该inode,所以我们先等待唤醒。
157行,如果引用计数已经为0,说明内核出错
159-169行,处理管道文件的情况。可以看到就是唤醒等待在该管道上的任务,并如果引用计数减一后为0,则释放管道文件占用的内存。可以看到,管道文件实际是占用了内存的,i_size
即是占用的内存地址。
174-177行,处理块设备文件的情况。主要就是刷新设备。
如果引用计数不为1,也就是还有别的进程在使用该inode,那么减一后直接返回。
如果链接数为0,则说明文件已经被删除,截断文件并释放inode即可。释放inode的操作在【linux0.12】盘块位图和i节点位图的管理与操作中讲解,实际上就是设置该i节点对应的位图位为0。
inode中数据与高速缓冲区数据不一致,则将i节点信息写入高速缓冲区。
iget
与iput函数功能相反,主要是找到节点号为nr的i节点,并将其引用计数加一。
前置知识
首先要知道磁盘i节点(struct d_inode
)和内存i节点(struct m_inode
)的结构。可以看到,磁盘i节点是内存i节点的子集。
内核中维护一个内存i节点表,里面是常驻内存的i节点,可以看到i节点表的大小(NR_INODE
)是和打开文件表的大小(NR_FILE
)是一样的。
内核中维护一个super_block表,里面存放了所有被挂载的文件系统的超级块结构。可以看到Linux对最多挂载的文件系统数量也有所限制(NR_SUPER
)。
代码
#define NR_INODE 64
#define NR_FILE 64
#define NR_SUPER 8
struct m_inode inode_table[NR_INODE]={{0,},};
struct super_block super_block[NR_SUPER];
88 struct d_inode {
89 unsigned short i_mode;
90 unsigned short i_uid;
91 unsigned long i_size;
92 unsigned long i_time;
93 unsigned char i_gid;
94 unsigned char i_nlinks;
95 unsigned short i_zone[9];
96 };
97
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];
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 };
247 struct m_inode * iget(int dev,int nr)
248 {
249 struct m_inode * inode, * empty;
250
251 if (!dev)
252 panic("iget with dev==0");
253 empty = get_empty_inode();
254 inode = inode_table;
255 while (inode < NR_INODE+inode_table) {
256 if (inode->i_dev != dev || inode->i_num != nr) {
257 inode++;
258 continue;
259 }
260 wait_on_inode(inode);
261 if (inode->i_dev != dev || inode->i_num != nr) {
262 inode = inode_table;
263 continue;
264 }
265 inode->i_count++;
266 if (inode->i_mount) {
267 int i;
268
269 for (i = 0 ; i<NR_SUPER ; i++)
270 if (super_block[i].s_imount==inode)
271 break;
272 if (i >= NR_SUPER) {
273 printk("Mounted inode hasn't got sb\n");
274 if (empty)
275 iput(empty);
276 return inode;
277 }
278 iput(inode);
279 dev = super_block[i].s_dev;
280 nr = ROOT_INO;
281 inode = inode_table;
282 continue;
283 }
284 if (empty)
285 iput(empty);
286 return inode;
287 }
288 if (!empty)
289 return (NULL);
290 inode=empty;
291 inode->i_dev = dev;
292 inode->i_num = nr;
293 read_inode(inode);
294 return inode;
295 }
296
讲解
253行,预先从i节点表中取得一个空闲的节点。取空闲i节点的过程放在下面来讲。
256-264行看指定节点号的i节点是否已经在内存i节点表中了。当我们拿到这个i节点时,可能有别的进程在对该i节点操作并上锁,于是等待唤醒。261行再判断的原因是,有可能再等待唤醒的过程中,别的进程改变了该i节点的属性,于是这里需要再判断。
266行-283行,对于挂载点的特殊处理。如果该节点是挂载点但是没有在全局super_block表中找到,则出错。否则重新设置inode的设备号,并将节点号设为ROOT_INO
重新查找。对应的场景是:如果在/tmp/etc
下挂载了一个文件系统,该文件系统的设备号为3,那么搜索内存i节点时不应该以根文件系统为设备号来查找,而是以新的文件系统的设备号3来查找,并且挂载点是新文件系统的根目录。
走到288行则说明没有在节点表中找到指定的节点,如果预先取i节点失败则出错返回。
否则设置新i节点的设备号和节点号,然后从磁盘i节点读取相关的属性。读取磁盘i节点到内存i节点的过程(read_inode
)放在下面来讲。
总结
总结一下这个函数干的事情,就是看一下内存结点表中是否有指定的节点,如果有则处理一下返回。如果没有则使用一个新的节点结构,并从磁盘i节点读入相关的属性。
get_empty_inode
代码
197 struct m_inode * get_empty_inode(void)
198 {
199 struct m_inode * inode;
200 static struct m_inode * last_inode = inode_table;
201 int i;
202
203 do {
204 inode = NULL;
205 for (i = NR_INODE; i ; i--) {
206 if (++last_inode >= inode_table + NR_INODE)
207 last_inode = inode_table;
208 if (!last_inode->i_count) {
209 inode = last_inode;
210 if (!inode->i_dirt && !inode->i_lock)
211 break;
212 }
213 }
214 if (!inode) {
215 for (i=0 ; i<NR_INODE ; i++)
216 printk("%04x: %6d\t",inode_table[i].i_dev,
217 inode_table[i].i_num);
218 panic("No free inodes in mem");
219 }
220 wait_on_inode(inode);
221 while (inode->i_dirt) {
222 write_inode(inode);
223 wait_on_inode(inode);
224 }
225 } while (inode->i_count);
226 memset(inode,0,sizeof(*inode));
227 inode->i_count = 1;
228 return inode;
229 }
讲解
代码很清晰,尝试从内存i节点表中找到一个引用计数为0的表项。如果没找到则出错停机。找到的话等待该i节点解锁并把i节点信息同步到高速缓冲区(write_inode
)。最后把i节点所有结构清零返回。
需要注意的是没有找到空闲i节点的处理,这里是打印节点表的每个设备号和节点号然后停机。这么处理的原因在于,linux规定了内存表大小和最大同时打开文件数量是相同的,如果打开超出最大数量的文件时会在sys_open
函数内就直接返回错误,从而理论上永远不会在内存表节点全部使用的情况下再获取空闲i节点,如果这样做则说明内核出错了,那么做停机处理。
read_inode
前置知识
我们要知道文件系统整体的结构,引导块+超级块+i节点位图块+盘块位图块+i节点块+数据块。如下图
代码
#define BLOCK_SIZE 1024
#define INODES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct d_inode)))
297 static void read_inode(struct m_inode * inode)
298 {
299 struct super_block * sb;
300 struct buffer_head * bh;
301 int block;
302
303 lock_inode(inode);
304 if (!(sb=get_super(inode->i_dev)))
305 panic("trying to read inode without dev");
306 block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
307 (inode->i_num-1)/INODES_PER_BLOCK;
308 if (!(bh=bread(inode->i_dev,block)))
309 panic("unable to read i-node block");
310 *(struct d_inode *)inode =
311 ((struct d_inode *)bh->b_data)
312 [(inode->i_num-1)%INODES_PER_BLOCK];
313 brelse(bh);
314 if (S_ISBLK(inode->i_mode)) {
315 int i = inode->i_zone[0];
316 if (blk_size[MAJOR(i)])
317 inode->i_size = 1024*blk_size[MAJOR(i)][MINOR(i)];
318 else
319 inode->i_size = 0x7fffffff;
320 }
321 unlock_inode(inode);
322 }
讲解
304行首先拿到该设备的超级块(304行)
306行根据超级块中的记录算出该节点的信息在哪个盘块上。2 + sb->s_imap_blocks + sb->s_zmap_blocks
可以算出第一个存放i节点的盘块号。如i节点号是1024,第一个存放i节点的盘块是12,每个盘块上可以存放64个i节点,那么该i节点就存放在第(1023-1)/64+12=75个盘块上.
308行然后读入该盘块中的数据
310-312完成赋值操作,使磁盘i节点上的信息赋值给内存i节点。
314-320行完成对块设备文件的特殊处理