临界资源 互斥访问 内核中的up和down函数

信号量(semaphore)是用于保护临界区的一种常用方法。只有得到信号量的进程才能执行临界区代码,而没有得到信号量的进程进入休眠等待状态。

Linux系统中与信号量相关的操作主要有如下4种。


定义信号量


下面代码定义名为sem的信号量。

struct semaphore sem;

struct semaohore结构体在内核中定义如下:

/include/linux/semaphore.h目录下:

struct semaphore{

       spinlock_t              lock;

       unsigned int           count;

       struct list_head       wait_list;

};


2初始化信号量


/include/linux/semaphore.h目录下,void sema_init(struct semaphore*

sem, int val) 函数用于初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。

内核定义了两个宏来把sem的值设置为1或者0

#define init_MUTEX(sem)                  sema_init(sem, 1)

#define init_MUTEX_LOCKED(sem)         sema_init(sem, 0)

使用init_MUTEX(sem) 初始化信号量时,表示信号量最初是可以被获取的。而使用init_MUTEX_LOCKED(sem) 初始化信号量时,此信号量只有先被释放才可以获取。


3获取信号量


void down(struct semaphore *sem);

该函数用于获取信号量sem,它会导致睡眠,因此不能在中断上下文使用。

在内核里该函数的源代码如下:

kernel/semaphore.c文件里:

53 void down(struct semaphore *sem)

 54 {

 55         unsigned long flags;

 56

 57         spin_lock_irqsave(&sem->lock, flags);

 58         if (likely(sem->count > 0))

 59                 sem->count--;

 60         else

 61                 __down(sem);

 62         spin_unlock_irqrestore(&sem->lock, flags);

 63 }

这里重点看58行:if (likely(sem->count > 0)),这句话表示当获取信号量成功时,就执行sem->count—; 即对信号量的值减一。else表示获取信号量失败,此时调用__down函数进入睡眠状态,并将此进程插入到等待队列尾部。

内核定义了信号量的等待队列结构体:

193 struct semaphore_waiter {

194         struct list_head list;

195         struct task_struct *task;

196         int up;

197 };

此结构体是一个双向循环链表。

int down_interruptible(struct semaphore *sem);

该函数功能与down()类似,不同之处是,down()在获取信号量失败进入睡眠状态时的进程是不能被打断的,而down_interruptible()在进入睡眠状态时的进程能被信号打断,信号也会导致函数返回。下面我们也来看一看这个函数的源码:

kernel/semaphore.c文件里:

75 int down_interruptible(struct semaphore *sem)

 76 {

 77         unsigned long flags;

 78         int result = 0;

 79

 80         spin_lock_irqsave(&sem->lock, flags);

 81         if (likely(sem->count > 0))

 82                 sem->count--;

 83         else

 84                 result = __down_interruptible(sem);

 85         spin_unlock_irqrestore(&sem->lock, flags);

 86

 87         return result;

 88 }

这里我们可以看到,当获取信号量成功时,返回0,而获取信号量失败时,返回一个非0的值。在使用down_interruptible()函数获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS。如:

if ( down_interruptible(&sem) )

                     return -ERESTARTSYS;

这里还有一个问题:在获取信号量失败后,为什么down不能被中断,而down_interruptible却可以被中断呢?我们从downdown_interruptible的源代码可以得知,在获取信号量失败后,down函数运行了__down函数,而down_interruptible函数运行了__down_interruptible。那么让我们来看一下这两个函数的源码:

kernel/semaphore.c文件里:

236 static noinline void __sched __down(struct semaphore *sem)

237 {

238   __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);

239 }

240

241 static noinline int __sched __down_interruptible(struct semaphore *sem)

242 {

243 return __down_common(sem,TASK_INTERRUPTIBLE,                     MAX_SCHEDULE_TIMEOUT);

244 }

__down函数里,是把进程的状态设置为TASK_UNINTERRUPTIBLE ,即不可中断状态。

而在__down_interruptible里,是把进程的状态设置为TASK_INTERRUPTIBLE ,即可中断状态。这就解释了以上提出的问题。

4释放信号量

void up(struct semaphore *sem);

该函数用于释放信号量sem,唤醒等待者。

它的源代码如下:

178 void up(struct semaphore *sem)

179 {

180         unsigned long flags;

181

182         spin_lock_irqsave(&sem->lock, flags);

183         if (likely(list_empty(&sem->wait_list)))

184                 sem->count++;

185         else

186                 __up(sem);

187         spin_unlock_irqrestore(&sem->lock, flags);

183行的语句中,up函数首先判断等待队列是否为空,如果是空的话,就执行sem->count++;否则,执行__up() 函数,释放掉等待队列尾部的信号量。

信号量用于同步举例:

 

 

   


如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行单元的继续执行需要等待另一个执行单元完成某事,保证执行的先后顺序。

如上图所示,执行单元A执行代码区域a之前,必须等待执行单元B执行完代码区域b后释放信号量给它。

以下模块很好地使用了信号量:

#include <linux/init.h>

#include <linux/module.h>

#include <linux/sched.h>

#include <linux/sem.h>

 

struct semaphore sem1;

struct semaphore sem2;

 

int num[2][5] = {

       {0,2,4,6,8},

       {1,3,5,7,9}

};

int thread_one(void *p);

int thread_two(void *p);

int thread_one(void *p)

{

       int *num = (int *)p;

       int i;

       for(i = 0; i < 5; i++){

              down(&sem1);      //获取信号量1

              printk("%d ", num[i]);

              up(&sem2);    //释放信号量2

       }

       return 0;

}

int thread_two(void *p)

{

       int *num = (int *)p;

       int i;

       for(i = 0; i < 5; i++){

              down(&sem2);             //获取信号量2

              printk("%d ", num[i]);

              up(&sem1);           //释放信号量1

       }

       return 0;

}

static int lan_init(void)

{

       printk("lan is coming\n");

       init_MUTEX(&sem1);  //初始化信号量1 使信号量1最初可被获取

       init_MUTEX_LOCKED(&sem2);  //初始化信号量2,使信号量2只有被释放后才可被获取

       kernel_thread(thread_one, num[0], CLONE_KERNEL);

       kernel_thread(thread_two, num[1], CLONE_KERNEL);

       return 0;

}

static void lan_exit(void)

{

       printk("\nlan exit\n");

}

module_init(lan_init);

module_exit(lan_exit);


本文来源:谁不小心的CSDN博客 临界资源 互斥访问 内核中的up和down函数

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值