找到合适的缓冲块

// linux/fs/buffer.c

205 #define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
206 struct buffer_head * getblk(int dev,int block)
207 {
208 struct buffer_head * tmp, * bh;
209
210 repeat:
211 if (bh = get_hash_table(dev,block))
212 return bh;
213 tmp = free_list;
214 do {
215 if (tmp->b_count)
216 continue;
217 if (!bh || BADNESS(tmp)<BADNESS(bh)) {
218 bh = tmp;
219 if (!BADNESS(tmp))
220 break;
221 }
222 /* and repeat until we find something good */
223 } while ((tmp = tmp->b_next_free) != free_list);

struct buffer_head *tmp,*bh就是缓冲块头结构的两个变量,然后,进入repeat循环。

在repeat循环中,首先,先搜索hash表,检查是不是所需要的缓冲块已经被读入缓冲区了,即if(bh=get_hash_table(dev,block))  return bh;。那么,我们来看get_hash_table是如何实现的

//   linux/fs/buffer.c

183 struct buffer_head * get_hash_table(int dev, int block)
184 {
185 struct buffer_head * bh;
186
187 for (;;) {
188 if (!(bh=find_buffer(dev,block)))
189 return NULL;
190 bh->b_count++;
191 wait_on_buffer(bh);
192 if (bh->b_dev == dev && bh->b_blocknr == block)
193 return bh;
194 bh->b_count--;
195 }
196 }


可以看到,这个函数又会调用find_buffer,我们再往下走

// linux/fs/buffer.c

166 static struct buffer_head * find_buffer(int dev, int block)                 
167 {
168 struct buffer_head * tmp;
169
170 for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)
171 if (tmp->b_dev==dev && tmp->b_blocknr==block)
172 return tmp;
173 return NULL;
174 }

这个函数的主体是一个for循环,tmp=hash(dev,block),我们再看hash函数的实现

//linux/fs/buffer.c

128 #define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
129 #define hash(dev,block) hash_table[_hashfn(dev,block)]

可以看到,hash函数其实就是一个宏,hash函数为(dev^block)%NR_HASH,其中NR_HASH为307。这样,也就是说,先从标号为hash(dev,block)的缓冲头开始找,如果往下走,能够找到一个该设备的,且为该数据块的缓冲块已经存在于内存中(即if(tmp->b_dev==dev&&tmp->b_blocknr==block)。

如果找不到,则返回NULL

如果找到了,

187     for (;;) {
188 if (!(bh=find_buffer(dev,block)))
189 return NULL;
190 bh->b_count++;
191 wait_on_buffer(bh);
192 if (bh->b_dev == dev && bh->b_blocknr == block)
193 return bh;
194 bh->b_count--;
195 }

便会将这个缓冲块的引用计数加1,然后如果被锁,则等待其解锁,待锁解开后,if(bh->b_dev==dev&&bh->b_blocknr==block),即再进行一次确认,如果确认成功,将bh返回,否则,将bh的引用计数减1(因为在等待解锁前已经将其加1)。

这里的再次确认是有必要的,因为在等待其解锁的过程中,有可能这个块已经被别的进程使用了。




现在再回过头去看getblk函数

//linux/fs/buffer.c

210 repeat:
211 if (bh = get_hash_table(dev,block))
212 return bh;
213 tmp = free_list;
214 do {
215 if (tmp->b_count)
216 continue;
217 if (!bh || BADNESS(tmp)<BADNESS(bh)) {
218 bh = tmp;
219 if (!BADNESS(tmp))
220 break;
221 }
222 /* and repeat until we find something good */
223 } while ((tmp = tmp->b_next_free) != free_list);


这里,如果没找到,将会进入do while循环。  

if(tmp->count)   countinue;如果缓冲块正在被使用,则跳过  

直到找到一个合适的缓冲块。

如果循环了链表的一圈,还是没有找到合适的缓冲块,那么将这个进程挂起,也就是

224     if (!bh) {
225 sleep_on(&buffer_wait);
226 goto repeat;
227 }

待其被唤醒后,重新进入repeat循环,进行合适缓冲块的查找。

228     wait_on_buffer(bh);
229 if (bh->b_count)
230 goto repeat;
231 while (bh->b_dirt) {
232 sync_dev(bh->b_dev);
233 wait_on_buffer(bh);
234 if (bh->b_count)
235 goto repeat;

如果经过了一次repeat循环,找到了合适的缓冲块,那么,等待这个缓冲块的解锁,即wait_on_buffer(bh),注意这个函数并不是真正的等待,我们可以去看它的内部实现,只是在该缓冲块被锁定时,调用schedule函数进入任务切换,否则,函数直接返回。

同样,在等待的过程中,可能别的进程又将其占用了(这里面的原因个人认为应该和进程调试中,进程被唤醒的方式有关,也就是说,有两个进程同时等待同一个缓冲区,那么,在缓冲区可用时,系统肯定会发送一个信号,至于在schedule函数中,是怎么对task_struct数组进行遍历的,就决定了这里需不需要再进行检查)。

如果没被占用,但是该缓冲区已经被修改,那么进入while循环

sync_dev(bh->dev)是将所对应的设备进入写入操作。

在操作时,会对该块加锁,所以需要等待其解锁。

如果解锁完毕后,该块又被占用了(道理同上),那么,前功尽弃,只好重新进入repeat循环,再去寻找新的可用的缓冲块。

237 /* NOTE!! While we slept waiting for this block, somebody else might */
238 /* already have added "this" block to the cache. check it */
239 if (find_buffer(dev,block))
240 goto repeat;

这几句没看明白,虽然有注释。。。  等下问下同学和老师再回来做解释。

241 /* OK, FINALLY we know that this buffer is the only one of it's kind, */
242 /* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */
243 bh->b_count=1;
244 bh->b_dirt=0;
245 bh->b_uptodate=0;
246 remove_from_queues(bh);
247 bh->b_dev=dev;
248 bh->b_blocknr=block;
249 insert_into_queues(bh);
250 return bh;

看注释可以看出来,到这里,”OK,FINALLY we know that this buffer is the only one of it's kind,"

linus还故意用大写字母,可见他也是松了一口气~~

于是,将此缓冲区占用(bh->b_count=1),清除其脏标志(bh->dirt=0),以及有效标志(bh->b_uptodate=0)

既然它已经被占用了,那么便把它移出空闲链表

remove_from_queues(bh)

bh->b_dev=dev;

bh->b_blocknr=blockl;

移出后,把它放放到一个新的合适的位置。(因为这时候,有可能另一个进程需要使用同样的缓冲块,在hash时,最好能够尽快找到这个块。)

insert_into_queues(bh);

现在,一切大功告成,

return bh;









原文地址:https://www.cnblogs.com/yangce/p/2307997.html