增加网站访问量,自己怎样建设网站首页,上海有哪几家做新房的网站,营销手机系统安装第三十八章 linux-并发解决方法二#xff08;信号量#xff09; 文章目录第三十八章 linux-并发解决方法二#xff08;信号量#xff09;信号量的定义DOWN操作UP操作相对于自旋锁#xff0c;信号量的最大特点是允许调用它的线程进入睡眠状态这意味着试图获得某一信号的进程…第三十八章 linux-并发解决方法二信号量 文章目录第三十八章 linux-并发解决方法二信号量信号量的定义DOWN操作UP操作相对于自旋锁信号量的最大特点是允许调用它的线程进入睡眠状态·这意味着试图获得某一信号的进程会导致对处理器拥有权的丧失也即出现进程的切换。
信号量的定义
struct semaphore {
raw_spinlock_t lock;//lock是个自旋锁变量用于实现对信号量的另一个成员count的原子操作。
unsigned int count;//无符号整型变量count用于表示通过该信号量允许进入临界区的执行路径的个数。
struct list_head wait_list;//wait_list用于管理所有在该信号量上睡眠的进程无法获得该信号量的进程将进入睡眠状态。
};如果驱动程序中定义了一个struct semaphore型的信号量变量需要注意的是不要直接对该变量的成员进行赋值而应该使用sema_init函数来初始化该信号量。sema_init函数定义如下
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);
}初始化主要通过__SEMAPHORE_INITIALIZER宏完成
#define DEFINE_SEMAPHORE(name) \struct semaphore name __SEMAPHORE_INITIALIZER(name, 1)#define __SEMAPHORE_INITIALIZER(name, n) \
{ \.lock __RAW_SPIN_LOCK_UNLOCKED((name).lock), \.count n, \.wait_list LIST_HEAD_INIT((name).wait_list), \
}所以 sema_init(struct semaphore *sem, int val)调用会把信号量sem的lock值设定为解锁状态count值设定为函数的调用参数val,同时初始化wait_list链表头。
DOWN操作
信号量上的主要操作是DOWN和UP在Linux内核中对信号量的DOWN操作有
void down(struct semaphore *sem) 获取信号量不建议使用此函数因为是 UNINTERRUPTABLE 的睡眠。int down_interruptible(struct semaphore *sem) 可被中断地获取信号量如果睡眠被信号中断返回错误-EINTR。int down_killable (struct semaphore *sem) 可被杀死地获取信号量。如果睡眠被致命信号中断返回错误-EINTR。int down_trylock(struct semaphore *sem) 尝试原子地获取信号量如果成功获取返回0不能获取返回1。int down_timeout(struct semaphore *sem, long jiffies) 在指定的时间jiffies内获取信号量若超时未获取返回错误-ETIME。
上面这些函数中驱动程序使用最频繁的是down_interruptible函数本节将重点讨论该函数之后再对其他入操作的功能作一概述性的描述。
int down_interruptible(struct semaphore *sem)
{unsigned long flags;int result 0;raw_spin_lock_irqsave(sem-lock, flags);//保证对sem-count操作的原子性防止多个进程对sem-count同时操作//可能引起混乱if (likely(sem-count 0))sem-count--;elseresult __down_interruptible(sem);raw_spin_unlock_irqrestore(sem-lock, flags);return result;
}函数首先通过spin_lock_irqsave的调用来保证对sem-count操作的原子性防止多个进程对sem-count同时操作 可能引起混乱。如果代码成功进入临界区则判断sem-count是否大于0如果count大于0表明当前进程可以获得信号量就将count值减I,然后退出如果count不大于0表明当前进程无法获得该信号量此时调用down_interruptible由后者完成一个进程无法获得信号量时的操作在内部调用__down_common(struct semaphore *semlong statelongt timeout)调用时的参数stateTASK_INTERRUPTIBLEtimeoutLONG_MAX所以当一个进程无法获得信号量时最终调用的函数为__down_common: __down_interruptible-__down_common
static inline int __sched __down_common(struct semaphore *sem, long state,long timeout)
{struct task_struct *task current;/*通过对一个struct semaphore_waiter变量waiter的使用把当前进程放到信号量sem的成员变量wait所管理的队列中*/struct semaphore_waiter waiter;list_add_tail(waiter.list, sem-wait_list);waiter.task task;waiter.up false;for (;;) {if (signal_pending_state(state, task))//把当前进程的状态设置为TASK_INTERRUPTIBLEgoto interrupted;if (unlikely(timeout 0))goto timed_out;__set_task_state(task, state);raw_spin_unlock_irq(sem-lock);timeout schedule_timeout(timeout);//使当前进程进入睡眠状态,函数将停留在schedule_timeout调用上直到再次被调度执行。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;
}函数的功能是首先通过对一个struct semaphore_waiter变量waiter的使用把当前进程放到信号量sem的成员变量wait所管理的队列中接着在一个for循环中把当前进程的状态设置为TASK_INTERRUPTIBLE再调用schedule_timeout使当前进程进入睡眠状态函数将停留在schedule_timeout调用上直到再次被调度执行。当该进程再一次被调度执行时schedule_timeout开始返回接下来根据进程被再次调度的原因进行处理如果waiter.up不为0说明进程在信号量sem的wati_list队列中被该信号量的UP操作所唤醒进程可以获得信号量返回0。如果进程是因为被用户空间发送的信号所中断或者是超时引起的唤醒则返回相应的错误代码。因此对面down_interruptible的调用总是应该坚持检查其返回值以确定函数是已经获得了信号量还是因为操作被中断因而需要特别处理通常驱动程序对返回的非0值要做的工作是返回-ERESTARTSYS比如下面的代码段
//定义一个信号量
struct semaphore demosem;
sema_init(demosem,2);
if(down_interruptiable(demosem));return -ERESTARTSYS;然而对down_interruptible的调用最常见的可能还是返回0表明调用者获得了信号量。为了让讨论具体化下面以一个例子来说明假设一个信号量sem的count2说明允许有两个进程进入临界区假设有进程A、B、C、D和E先后调用down_interruptible来获得信号量那么进程A和B将得到信号量进入临界区C、D和E将睡眠在sem的wait_list中此时的情形如图2所示 在接下来的UP操作中还会用到这里的例子来讨论进程A和B结束临界区中的操作返回时执行UP操作对wait_list中进程C、D和E的影响。
在讨论完驱动程序最常使用的down_interruptible函数之后再回过头来看看其他几种DOWN操作
void down(struct semaphore *sem); 与down_interruptible相比down函数是不可中断的这意味着调用它的进程如果无法获得信号量将一直处于睡眠状态直到有别的进程释放了该信号量。从用户空间的角度如果应用程序阻塞在了驱动程序的down函数中将无法通过一些强制措施比如按CtrlD组合键等来结束该进程。因此除非必要否则驱动程序中应该避免使用down函数。int down_killable(struct semaphore *sem); 睡眠的进程可以因收到一些致命性信号fatal signal)被唤醒而导致获取信号量的操作被中断在驱动程序中极少使用。int down_trylock(struct semaphore *sem); 进程试图获得信号量但若无法获得信号量则直接返回1而不进入睡眠状态返回0意味着函数的调用者己经获得了信号量。int down_timeout(struct semaphore *sem, long jiffies); 函数在无法获得信号量的情况下将进入睡眠状态但是处于这种睡眠状态有时间限制如果在jiffies指明的时间到期时函数依然无法获得信号量则将返回一错误码-ETIME在到期前进程的睡眠状态为TASK_UNINTERRUPTIBLE。成功获得信号量的函数返回0。
UP操作
void up(struct semaphore *sem)
{unsigned long flags;raw_spin_lock_irqsave(sem-lock, flags);if (likely(list_empty(sem-wait_list)))sem-count;else__up(sem);raw_spin_unlock_irqrestore(sem-lock, flags);
}如果信号量的wait_list队列为空则表明没有其他进程正在等待该信号量那么只要把sem的count加1即可。如果wait_list队列不为空则说明有其他进程正睡眠在wait_list上等待该信号量此时调用__up(sem来唤醒进程
static noinline void __sched __up(struct semaphore *sem)
{struct semaphore_waiter *waiter list_first_entry(sem-wait_list,struct semaphore_waiter, list);list_del(waiter-list);waiter-up true;wake_up_process(waiter-task);
}下面在图2的基础上讨论此处的操作。__up函数首先用list_first_entry取得sem-wait_list链表上的第一个waiter节点C然后将其从sem-wait_list链表中删除waiter-up1最后调用wake_up_process来唤醒waiter C上的进程C。这样进程C将从之前down_interruptible。
调用中的timeoutschedule_timeout(timeout)处醒来waiter-up1down_interruptible返回0进程c获得信号量进程D和E继续等待直到有进程释放信号量或者被用户空间中断掉。即使不是信号量的拥有者也可以调用up函数来释放一个信号量这点与下节介绍的mutex是不同的。
在Linux系统中信号量的一个常见的用途是实现互斥机制这种情况下信号量的count值为1也就是任意时刻只允许一个进程进入临界区。为此Linux内核源码提供了一个宏DECLARE_MUTEX专门用于这种用途的信号量定义和初始化
static noinline void __sched __up(struct semaphore *sem)
{struct semaphore_waiter *waiter list_first_entry(sem-wait_list,struct semaphore_waiter, list);list_del(waiter-list);waiter-up true;wake_up_process(waiter-task);
}该宏定义了一个count1的信号量变量name并初始化了相关成员。所以接下来就可以使用信号量的DOWN和UP操作来实现互斥比如下面的这个用DECLARE_MUTEX定义的信号量来实现互斥的代码段
//先用DECLARE_MUTEX定义一个全局的信号量demo_sem
DECLARE_MUTEX(demo_sem);//函数demo_write里面使用demo_sem作互斥用
int demo_write{//打算进入临界区调用down_interruptible获得信号量if(down_interruptible(down_sem)){return -ERESTARTSYS }//成功获取信号量进入临界区.....//离开临界区调用up释放信号量up(demo_sem)
}