目录
一.进程的状态
1.schedule_timeout
2.wake_up
3.task_struct current
二.内核与用户空间信号传递 fasync
一.进程的状态
1.Schedule_timeout
当我们调用schedule_timeout时,有两种情况能打断该定时器,一种为超时,一种为有信号打断。
在该函数的申明中提到
* %TASK_INTERRUPTIBLE - the routine may return early if a signal is
* delivered to the current task. In this case the remaining time
* in jiffies will be returned, or 0 if the timer expired in time
*
* The current task state is guaranteed to be TASK_RUNNING when this
* routine returns.
配合schedule_timeout,使用__set_currnet_state.对于支持信号打断的schedule_timeout,调用__set_currnet_state设置task state 为TASK_INTERRUPIBLE,如下,
__set_current_state(TASK_INTERRUPTIBLE);
Timeout=schedule_timeout(timeout);
__set_current_state(TASK_RUNNING);
调用schedule_timeout如果超时之后会自动设置task_state为TASK_RUNNING,在调用schedule_timeout之后,进程会进入休眠状态,退出进程调度,只有在有关于此进程的事件到来时会被唤醒(各种信号),重新加入进程调度中。
用过msleep都知道所在的task会sleep 相应ms,而实际上其原理就与schedule_timeout和进程状态有关。
当设置current_state为TASK_UNINTERRUPTIBLE时,只能等待超时,signal无法打断
这就是msleep的原理,msleep调用的函数为schedule_timeout_uninterruptible() 如下,
signed long __sched schedule_timeout_uninterruptible(signed long timeout)
{
__set_current_state(TASK_UNINTERRUPTIBLE);
return schedule_timeout(timeout);
}
2.Wake_up
schedule_timeout会被信号唤醒,那么具体是如何操作的?
以Linux kernel中的n_tty.c中的n_tty_read函数为例,
Wait_queue_head_t read_wait
Init_waitqueue_head(&tty->read_wait)
DECLARE_WAITQUEUE(wait,current); //初始化一个wait_queue_t wait ,task 为 current
add_wait_queue(&tty->read_wait,&wait);//将wait添加到read_wait队列中去
if(waitqueue_active(&tty->read_wait)) //判断read_wait是否为空
wake_up_interruptible(&tty->read_wait) // 唤醒等待队列
这里唤醒的是自己定义的read_wait队列,在wake_up_interruptible中,会将该进程重新加入run_queue中进行调度(实际调用的是try_to_wake_up函数),这样schedule_timeout被打断。
以上就是schedule_timeout(schedule())和wake_up的应用,实际上很多机制如wait_event_interrupt,wake_event_interrupt,wait_for_completion,complete等机制都是在这些基础上封装而实现的机制。
3.Task_struct Current
Current是一个很重要的task_struct,代码部分通过thread_info->task得到,而thread_info通过current_thread_info()得到,这是在2.6之后的一个设计,current_thread_info()代码如下,
static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}
THREAD_SIZE 8192 or 4096
因为每一个进程都一个自己的堆栈(4K or 8K),而SP寄存器只有一个,当进行调度时,会将一个进程堆栈的SP 转换到另一个进程的SP,而SP寄存器永远为当前运行的进程的SP值。
而每一个进程堆栈的低地址(12位或者13位)就存放了该进程的thread_info起始的信息。
这是内核进程的堆栈空间,空间大小固定,必须物理地址连续,溢出为固定堆栈大小溢出。而对应用户空间进程而言,它的堆栈空间可大可小,并且物理地址可以不连续(由MMU管控),出现溢出问题的时候有可能用户空间进程本身没有问题,而溢出部分将用户空间的其他进程的堆栈进行了修改导致这些进程运行出现错误。
二. fasync
使用fasync实现内核信号到用户空间的信号传递(比uevent简单)
内核部分:(以tty为例)
Struct fasync_struct *fasync;
在tty_fasync(int fd,struct file *filp,int on)中
Fasync_helper(fd,filp,on,&tty->fasync);//init fasync
当需要发送信号给用户空间时,调用
Kill_fasync(&tty->fasync,SIGIO,POLL_IN);
Kill_fasync(&tty->fasync,SIGIO,POLL_OUT);
用户空间:
利用signal或者signaction设置SIGIO信号处理的函数
Fcntl F_SETOWN设置当前进程为设备文件owner
Fcntl F_SETFL指令设置FASYNC指令