Linux内核中的信号量

本文详细探讨了Linux内核中的信号量,解释了为何使用信号量以实现进程间对共享资源的访问控制。信号量通过P和V操作实现资源的互斥和同步,确保了多个进程的安全访问。文章深入讲解了内核信号量的结构、函数以及如何在Linux中使用信号量,包括内核信号量、POSIX信号量和SYSTEM V信号量的对比,还通过生产者消费者问题展示了信号量的实际应用。
摘要由CSDN通过智能技术生成

        Linux内核中的信号量

        信号量的本质是⼀种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。当请求⼀个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可⽤。大于0,资源可以请求,等于0,⽆资源可⽤,进程会进入睡眠状态直⾄资源可⽤。当进程不再使⽤⼀个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原⼦操作,这是由于信号量主要的作⽤是维护资源的互斥或多进程的同步访问。⽽在信号量的创建及初始化上,不能保证操作均为原⼦性。

        A:为什么要使⽤信号量?

        为了防止出现因多个程序同时访问⼀个共享资源⽽引发的一系列问题,我们需要一种方法,它可以通过生成并使⽤令牌来授权,在任⼀时刻只能有⼀个执⾏线程访问代码的临界区域。临界区域是指执⾏数据更新的代码需要独占式地执行。⽽信号量就可以提供这样的⼀种访问机制,让⼀个临界区同⼀时间只有⼀个线程在访问它, 也就是说信号量是⽤来调协进程对共享资源的访问的。其中共享内存的使⽤用就要⽤到信号量。

        B:信号量的⼯作原理:

        由于信号量只能进⾏两种操作等待和发送信号,即P(sv)和V(sv),他们的⾏为是这样的:

        P(sv):如果sv的值⼤于零,就给它减1;如果它的值为零,就挂起该进程的执行。
        V(sv):如果有其他进程因等待sv⽽被挂起,就让它恢复运⾏,如果没有进程因等待sv⽽挂起,就给它加1.举个例⼦子,就是两个进程共享信号量sv,⼀旦其中⼀个进程执⾏行了P(sv)操作,它将得到信号量,并可以进⼊临界区,使sv减1。⽽第⼆个进程将被阻⽌进⼊临界区,因为当它试图执⾏P(sv)时,sv为0,它会被挂起以等待第⼀个进程离开临界区域并执⾏行V(sv)释放信号量,这时第⼆个进程就可以恢复执⾏。

          C:Linux的信号量机制

          Linux提供了⼀一组精⼼设计的信号量接⼜⼝来对信号量进⾏操作,它们不只是针对⼆进制信号量,下⾯面将会对这些函数进⾏行介绍,但请注意,这些函数都是⽤用来对成组的信号量值进⾏行操作的。它们声明在头⽂文件sys/sem.h中。
       【信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号量也可⽤用于线程间,互斥锁和条件变量也可⽤用于进程间。我们应该使⽤用适合具体应⽤用的那组原语。】

一.什么是信号量

       信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

二.信号量的分类

        在学习信号量之前,我们必须先知道——Linux提供两种信号量:

       (1) 内核信号量,由内核控制路径使用。

       (2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEMV信号量。POSIX信号量又分为有名信号量和无名信号量。

有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷失了方向。

三.内核信号量

       1.内核信号量的构成

             内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。

内核信号量是struct semaphore类型的对象,它在<asm/semaphore.h>中定义:
struct semaphore 
{
   atomic_t count;
   int sleepers;
   wait_queue_head_t wait;
};

        count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
        wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。

        sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。

        2.内核信号量中的等待队列(删除,没有联系)

             上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作。当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足

时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工作队列中等待CPU资源,在适当的时机被调度。内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用户干涉。由用户真正使用的等待队列我们将在另外的篇章进行详解。

        3.内核信号量的相关函数

      (1)初始化:
void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //将sem的值置为1,表示资源空闲
void init_MUTEX_LOCKED (struct semaphore *sem); //将sem的值置为0,表示资源忙

      (2)申请内核信号量所保护的资源:
void down(struct semaphore * sem); // 可引起睡眠
int down_interruptible(struct semaphore * sem); // down_interruptible能被信号打断
int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠。无法锁定资源则
马上返回


      (3)释放内核信号量所保护的资源:
void up(struct semaphore * sem);


        4.内核信号量的使用例程

              在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
 //获得信号量
 if (down_interruptible(&sem))
 {
  return - ERESTARTSYS;
 }
 //将用户空间的数据复制到内核空间的global_var
 if (copy_from_user(&global_var, buf, sizeof(int)))
 {
  up(&sem);
  return - EFAULT;
 }
 //释放信号量
 up(&sem);
 return sizeof(int);
}

四.POSIX 信号量与SYSTEM V信号量的比较

       1. 对POSIX来说,信号量是个非负整数。常用于线程间同步。

          而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。

       2.POSIX信号量的引用头文件是“<semaphore.h>”,而SYSTEM V信号量的引用头文件是“<sys/sem.h>”。

       3.从使用的角度,System V信号量是复杂的,而Posix信号量是简单。比如,POSIX信号量的创建和初始化或PV操作就很非常方便。</

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值