lionel chang

导航

内核同步

一.概念

1.临界区:访问临界资源的代码段

临界资源:可以被多个进程同时访问的资源

2.竞争状态

多个内核任务同时访问同一临界区的状态

3.共享队列和加锁

这是一种特殊的竞争状态。队列中的每个节点代表一个”请求“。有出队列和入队列两个函数对其进行操作。多个进程之间通过加锁防止竞争。

4.确定保护对象

<1>.不需要保护的对象:

   a.内核任务的局部数据

   b.只被特定进程访问的数据

<2>.需要加锁的对象:大多数内核数据结构都需要加锁

如果有其他内核任务可以访问这些数据,那么就给这些数据加上某种形式的锁;如果其他东西能看到它,那么就要锁住它。

5.死锁

6.并发执行的原因

<1>.中断

<2>内核抢占

<3>睡眠

<4>对称多处理

7.内核任务

内核状态下可以独立执行的内核例程,每个内核任务运行时都拥有一个独立的程序计数器,栈和一组寄存器。

内核任务可分为以下几类:内核线程,系统调用,中断服务程序,异常处理程序和下半部。

二.同步措施

内核中有三种同步措施:原子操作,自旋锁和信号量

1.原子操作

1.1数据结构

该数据结构定义在types.h文件里面,如下:

typedef struct {
         volatile int counter;
} atomic_t;

 

 可见该结构就是一个计数器。

加volatile表示变量counter的值每次都必须从内存中读取最新的值,防止数据的不一致。

另外一个问题是:该结构里面只有一个int型的数据,为什么非要将它放入到一个结构体中呢?这是为了强制编译时的类型检查,保证只能通过内核定义的相关函数进行更改或读取。

1.2.函数

与原子操作相关的函数都在atomic_32.h文件里面找到。atomic_read()和atomic_set()都比较简单。

下面是atomic_add()的代码,其中使用了嵌入式汇编。

1 static inline void atomic_add(int i, atomic_t *v) 
2 {
3         asm volatile(LOCK_PREFIX "addl %1,%0"
4                      : "+m" (v->counter)
5                      : "ir" (i));
6 }

LOCK_PREFIX的含义:加锁,并且支持SMP系统。(可以参考定义前面的英文注释)

这段嵌入式汇编代码的含义是将从寄存器中读取的变量i加到内存中读出的变量v->counter上去。

其他函数与此类似。

2.自旋锁

自旋锁是专门为多处理器设计的。但它适合于短期的加锁,因为等待它的进程会处于忙等状态,这是非常浪费资源的;要进行长期加锁,可使用信号量,它会让等待进程去睡眠。

2.1.数据结构

typedef struct {
        raw_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
        unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
        unsigned int magic, owner_cpu;
        void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map dep_map;
#endif
} spinlock_t;

 

typedef struct {
        volatile unsigned int lock;
} raw_spinlock_t;

 

 2.2.函数

有两个基本的函数spin_lock和spin_unlock():

static inline void __spin_lock(spinlock_t *lock)
{
        preempt_disable();
        spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
        LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
}

2.2.1.preempt_disable()宏

#define preempt_disable() \
do { \
        inc_preempt_count(); \
        barrier(); \
} while (0)

 

 从字面上看,preempt_disable()是将CPU的抢占(preempt)功能取消掉。

首先,调用inc_preempt_count()给当前进程current的抢占计数器preempt_count加1,然后调用barrier()。关于barrier()可以看一下源代码:

#define barrier() __asm__ __volatile__("": : :"memory")

 

可见该宏的作用是将当前寄存器的内容写入内容中。

另一个问题是,为何要将preempt_disable()定义为do{...}while(0)的形式?这是为了防止宏替换出错,与给一个变量加括号是一个作用。

2.2.2.spin_acquire()

从字面上来看,该函数的作用应该是取得自旋锁。但是该宏最终被替换为do{  }while(0),即不执行任何语句。这儿有些不懂了。

2.2.3.LOCK_CONTENTDED()

#define LOCK_CONTENDED(_lock, try, lock)                        \
do {                                                            \
        if (!try(_lock)) {                                      \
                lock_contended(&(_lock)->dep_map, _RET_IP_);    \
                lock(_lock);                                    \
        }                                                       \
        lock_acquired(&(_lock)->dep_map, _RET_IP_);                     \
} while (0)

这儿的lock_contended()和lock_acquired()都被替换为do{  }while(0),还是不懂。

3.信号量

信号量适用于需要长期进行加锁的进程。

3.1.数据结构

struct semaphore {
        spinlock_t              lock;
        unsigned int            count;
        struct list_head        wait_list;
};

 

因为该结构中已经包含了自旋锁,所以不能让一个任务同时持有自旋锁和信号量。

3.2.函数

3.2.1.down()函数

down()获取一个信号量,但是建议使用down_interruptible()或down_killable().因为这两个函数支持中断信号。

这几个函数如果不能获取信号量的话,都会最终调用__down_common()函数:

static inline int __sched __down_common(struct semaphore *sem, long state,
                                                                long timeout)
{
        struct task_struct *task = current;
        struct semaphore_waiter waiter;

        list_add_tail(&waiter.list, &sem->wait_list);
        waiter.task = task;
        waiter.up = 0;

        for (;;) {
                if (signal_pending_state(state, task))
                        goto interrupted;
                if (timeout <= 0)
                        goto timed_out;
                __set_task_state(task, state);
                spin_unlock_irq(&sem->lock);
                timeout = schedule_timeout(timeout);
                spin_lock_irq(&sem->lock);
                if (waiter.up)
                        return 0;
        }

 timed_out:
        list_del(&waiter.list);
        return -ETIME;

 interrupted:
        list_del(&waiter.list);
        return -EINTR;
}

 

该函数的流程是:

将当前进程加入到等待队列,然后进入循环:

如果被信号中断或者超时的话,返回;

否则,设置进程状态,并且使当前进程睡眠;

3.2.2.up()函数

当等待队列为空时,sem的count加1,否则调用__up()函数。

static noinline void __sched __up(struct semaphore *sem)
{
        struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
                                                struct semaphore_waiter, list);
        list_del(&waiter->list);
        waiter->up = 1;
        wake_up_process(waiter->task);
}

该函数的过程是:删除等待队列首元素,并唤醒对应的进程。

注意这里的list_first_entry()的含义:获取队列的首元素的地址。

#define list_first_entry(ptr, type, member) \
        list_entry((ptr)->next, type, member)

 

posted on 2012-11-27 10:35  woshizyl  阅读(212)  评论(0编辑  收藏  举报