Chinaunix首页 | 论坛 | 博客
  • 博客访问: 957885
  • 博文数量: 173
  • 博客积分: 3436
  • 博客等级: 中校
  • 技术积分: 1886
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-07 09:29
文章分类

全部博文(173)

文章存档

2016年(6)

2015年(10)

2014年(14)

2013年(8)

2012年(36)

2011年(63)

2010年(19)

2009年(17)

分类: LINUX

2011-05-15 17:31:43

down_interruptible() 被中断的疑问
对down_interruptible能被信号中断不大理解。如果获得信号,程序可以继续运行,否则休眠。
那么什么叫被信号中断呢?
调用down_interruptible后,会进入睡眠。这个睡眠有可能被信号中断?什么情况下会出现这样的情况?
此处的信号指POSIX标准定义的信号。比如说ctrl+C杀死当前进程。也就是说,这个等待可以被外部的用户通过命令取消。
int down_interruptible(struct semaphore *sem)
如果得不到信号量,此时没有信号打断,那么进入睡眠。
主要用来进程间的互斥同步
“信号” 与 “信号量” 是两个不同的概念。
 

你如果看函数的注释,也可以知道大概的
  66/**
  67 * down_interruptible - acquire the semaphore unless interrupted
  68 * @sem: the semaphore to be acquired
  69 *
  70 * Attempts to acquire the semaphore. If no more tasks are allowed to
  71 * acquire the semaphore, calling this function will put the task to sleep.
  72 * If the sleep is interrupted by a signal, this function will return -EINTR.
  73 * If the semaphore is successfully acquired, this function returns 0.
  74 */
  75int down_interruptible(struct semaphore *sem)
 
wait_event_interruptible为什么需要加个dev->sleep_flag 判断标志
这个含义是什么?
/**
 * wait_event_interruptible - sleep until a condition gets true
 * @wq: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_INTERRUPTIBLE) until the
 * @condition evaluates to true or a signal is received.
 * The @condition is checked each time the waitqueue @wq is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 *
 * The function will return -ERESTARTSYS if it was interrupted by a
 * signal and 0 if @condition evaluated to true.
 */
注意:wq和condition的意义。
其中
wait_event_interruptible为一个宏定义,因此在调用时需要提供“condition”
wait_event_interruptible(key1_interrupt, (sleep_flag == 1));如果将condition固定为0或者1将导致wait_event没有任何意义了。
 
 
2011年4月底调试2440键盘驱动时,没有在这个之前加个sleep,造成来中断后,经常无法响应,之后就导致再也不响应中断。对此甚是不理解。
       wait_event_interruptible(ioctl_fpga_ready, (dev->sleep_flag == 1));
        dev->sleep_flag = 0;
 


/**
 * wait_event_interruptible - sleep until a condition gets true
 * @wq: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_INTERRUPTIBLE) until the
 * @condition evaluates to true or a signal is received.
 * The @condition is checked each time the waitqueue @wq is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 *
 * The function will return -ERESTARTSYS if it was interrupted by a
 * signal and 0 if @condition evaluated to true.

 */

#define wait_event_interruptible(wq, condition)                \
({                                    \
    int __ret = 0;                            \
    if (!(condition))                        \
        __wait_event_interruptible(wq, condition, __ret);    \
    __ret;                                \
})

#define __wait_event_interruptible(wq, condition, ret)   \
do {         \
 DEFINE_WAIT(__wait);      \
         \
 for (;;) {       \
  prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
  if (condition)      \
   break;      \
  if (!signal_pending(current)) {    \
   schedule();     \
   continue;     \
  }       \
  ret = -ERESTARTSYS;     \
  break;       \
 }        \
 finish_wait(&wq, &__wait);     \
} while (0)

 
signal_pending(current) 是不是就是用来检查有没有外部信号的?
比如 ctrl+C外部的kill命令,杀掉当前进程。
linux中,内核驱动也位于当前用户空间的进程空间中,如果得到current 的PID的话,(这点和windows一样的)。驱动的不同函数,可能位于不一样的进程空间中。
 
 
自旋锁主要针对SMP,在单CPU中它仅仅设置内核抢占机制的是否启用的开关。 在内核不支持抢占的系统中,自旋锁退为空操作。
 
临界区
 
 
 

