注册本站  论坛  繁體中文

电脑技巧
手机 | MP3 | MP4 | 显卡 | 主板 | 显示器 | 光存储 | 笔记本 | 网络设备 | 移动存储 | 数码相机
键鼠 | CPU | 音箱 | GPS | 电视 | 服务器 | 投影机 | 机箱电源 | 品牌电脑 | 办公打印 |
| 网站首页 | Cisco | Windows | Linux | Java | Dotnet | Oracle | 网页设计 | 平面设计 | 安全 | 软件应用 | 电脑维修 | 办公维修 |
您现在的位置: 电脑技巧 >> Linux >> Unix >> Linux正文

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

文章来源:中国IT实验室收集整理 作者:佚名 更新时间:2007-9-20 【 】 【加入收藏

加锁和解锁的基本思想是,当某个进程进入临界区,它将持有一个某种类型的锁(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算法;//这一段我也不明白

      }

而在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\n");

    bh->b_lock=1;

}

static inline void unlock_buffer(struct buffer_head * bh)

{

    if (!bh->b_lock)

        printk("hd.c: free buffer being unlocked\n");

    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)\n",((unsigned long *) q)[-1]);

            printk("        q = %p\n",q);

            printk("       *q = %p\n",*q);

            printk("      tmp = %p\n",tmp);

            break;

        }

        tmp = tmp->next;

    } while (tmp != *q);

      }
  • 上一篇Linux:

  • 下一篇Linux:
  • 最 新 热 门
     VMware下Linux与真实主机共享上网
     Linux系统安全隐患及加强安全管理方法
     各类Unix和Linux密码丢失解决方法
     配置Linux操作系统的环境变量
     在Linux系统下刷新主板BIOS的好方法
     删除文件提示:Operation not permitted
     Linux系统下硬盘挂载详细说明
     Linux文本模式下如何发送带附件的Email
     在Linux自动生成一个保险密码的方法
     Linux商业电影动画制作软件列表
    最 新 推 荐
     在Fedora Core系统中共享Windows打印机
     不同的类UNIX操作系统密码破解方法介绍
     UNIX的分支 服务器上的FreeBSD操作系统
     UNIX操作系统的加锁解锁:等待事件及唤醒
     Solaris TELNET ftp时间超长的解决方法
     SCO UNIX到Linux操作系统的程序移植问题
     使用Unix命令行模式高效地进行文本编辑
     Solaris第一次亲密接触
     Unix学习方法-高手之路
     FreeBSD下3D功能的实现
    相 关 文 章

    各类Unix和Linux密码丢失解决方法
    Ubuntu Linux系统下TP-link无线上网方法
    Ubuntu Linux系统中如何使用Samba客户端
    Kubuntu 8.04 系统确认用KDE4取代LTS
    Ubuntu 7.10 Linux建立C与GTK的开发环境
    Ubuntu中Vsftp的配置和使用
    在Ubuntu Linux中配置虚拟主机
    Ubuntu系统下Trac+Subversion+Apache安装
    Ubuntu Linux系统配置SSH验证
    Linux下NFS文件mount超时的问题解答

    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告

     

    Copyright 2006-2008 pcjx.com All Rights Reserved
    电脑技巧 版权所有 粤ICP备06059145号 地图
    本网站所有内容未经许可不得转载或做其他使用