快捷搜索:

UNIX操作系统的加锁解锁:等待事件及唤醒

加锁和解锁的基础思惟是,当某个进程进入临界区,它将持有一个某种类型的锁(UNIX里一样平常来说是semaphore,Linux里一样平常是旌旗灯号量和原子量或者spinlock)。当其他进程在该进程没有开释该锁时试图进入临界区(加锁),它将会被设置造诣寝状态,然后被置入等待该锁的进程行列步队(某个优先级的)。当该锁被开释时,也便是解锁事故发生时,内核将从等待该锁的进程优先级行列步队中探求一个进程并将其置为就绪态,等待调整(schedule)。

在system v中,等待某一事故被称为sleep(sleep on an event),是以下文将统一应用就寝(sleep)。等待某事故也可以成为等待某个锁。(注:本文中的sleep与sleep()系统调用不合)

系统的实现将一组事故映射到一组内核虚拟地址(锁);而且事故不差别对待到底有若干进程在等待。这就意味着两个不规则的工作:

一、当某个事故发生时,等待该事故的一组进程均被唤醒(而不是仅仅唤醒一个进程),并且状态均被设置成绩绪(ready-to-run)。这时刻由内核选择(schedule)一个进程来履行,因为system v内核不是可抢占的(Linux内核可抢占),是以其他的进程将不停在就绪状态等待调整,或者再次进入就寝(由于该锁有可能被履行进程持有,而履行进程由于等待其他事故的发生而就寝),或者等其他进程在用户态被抢占。

二、多个事故映射到同一个地址(锁)。假设事故e1和e2都映射到同一个地址(锁)addr,有一组进程在等待e1,一组进程在等待e2,它们等待的事故不合,然则对应的锁相同。要是e2发生了,所有等待e2的进程都被唤醒进入就绪状态,而因为e1没有发生,锁addr没有被开释,所有被唤醒的进程又回到就寝状态。貌似一个事故对应一个地址会前进效率,但实际上因为system v长短抢占式内核,而且这种多对一映射异常少,再加上运行态进程很快就会开释资本(在其他进程被调整之前),是以这种映射不会导致机能的显明低落。

下面简单阐述一下sleep和wakeup的算法。

//伪代码

sleep(地址(事故),优先级)

返回值:进程能捕获的旌旗灯号发生导致的返回则返回1,当进程不能捕获的旌旗灯号发生时返回longjmp算法,否则返回0。

{

前进处置惩罚器履行等级以禁用所有中断;//避免竞态前提

将进程的状态设置为就寝;

根据事故将进程放入就寝哈希行列步队;//一样平常来说每个事故都有一个等待行列步队

将就寝地址(事故)及输入的优先级保存到进程表中;

if (该等待是弗成中断的等待)

//一样平常有两种就寝状态:可中断的和弗成中断的。弗成中断的就寝是指进程除了等待的事故外,

//不会被其他任何事故(如旌旗灯号)中断就寝状态,该环境不太常用。

{

高低文切换;//此处该进程履行高低文被保存起来,内核转而履行其他进程

//在别处进行了高低文切换,内核选择该高低文进行履行,此时该进程被唤醒

规复处置惩罚器等级来容许中断;

返回0;

}

// 被旌旗灯号中断的就寝

if (没有未递送的旌旗灯号)

{

高低文切换;

if (没有未递送的旌旗灯号)

{

规复处置惩罚器等级来容许中断;

返回0;

}

}

//有未递送的旌旗灯号

若进程还在等待哈希行列步队中,将其从该行列步队移出;

规复处置惩罚器等级来容许中断;

if(进程捕获该旌旗灯号)

返回1;

履行longjmp算法;//这一段我也不明白

}

void wakeup(地址(事故))

{

禁用所有的中断;

根据地址(事故)查找就寝进程行列步队;

for(每个在该事故上就寝的进程)

{

将该进程从哈希行列步队中移出;

设置状态为就绪;

将该进程放入调整链表中;

清除进程表中的就寝地址(事故);

if(进程不在内存中)

{

唤醒swapper进程;

}

else if(唤醒的进程更得当运行)

{

设置调整标志;

}

}

规复中断;

}

这两个函数对照难以理解,主如果在着末两条语句。在schedule()之前,切换的是当提高程的高低文,然则,切换回来之后,却是将本来正在就寝的进程置为就绪态。在履行schedule()之前,各指针如下图所示(欠美意思,不会粘贴图片):

---

| p |

---

||

/

----  Step 3  ---------

| *p |--------->| current |

----      ---------

|

X  Step 1

|

/

----------------  Step 2 -----

| Wait Process |

而在schedule()返回到这段代码之后,工作就不一样了。由于在step 3之后,current进程已经进入就寝,tmp指向的就寝进程的描述符也被保存下来。从schedule()返回之后,履行的代码仍旧是current,而tmp指向的仍旧是wait process,此时将其状态置为就绪,等待下一次调整。

与前两个函数比拟,wake_up相称简单:

//被唤醒的进程并不是顿时投入运行,而是让其得当运行

void wake_up(struct task_struct **p)

{

if (p && *p) {

(**p).state=0; //将要唤醒的进程状态置为就绪

*p=NULL;    //将进程移出等待的进程

}

}

有了sleep_on()和wake_up()之后,就可以对资本加锁了,如(硬盘缓冲加锁、等待缓冲可用、唤醒等待进程):

//锁住bh

static inline void lock_buffer(struct buffer_head * bh)

{

if (bh->b_lock)

printk("hd.c: buffer multiply locked

");

bh->b_lock=1;

}

static inline void unlock_buffer(struct buffer_head * bh)

{

if (!bh->b_lock)

printk("hd.c: free buffer being unlocked

");

bh->b_lock=0;

wake_up(&bh->b_wait);

}

static inline void wait_on_buffer(struct buffer_head * bh)

{

cli();  //禁止中断

while (bh->b_lock)

sleep_on(&bh->b_wait);

sti();  //规复中断

}

//Linux 0.99.15的sleep和wake_up的实现(支持等待行列步队):

static inline void __sleep_on(struct wait_queue **p, int state)

{

unsigned long flags;

struct wait_queue wait = { current, NULL };

if (!p)

return;

if (current == task[0])

panic("task[0] trying to sleep");

current->state = state;

add_wait_queue(p, &wait); //将当提高程加入等待行列步队

save_flags(flags);    //保存中断掩码

sti();          //樊篱中断

schedule();        //高低文切换

remove_wait_queue(p, &wait); //从等待行列步队中移除当提高程

restore_flags(flags);   //规复中断掩码

}

void wake_up(struct wait_queue **q)

{

struct wait_queue *tmp;

struct task_struct * p;

if (!q || !(tmp = *q))

return;

do {//将等待行列步队中唤醒队首进程

if ((p = tmp->task) != NULL) {

if ((p->state == TASK_UNINTERRUPTIBLE) ||

(p->state == TASK_INTERRUPTIBLE)) {

p->state = TASK_RUNNING;

if (p->counter > current->counter)

need_resched = 1;

}

}

if (!tmp->next) {

printk("wait_queue is bad (eip = %08lx)

",((unsigned long *) q)[-1]);

printk("    q = %p

",q);

printk("    *q = %p

",*q);

printk("   tmp = %p

",tmp);

break;

}

tmp = tmp->next;

} while (tmp != *q);

}

您可能还会对下面的文章感兴趣: