进程间通信之信号量semaphore--linux内核剖析( 二 )


需要C/C++ Linux服务器架构师学习资料私信“资料”(资料包括C/C++ , Linux , golang技术 , Nginx , ZeroMQ , MySQL , Redis , fastdfs , MongoDB , ZK , 流媒体 , CDN , P2P , K8S , Docker , TCP/IP , 协程 , DPDK , ffmpeg等) , 免费分享
进程间通信之信号量semaphore--linux内核剖析文章插图
内核信号量的构成内核信号量类似于自旋锁 , 因为当锁关闭着时 , 它不允许内核控制路径继续进行 。 然而 , 当内核控制路径试图获取内核信号量锁保护的忙资源时 , 相应的进程就被挂起 。 只有在资源被释放时 , 进程才再次变为可运行 。只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量 。内核信号量是struct semaphore类型的对象 , 在内核源码中位于include\linux\semaphore.h文件
struct semaphore{atomic_t count;int sleepers;wait_queue_head_t wait;}
进程间通信之信号量semaphore--linux内核剖析文章插图
内核信号量中的等待队列上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作 。
当某任务由于没有某种条件没有得到满足时 , 它就被挂到等待队列中睡眠 。 当条件得到满足时 , 该任务就被移出等待队列 , 此时并不意味着该任务就被马上执行 , 因为它又被移进工作队列中等待CPU资源 , 在适当的时机被调度 。
内核信号量是在内部使用等待队列的 , 也就是说该等待队列对用户是隐藏的 , 无须用户干涉 。 由用户真正使用的等待队列我们将在另外的篇章进行详解 。
内核信号量的相关函数
进程间通信之信号量semaphore--linux内核剖析文章插图
初始化#define __SEMAPHORE_INITIALIZER(name, n)\{\.lock= __SPIN_LOCK_UNLOCKED((name).lock),\.count= n,\.wait_list= LIST_HEAD_INIT((name).wait_list),\}【进程间通信之信号量semaphore--linux内核剖析】该宏声明一个信号量name是直接将结构体中count值设置成n , 此时信号量可用于实现进程间的互斥量 。
#define DECLARE_MUTEX(name)\struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)该宏声明一个互斥锁name , 但把它的初始值设置为1
void sema_init (struct semaphore *sem, int val);该函用于数初始化设置信号量的初值 , 它设置信号量sem的值为val 。
#define init_MUTEX(sem)sema_init(sem, 1)该函数用于初始化一个互斥锁 , 即它把信号量sem的值设置为1 。
#define init_MUTEX_LOCKED(sem)sema_init(sem, 0)该函数也用于初始化一个互斥锁 , 但它把信号量sem的值设置为0 , 即一开始就处在已锁状态 。
注意:对于信号量的初始化函数Linux最新版本存在变化 , 如init_MUTEX和init_MUTEX_LOCKED等初始化函数目前新的内核中已经没有或者更换了了名字等
因此建议以后在编程中遇到需要使用信号量的时候尽量采用sema_init(struct semaphore *sem, int val)函数 , 因为这个函数就目前为止从未发生变化 。
获取信号量–申请内核信号量所保护的资源void down(struct semaphore * sem);该函数用于获得信号量sem , 它会导致睡眠 , 因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数 。 该函数将把sem的值减1 , 如果信号量sem的值非负 , 就直接返回 , 否则调用者将被挂起 , 直到别的任务释放该信号量才能继续运行 。
int down_interruptible(struct semaphore * sem);该函数功能与down类似 , 不同之处为 , down不会被信号(signal)打断 , 但down_interruptible能被信号(比如Ctrl+C)打断 , 因此该函数有返回值来区分是正常返回还是被信号中断 , 如果返回0 , 表示获得信号量正常返回 , 如果被信号打断 , 返回-EINTR
int down_trylock(struct semaphore * sem);该函数试着获得信号量sem , 如果能够立刻获得 , 它就获得该信号量并返回0 , 否则 , 表示不能获得信号量sem , 返回值为非0值 。 因此 , 它不会导致调用者睡眠 , 可以在中断上下文使用 。
释放内核信号量所保护的资源void up(struct semaphore * sem);该函数释放信号量sem , 即把sem的值加1 , 如果sem的值为非正数 , 表明有任务等待该信号量 , 因此唤醒这些等待者 。
内核信号量的使用例程在驱动程序中 , 当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的 共享资源) , 可能会引发“竞态“ , 因此我们必须对共享资源进行并发控制 。 Linux内核中 解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用) 。