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-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
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-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了