天天看點

linux-0.11互斥信号量核心代碼的實作

互斥信号量核心代碼的實作

當我們讀取磁盤中的一些資料時,我們會調用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、然後這些程序再通過優先級的排程方法依次進入讀取磁盤,然後又會将該信号量鎖住,等待釋放,排程下一個程序進行讀取磁盤。以此往複,直到全部讀完磁盤為止。

繼續閱讀