程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux同步机制-信号量

一、信号量(semaphore)

1.1 什么是信号量

信号量本质上是一个计数器,它用来记录对某个资源的存取状态。一般来说,为了获取共享资源,进程需要执行下列操作:

  • 测试控制该资源的信号量;
  • 如果信号量的值为正,则允许操作该资源,并且信号量值减1;
  • 如果信号量为0,则资源目前不可用,进程进入休眠状态,直至信号量值大于0,进程被唤醒,转入上一步;
  • 当进程不在使用一个信号量控制的资源时,信号量的值加1;

1.2 信号量的工作原理

针对信号量只能进行两种操作,即PV操作,PV操作由P操作原语和V操作原子组成(原子是不可中断的过程),对信号量进行操作,具体定义如下:

  • P(s):如果s的值大于零,就给它减1;如果它的值为零,则将该进程设置为等待状态,排入等待队列;
  • V(s):如果有其它进程因等待s而被挂起,就让它恢复运行,如果没有进程因等待s而挂起,就给它加1;

1.3 信号量的使用

定义信号量:

struct semaphore sema;

DEFINE_SEAMPHORE宏用于定义一个信号量,并设置信号量的值为1:

DEFINE_SEAMPHORE(name)

sema_init 函数用于初始化信号量,并设置信号量sem的值为val:

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

down函数用于获得信号量sem,它会导致进程睡眠,不能在中断上下文中使用,不然会导致中断处理程序休眠(down函数目前已不建议使用):

int down(struct semaphore * sem);

down_interruptible函数功能与down类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠后,进程状态被设置为TASK_INTERRUPTIBLE,该类型的睡眠是可以被信号打断的。

如果返回0,表示获得信号量;如果被信号打断,返回EINTR。

int down_interruptible(struct semaphore * sem);

down_trylock函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回正数。它不会导致调用者睡眠,可以在中断上下文使用:

int down_trylock(struct semaphore * sem);

up函数释放信号量sem,唤醒等待者。

void up(struct semaphore * sem);

二、信号量的实现源码

2.1 struct semaphore结构体

维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件include/linux/semaphore.h:中看到内核用来维护信号量状态的各个结构的定义。信号量结构体定义:

/* Please don't access any members of this structure directly */
struct semaphore {
        raw_spinlock_t          lock;
        unsigned int            count;
        struct list_head        wait_list;
};

lock是一个自旋锁结构,其实现原理在后面的章节介绍。count用来保存可用资源的数量。

list_head是一个双向链表,使用该等待列表保存因获取不到信号量而进行睡眠的进程:

struct list_head{
    struct list_head  *next,*prev;
}

再来看一下DEFINE_SEMAPHORE宏定义初始化信号量成员lock、count、wait_list:

复制代码
#define __SEMAPHORE_INITIALIZER(name, n)                                \
{                                                                       \
        .lock           = __RAW_SPIN_LOCK_UNLOCKED((name).lock),        \
        .count          = n,                                            \
        .wait_list      = LIST_HEAD_INIT((name).wait_list),             \
}

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

static inline void sema_init(struct semaphore *sem, int val)
{
        static struct lock_class_key __key;
        *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
        lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
复制代码

2.2 down函数

再来看看一下down函数,定义在kernel/locking/semaphore.c:文件中,该函数是用来获取信号量的,也是一个原子操作:

复制代码
/**
 * down - acquire the semaphore
 * @sem: the semaphore to be acquired
 *
 * Acquires the semaphore.  If no more tasks are allowed to acquire the
 * semaphore, calling this function will put the task to sleep until the
 * semaphore is released.
 *
 * Use of this function is deprecated, please use down_interruptible() or
 * down_killable() instead.
 */
void down(struct semaphore *sem)
{
        unsigned long flags;

        raw_spin_lock_irqsave(&sem->lock, flags);
        if (likely(sem->count > 0))
                sem->count--;
        else
                __down(sem);
        raw_spin_unlock_irqrestore(&sem->lock, flags);
}
复制代码

这里实际上内部调用raw_spin_lock_irqsave、raw_spin_unlock_irqrestore这个两个函数来实现进程同步的,其实现原理是自旋锁。自旋锁可以保证在这两个函数中的代码可以独占的执行,具体实现原理这块内容我们会在自旋锁里面单独介绍。

如果获取不到信号量,__down函数调用 __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);

复制代码
/*
 * Because this function is inlined, the 'state' parameter will be
 * constant, and thus optimised away by the compiler.  Likewise the
 * 'timeout' parameter for the cases without timeouts.
 */
static inline int __sched __down_common(struct semaphore *sem, long state,
                                                                long timeout)
{
        struct semaphore_waiter waiter;

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

        for (;;) {
                if (signal_pending_state(state, current))
                        goto interrupted;
                if (unlikely(timeout <= 0))
                        goto timed_out;
                __set_current_state(state);
                raw_spin_unlock_irq(&sem->lock);
                timeout = schedule_timeout(timeout); // CPU调度、无限睡眠,导致进程切换,开销较大
                raw_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;
}
复制代码

在__down_common函数中会做以下事情:

  • 将当前进程加入信号量等待列表wait_list中;
  • 将当前进程设置为睡眠状态,并且进程状态被设置为TASK_UNINTERRUPTIBLE;
  • 执行CPU调度,当其它进程调用up函数释放信号量,才会唤醒第一个被等待的进程;

2.3 up函数

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(562)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示