Linux下的“锁”事儿

发表于:2016-6-30 13:46

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:禅宗花园...迷失的佛    来源:51Testing软件测试网采编

  原由
  之所以写这篇文章当然还是在面试中涉及了对本文标题的相关问题-互斥锁和自旋锁的区别。听到这个问题的时候,我是比较忐忑的。互斥锁我还能简单说一些,但是对于自旋锁的了解几乎为零。为此,将总结Linux下的相关锁-那些“锁”事儿。知之为知之,不知为不知,是知也。不懂的地方,尽快查漏补缺!
  简介
  我们晓得在Linux内核中,同步机制是一大特性。比较经典的有原子操作、spin_lock(自旋锁)、mutex(互斥锁)、semaphore(信号量)等。
  原子操作
  原子操作,也是数据库事务的一大特性。就是该操作绝不会在执行完之前被任何任务或者事件终止,要不全部执行,要不全部不执行。它是最小的执行单位,原子操作需要硬件的支持,因此是架构相关的。它的API和原子类型定义都在内核源码树的include/asm/atomic.h文件中,都是用汇编语言实现。
  原子操作主要用于实现资源计数,很多引用计数就是通过原子操作实现的。
  原子类型定义:
  typedef struct{
  volatile int counter;
  }atomic_t;
  volatile关键字修饰的字段可以通知gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。
  原子操作的API:
  1)atomic_read(atomic_t *v);
  作用:对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。
  2)atomic_set(atomic_t *v,int i);
  作用:设置原子类型的变量v的值为i。
  3)atomic_add(int i,atomic_t *v);
  作用:给原子类型的变量v增加值i。
  4)atomic_sub(int i,atomic_t *v);
  作用:从原子类型的变量v中减去i。
  5)atomic_sub_and_test(int i, atomic_t *v);
  作用:从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。
  6)atomic_inc(atomic_t *v);
  作用:对原子类型变量v原子地增加1。
  7)atomic_dec(atomic_t *v);
  作用:对原子类型的变量v原子地减1。
  8)atomic_dec_and_test(atomic_t *v);
  作用:对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
  9)int atomic_inc_and_test(atomic_t *v);
  作用:对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。
  10)atomic_add_negative(int i, atomic_t*v);
  作用:对原子类型的变量v原子地增加i,并判断结果是否为负数,如果是,返回真,否则返回假。
  11)atomic_add_return(int i, atomic_t *v);
  作用:对原子类型的变量v原子地增加i,并且返回指向v的指针。
  12)int atomic_sub_return(int i, atomic_t *v);
  作用:从原子类型的变量v中减去i,并且返回指向v的指针。
  13)int atomic_inc_return(atomic_t * v);
  作用:对原子类型的变量v原子地增加1并且返回指向v的指针。
  14)int atomic_dec_return(atomic_t * v);
  作用:对原子类型的变量v原子地减1并且返回指向v的指针。
  原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构structipq描述了一个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实现)。
  信号量
  信号量的本质也是一个计数器,用来记录对某个资源(如共享内存)的存取状况。用来协调不同进程间的数据对象,最主要的应用是共享内存方式的进程间通信。
  一般情况,为了获取共享资源,进程需要执行如下步骤:
  1)测试控制该资源的信号量;
  2)如果该信号量为正,就允许使用该资源,进程将型号量减一;
  3)如果为0,则该资源目前不可用,进程sleep,知道信号量值大于0,才能被唤醒,从步骤1)开始执行;
  4)当进程不再使用某信号量控制的资源时,信号量值加1,。如果此时有进程在sleep并等待此信号量,则可以唤醒该进程。
  信号量的定义在头文件/usr/src/linux/include/linux/sem.h 中,信号量是一个数据集合,用户可以单独使用这一集合中的每个元素。
  Linux2.6.26下定义的信号量结构体:
  struct semaphore {
  spinlock_t                lock;
  unsigned int             count;
  struct list_head        wait_list;
  };
  从以上信号量的定义中,可以看到信号量底层使用到了spinlock的锁定机制,这个spinlock主要用来确保对count成员的原子性的操作(count--)和测试(count > 0)。
  信号量的pv操作
  信号量的P操作
  (1)void down(struct semaphore *sem);
  (2)int down_interruptible(struct semaphore *sem);
  (3)int down_trylock(struct semaphore *sem);
  (1)中的函数根据2.6.26中的代码注释,这个函数已经out了(Use of this function is deprecated),所以从实用角度,彻底忘了它吧。
  (2)最常用,函数原型:
/**
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore.  If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a signal, this function will return -EINTR.
* If the semaphore is successfully acquired, this function returns 0.
*/
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);
spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
  函数说明:在保证原子操作的前提下,先测试count是否大于0,如果是说明可以获得信号量,这种情况下需要先将count--,以确保别的进程能否获得该信号量,然后函数返回,其调用者开始进入临界区。如果没有获得信号量,当前进程利用struct semaphore 中wait_list加入等待队列,开始睡眠。
  对于需要休眠的情况,在__down_interruptible()函数中,会构造一个struct semaphore_waiter类型的变量struct semaphore_waiter定义如下:
  struct semaphore_waiter
  {
  struct list_head list;
  struct task_struct *task;
  int up;
  };
  将当前进程赋给task,并利用其list成员将该变量的节点加入到以sem中的wait_list为头部的一个列表中,假设有多个进程在sem上调用down_interruptible,则sem的wait_list上形成的队列如下图:
  (注:将一个进程阻塞,一般的经过是先把进程放到等待队列中,接着改变进程的状态,比如设为TASK_INTERRUPTIBLE,然后调用调度函数schedule(),后者将会把当前进程从cpu的运行队列中摘下)
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号