/*
 * 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 task_struct *task = current;
    struct semaphore_waiter waiter;

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

    for (;;) {
        if (signal_pending_state(state, task))
            goto interrupted;
        if (timeout <= 0)
            goto timed_out;
        __set_task_state(task, state);
        spin_unlock_irq(&sem->lock);//why unlock first
        timeout = schedule_timeout(timeout);
        spin_lock_irq(&sem->lock);//masc do not understand
        if (waiter.up)
            return 0;
    }

 timed_out:
    list_del(&waiter.list);
    return -ETIME;

 interrupted:
    list_del(&waiter.list);
    return -EINTR;
}


同步通常是为了达到多线程协同的目的而设计的一种机制,通常包含异步信号机制和互斥机制作为其实现的底层。在Linux 2.4内核中也有相应的技术实现,包括信号量、自旋锁、原子操作和等待队列,其中原子操作和等待队列又是实现信号量的底层。

wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。我们从它的使用范例着手,看看等待队列是如何实现异步信号功能的。

在核心运行过程中,经常会因为某些条件不满足而需要挂起当前线程,直至条件满足了才继续执行。在2.4内核中提供了一组新接口来实现这样的功能,下面的代码节选自kernel/printk.c:

    unsigned long log_size;
1:  DECLARE_WAIT_QUEUE_HEAD(log_wait);...
4:  spinlock_t console_lock = SPIN_LOCK_UNLOCKED;...
    int do_syslog(int type,char *buf,int len){
        ...
2:      error=wait_event_interruptible(log_wait,log_size);
        if(error)
             goto out;
        ...
5:      spin_lock_irq(&console_lock);
	...
        log_size--;
        ...
6:	spin_unlock_irq(&console_lock);
        ...
    }
    asmlinkage int printk(const char *fmt,...){
	...
7:	spin_lock_irqsave(&console_lock,flags);
        ...
        log_size++;...
8:	spin_unlock_irqrestore(&console_lock,flags);
3:      wake_up_interruptible(&log_wait);
        ...
    }
    

这段代码实现了printk调用和syslog之间的同步,syslog需要等待printk送数据到缓冲区,因此,在2:处等待log_size非0;而printk一边传送数据,一边增加log_size的值,完成后唤醒在log_wait上等待的所有线程(这个线程不是用户空间的线程概念,而是核内的一个执行序列)。执行了3:的wake_up_interruptible()后,2:处的wait_event_interruptible()返回0,从而进入syslog的实际动作。

1:是定义log_wait全局变量的宏调用。

在实际操作log_size全局变量的时候,还使用了spin_lock自旋锁来实现互斥,关于自旋锁,这里暂不作解释,但从这段代码中已经可以清楚的知道它的使用方法了。

所有wait queue使用上的技巧体现在wait_event_interruptible()的实现上,代码位于include/linux/sched.h中,前置数字表示行号:

779 #define __wait_event_interruptible(wq, condition, ret)                  \
780 do {                                                                    \
781         wait_queue_t __wait;                                            \
782         init_waitqueue_entry(&__wait, current);                         \
783                                                                         \
784         add_wait_queue(&wq, &__wait);                                   \
785         for (;;) {                                                      \
786                 set_current_state(TASK_INTERRUPTIBLE);                  \
787                 if (condition)                                          \
788                         break;                                          \
789                 if (!signal_pending(current)) {                         \
790                         schedule();                                     \
791                         continue;                                       \
792                 }                                                       \
793                 ret = -ERESTARTSYS;                                     \
794                 break;                                                  \
795         }                                                               \
796         current->state = TASK_RUNNING;                                  \
797         remove_wait_queue(&wq, &__wait);                                \
798 } while (0)
799         
800 #define wait_event_interruptible(wq, condition)                         \
801 ({                                                                      \
802         int __ret = 0;                                                  \
803         if (!(condition))                                               \
804                 __wait_event_interruptible(wq, condition, __ret);       \
805         __ret;                                                          \
806 })
 

wait_event_interruptible()中首先判断condition是不是已经满足,如果是则直接返回0,否则调用__wait_event_interruptible(),并用__ret来存放返回值。__wait_event_interruptible()首先定义并初始化一个wait_queue_t变量__wait,其中数据为当前进程结构current(struct task_struct),并把__wait入队。在无限循环中,__wait_event_interruptible()将本进程置为可中断的挂起状态,反复检查condition是否成立,如果成立则退出,如果不成立则继续休眠;条件满足后,即把本进程运行状态置为运行态,并将__wait从等待队列中清除掉,从而进程能够调度运行。如果进程当前有异步信号(POSIX的),则返回-ERESTARTSYS。

挂起的进程并不会自动转入运行的,因此,还需要一个唤醒动作,这个动作由wake_up_interruptible()完成,它将遍历作为参数传入的log_wait等待队列,将其中所有的元素(通常都是task_struct)置为运行态,从而可被调度到,执行__wait_event_interruptible()中的代码。

DECLARE_WAIT_QUEUE_HEAD(log_wait)经过宏展开后就是定义了一个log_wait等待队列头变量:

struct __wait_queue_head log_wait = {
	lock:	SPIN_LOCK_UNLOCKED,
	task_list:      { &log_wait.task_list, &log_wait.task_list }
}
 

其中task_list是struct list_head变量,包括两个list_head指针,一个next、一个prev,这里把它们初始化为自身,属于队列实现上的技巧,其细节可以参阅关于内核list数据结构的讨论,add_wait_queue()和remove_wait_queue()就等同于list_add()和list_del()。

wait_queue_t结构在include/linux/wait.h中定义,关键元素即为一个struct task_struct变量表征当前进程。

除了wait_event_interruptible()/wake_up_interruptible()以外,与此相对应的还有wait_event()和wake_up()接口,interruptible是更安全、更常用的选择,因为可中断的等待可以接收信号,从而挂起的进程允许被外界kill。

wait_event*()接口是2.4内核引入并推荐使用的,在此之前,最常用的等待操作是interruptible_sleep_on(wait_queue_head_t *wq),当然,与此配套的还有不可中断版本sleep_on(),另外,还有带有超时控制的*sleep_on_timeout()。sleep_on系列函数的语义比wait_event简单,没有条件判断功能,其余动作与wait_event完全相同,也就是说,我们可以用interruptible_sleep_on()来实现wait_event_interruptible()(仅作示意〉:

do{
	interruptible_sleep_on(&log_wait);
        if(condition)
		break;
}while(1);
 

相对而言,这种操作序列有反复的入队、出队动作,更加耗时,而很大一部分等待操作的确是需要判断一个条件是否满足的,因此2.4才推荐使用wait_event接口。

在wake_up系列接口中,还有一类wake_up_sync()和wake_up_interruptible_sync()接口,保证调度在wake_up返回之后进行。

阅读(4600) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~