天天看点

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、然后这些进程再通过优先级的调度方法依次进入读取磁盘,然后又会将该信号量锁住,等待释放,调度下一个进程进行读取磁盘。以此往复,直到全部读完磁盘为止。

继续阅读