信号量与自旋锁

来源:互联网 发布:cad网络插座图标 编辑:程序博客网 时间:2024/05/22 00:22

1. 定义:
头文件: <asm/semaphore.h>
数据类型: struct semaphore
直接创建:
            void sema_init(struct semaphore *sem, int val); /* 其中val是信号量的初始值 */

辅助宏:
            DECLARE_MUTEX(name); /* 把一个称为name的信号量变量初始化为1 */
            DECLARE_MUTEX_LOCKED(name); /* 把一个称为name的信号量变量初始化为0 */

动态分配:
            /* 用于运行时的初始化 */
            void init_MUTEX(struct semaphore *sem);
            void init_MUTEX_LOCKED(struct semaphore *sem);

      在Linux世界中, P函数被称为down, 指的是该函数减小了信号量的值, 它也许会将调用者置于休眠状态, 然后等待信号量变得可用, 之后再授予调用者对被保护资源的访问权限. down函数有三个版本:
            /* 减小信号量的值, 并在必要时一直等待 */
            void down(struct semaphore *sem);

            /* 可中断版本, 常用 */
            void down_interruptible(struct semephore *sem);
     作为通常规则, 我们不应该使用非中断版本. 使用该函数, 如果操作被中断, 该函数会返回非0值, 而调用者不会拥有该信号量. 因此对该函数的正确使用需要始终检查返回值, 并做出相应的响应.

            /* 永远不会休眠, 如信号量在调用时不可获得, 立即返回非0值 */
            void down_trylock(struct semaphore *sem);

当一个线程成功调用down函数的某个版本之后, 就称为该线程拥有了该信号量, 可以访问被该信号量保护的临界区. 当互斥操作完成后, 必须释放该信号量.
            Linux的V函数是up:
            /* 调用up之后, 调用者不再拥有该信号量 */
            void up(struct semaphore *sem);

2. 举例:
      我们拥有一个共享数据结构:
            struct st_data
            {
                char name[32];
                char data[128];
                int data_len;
            };
     这个数据结构被多个进程同时访问.为了避免这些进程在访问该结构时产生竞态, 我们在该结构的底部为其加上信号量:
            struct st_data
            {
                char name[32];              /* name */
                char data[128];              /* data */
                int data_len;                  /* data length */
                struct semaphore sem; /* semaphore */
            };
    信号量在使用前必须进行初始化, 而且是在共享数据其他部分可用前初始化. 因此, 我们在其他数据赋值之前调用init_MUTEX, 否则会建立一个竞态, 即在信号量准备好之前, 有代码可能会访问它们.

            st_data data;
            init_MUTEX(&data->sem);
            setup_data(&data); /* 初始化数据 */
            ...
            ...

     接下来, 我们必须仔细检查代码, 确保在不拥有该信号量的时候不会访问data数据结构. 例如, 在data_write的开始处加入:
            if (down_interruptible(&data->sem))   
                return -ERESTARTSYS;
     这是检查down_interruptible的返回值, 如果返回非0值, 说明操作被中断. 这种情况下, 通常要做的工作是返回-ERESTARTSYS. 在得到这个返回值后, 内核会从头重新启动该调用, 或者将该错误返回给用户.如果我们返回-ERESTARTSYS, 则必须首先撤销已经做出的修改, 这样, 系统调用才可正确重试. 如果无法撤销这些操作, 则应该返回-EINTR, 表明中断.
不管data_write能否完成其他工作, 它都必须释放信号量:
            out:
                up(&data->sem);
                return retval;

      在data_write中有几个地方可能会产生错误, 包括内存分配失败等. 在这些情况下, 代码会执行goto out, 确保正确的完成信号量的释放工作.

原创粉丝点击