互斥信号量核心代碼的實作
當我們讀取磁盤中的一些資料時,我們會調用read這個函數進行讀取資料:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
接着這個read函數内部又調用了系統調用sys_read函數:
// linux-0.11/fs/read_write.c
int sys_read(unsigned int fd,char * buf,int count);
然後這個sys_read函數内部又調用了bread函數:
// linux-0.11/fs/buffer.c
struct buffer_head * bread(int dev,int block)
{...
struct buffer_head * bh;
ll_rw_block(READ,bh);
wait_on_buffer(bh);...
}
分析wait_on_buffer函數
1、上面sys_read函數内部調用了ll_rw_block函數,讀取目前該資源的互斥鎖隊列,然後調用了wait_on_buffer函數,等待該互斥信号量釋放。
// linux-0.11/fs/buffer.c
static inline void wait_on_buffer(struct buffer_head * bh)
{
cli(); // close the global interrupt
while (bh->b_lock)
sleep_on(&bh->b_wait);
sti(); // open the global interrupt
}
2、wait_on_buffer函數中的臨界區内部調用了sleep_on:
while (bh->b_lock)
sleep_on(&bh->b_wait);
這句代碼的意思是如果該互斥信号量資源沒有釋放,則調用sleep_on函數,阻塞目前任務。
分析sleep_on函數
// linux-0.11/kernel/sched.c
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;...
tmp = *p; // get the last blocked task
*p = current; // add the current blocking task
current->state = TASK_UNINTERRUPTIBLE; // set blocking
schedule(); // start schedule
if (tmp) // set it running when the last blocked task exists
tmp->state=0;
}
1、sleep_on函數的形參是一個task_struct的二重結構體指針變量,上述傳遞變量的是阻塞在隊列中的任務棧指針。
2、在sleep_on函數内部的開始,是向目前程序的棧中放入一個任務棧結構體指針變量tmp,然後将最近的一個因為該資源堵塞的程序棧指針儲存在tmp中。
3、接着将目前任務棧的指針變量儲存在該資源的阻塞隊列中,并設定目前任務棧進入堵塞狀态,随後進入排程函數進行排程。
4、當該程序再次被喚醒時,繼續設定該程序的上一個堵塞的程序狀态為就緒狀态(如果有的話)
如何喚醒堵塞的程序
上面是将程序堵塞的代碼,當磁盤這個讀資源被釋放後,又是怎樣喚醒那些因它而堵塞的資源呢?
1、當磁盤這一次讀完後,會進入磁盤讀中斷read_intr:
// linux-0.11/kernel/blk_drv/hd.c
static void read_intr(void)
{
...
end_request(1);
...
}
2、read_intr内部調用了end_request:
// linux-0.11/kernel/blk_drv/blk.h
static inline void end_request(int uptodate)
{
...
unlock_buffer(CURRENT->bh);
...
}
3、end_request内部調用了unlock_buffer:
// linux-0.11/kernel/blk_drv/ll_rw_blk.c
static inline void unlock_buffer(struct buffer_head * bh)
{...
bh->b_lock = 0; // reset the mutex
wake_up(&bh->b_wait); // wake up the current blocking tasks
}
該函數内部釋放了互斥信号量,然後喚醒是以阻塞的任務。
4、unlock_buffer中調用了wake_up:
// linux-0.11/kernel/sched.c
void wake_up(struct task_struct **p)
{
if (p && *p) {
(**p).state=0;
*p=NULL;
}
}
該函數内部将傳傳進來的任務棧進行喚醒,接着通過排程函數會喚醒最近一次堵塞的程序,然後進入此處繼續運作:
// linux-0.11/kernel/sched.c
if (tmp) // set it running when the last blocked task exists
tmp->state=0;
連續喚醒所有被堵塞的程序
1、通過上面的tmp變量,将每一次堵塞的任務棧指針形成一個遞歸隊列,當wake_up中将信号量釋放後,會依次通過schedule喚醒這些程序
2、然後這些程序再通過優先級的排程方法依次進入讀取磁盤,然後又會将該信号量鎖住,等待釋放,排程下一個程序進行讀取磁盤。以此往複,直到全部讀完磁盤為止。