文章目录
- 什么是异步通知
- 异步通知和异步IO的区别
- 信号含义
- 应用层使用信号
- 驱动如何实现异步信号
- 驱动实例
什么是异步通知
异步通知在Linux的实现中是通过信号,而信号是在软件层次上对中断机制的一种模拟。这种机制和中断非常类似,所以可以以中断的思想来理解这一过程,信号其实就相当于应用层的中断。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
异步通知和异步IO的区别
异步通知:当资源可获得时,由驱动程序向应用层发送一个信号,主动通知应用程序,再由应用程序发起访问。
异步IO:主动获取设备的资源信息,首先发起一个IO操作请求,资源可用时,应用层注册的回调函数会被主动调用。但是异步通知不能直接调用应用层注册的回调函数,而是由驱动程序向应用层发送一个信号。
信号含义
信号名 | 含义 | 默认操作 |
---|---|---|
SIGHUP | 该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。 | 终止 |
SIGINT | 该信号在用户键入INTR 字符(通常是Ctrl-C )时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。 | 终止 |
SIGQUIT | 该信号和SIGINT 类似,但由QUIT字符(通常是Ctrl-\ )来控制。 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。 | 终止 |
SIGFPE | 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。 | 终止 |
SIGKILL | 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。 | 终止 |
---|---|---|
SIGALRM | 该信号当一个定时器到时的时候发出。 | 终止 |
SIGSTOP | 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。 | 暂停进程 |
SIGTSTP | 该信号用于暂停交互进程,用户可键入SUSP 字符(通常是Ctrl-Z )发出这个信号。 | 暂停进程 |
SIGCHLD | 子进程改变状态时,父进程会收到这个信号 | 忽略 |
SIGABORT | 该信号用于结束进程 | 终止 |
当应用层接收到一个信号时,可以对信号执行忽略、捕捉和缺省三种操作:
忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL
及SIGSTOP
。
捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
缺省信号:执行Linux对该信号的默认操作
应用层使用信号
以下是应用层捕捉SIGIO
信号的简单示例:
void input_handler(int signum)
{
//如果驱动发送了SIGIO信号,在此处理
printf("recive from %d\n",signum);
}
int main()
{
int fd ,oflags;
fd=open("/dev/global",O_RDWR,S_IRUSR | S_IWUSR);
if(fd != -1)
{
//启动信号机制
signal(SIGIO,input_handler);//设置SIGIO信号的处理函数
fcntl(fd,F_SETOWN,getpid());//将进程ID赋值给filp->f_owner
oflags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,oflags | FASYNC);//驱动的fasync方法被调用
while(1)
{
sleep(1000);
}
}
}
- 当应用层
F_SETOWN
, 驱动什么都没做,内核只是将进程的ID赋值给filp->f_owner
; - 当应用层
F_SETFL
被执行来打开FASYNC
, 驱动的 fasync 方法被调用. - 当数据到达, 驱动向进程发出一个
SIGIO
信号
驱动如何实现异步信号
驱动的实现主要用到一个结构体和两个函数:
fasync_struct
结构体:
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
函数:
fasync_helper()
:用于处理FASYNC
标志变更
fasync_helper()
函数是用来初始化fasync_struct
结构体变量,并设置异步通知队列的
int fasync_helper(int fd, //文件描述符
struct file * filp, //文件指针
int on,
struct fasync_struct **fapp); //要设置的结构
第三个参数on表示设置还是删除,on为真时初始化,为假时(0),移除.
kill_fasync()
:发送信号
void kill_fasync(struct fasync_struct **fp, int sig, int band);
fp
:是已初始化的fasync_struct
数据结构
sig
:要发送的信号
band
:在可读时设置为POLL_IN
,在可写时设置为POLL_OUT
;
驱动实例
static struct fasync_struct *btn_fasync;
static irqreturn_t btn_irq_handler(int irq, void *dev)
{
struct btn_t *p = (struct btn_t *)dev;
//发送信号
kill_fasync(&btn_fasync, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
static int btn_drv_fasync(int fd, struct file *fp, int on)
{
//初始化btn_fasync结构,并添加到异步通知列表中
return fasync_helper(fd, fp, on, &btn_fasync);
}
static int btn_drv_close(struct inode *inode, struct file *filp)
{
if(filp->f_flags & FASYNC)
fasync_helper(-1, flip, 0, &btn_fasync);//将文件从异步通知的列表中删除
return 0;
}
static struct file_operations btn_ops={
......
.release = btn_drv_close,
.fasync = btn_drv_fasync,
};
主要步骤:
1、构造struct fasync_struct
链表的头
2、实现fasync
接口函数,调用fasync_helper
函数来构造struct fasync_struct
节点,并加入链表。
3、在资源可用时,调用kill_fasync
发送信号,并设置资源的可用类型是可读还是可写。
4、在文件最后一次关闭时,即在release
接口中,需要显式调用驱动实现的fasync
接口函数,将节点从链表中删除,这样进程就不会再次收到信号。
异步通知主要还是弄明白信号是软件层次对中断的一种模拟,并且信号是由驱动发出的。