Linux内核中的同步机制

来源:互联网 发布:mysql误删数据恢复 编辑:程序博客网 时间:2024/05/01 11:43

Linux内核中的同步机制:原子操作linux内核信号API本文详细的介绍了、信号量、读写信号量和自旋锁的API,使用要求以及一些典型示例

一、原子操作

  所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。

  原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。

  原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下:

typedef struct  {  volatile int counter;  }  atomic_t;


  volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。

  原子操作API包括:

#define atomic_read(v)  (*(volatile int *)&(v)->counter)


  该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

atomic_set(atomic_t * v, int i);


  该函数设置原子类型的变量v的值为i。

#define atomic_set(v,i) (((v)->counter) = (i))


  该函数给原子类型的变量v增加值i。

atomic_sub(int i, atomic_t *v);


  该函数从原子类型的变量v中减去i。

int atomic_sub_and_test(int i, atomic_t *v);


  该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

void atomic_inc(atomic_t *v);


  该函数对原子类型变量v原子地增加1。

void atomic_dec(atomic_t *v);


  该函数对原子类型的变量v原子地减1。

int atomic_dec_and_test(atomic_t *v);


  该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_inc_and_test(atomic_t *v);


  该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_add_negative(int i, atomic_t *v);


  该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

int atomic_add_return(int i, atomic_t *v);


  该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。

int atomic_sub_return(int i, atomic_t *v);


  该函数从原子类型的变量v中减去i,并且返回指向v的指针。

int atomic_inc_return(atomic_t * v);


  该函数对原子类型的变量v原子地增加1并且返回指向v的指针。

int atomic_dec_return(atomic_t * v);

  该函数对原子类型的变量v原子地减1并且返回指向v的指针。

  原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。

  当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。



二、信号量(semaphore)(/include/linux/semaphore.h)

  Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。

  信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。

  一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

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



  信号量的API有:


#define DEFINE_SEMAPHORE(name)  \
        struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)


  该宏声明一个信号量name并初始化它的值为1,即声明一个互斥锁。



void sema_init (struct semaphore *sem, int val);

  该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。


void init_MUTEX_LOCKED (struct semaphore *sem);

  该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

void down(struct semaphore * sem);

  该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

int down_interruptible(struct semaphore * sem);

  该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

int down_trylock(struct semaphore * sem);

  该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用。

void up(struct semaphore * sem);

  该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。

  信号量在绝大部分情况下作为互斥锁使用,下面以console驱动系统为例说明信号量的使用。

  在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,它用于保护console驱动列表console_drivers以及同步对整个console驱动系统的访问。

  其中定义了函数acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来释放互斥锁console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。这三个函数实际上是分别对函数down,up和down_trylock的简单包装。

  需要访问console_drivers驱动列表时就需要使用acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释放信号量console_sem。

  函数console_unblank,console_device,console_stop,console_start,register_console和unregister_console都需要访问console_drivers,因此它们都使用函数对acquire_console_sem和release_console_sem来对console_drivers进行保护。

 




原创粉丝点击