文章目录
一、概念
当硬件设备的数据就绪时,会产生中断,然后在中断处理函数中给上层的进程发送信号SIGIO(29)
,进程收到信号时执行信号处理函数,进程没有收到信号时可以执行其他代码。
异步通知:信号的发送和进程的执行是异步的
二、异步通知IO模型驱动实现
(一)异步通知IO模型实现
#include <my_head.h>
void my_handler(int signum){
printf("This is my_handler\n");
}
int main(int argc, char const *argv[])
{
int fd=0;
//此处打开的是键盘的驱动文件
if(-1 == (fd = open("/dev/input/event1",O_RDWR))){
//打开文件失败
ERR_LOG("open error");
}
//第一步:注册信号
if(SIG_ERR == signal(SIGIO,my_handler))
ERR_LOG("signal error");
//第二步:调用底层的fasync函数
unsigned int flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);
//第三步:告诉驱动,该进程可以接收信号
fcntl(fd,F_SETOWN,getpid());
while(1){
//此时程序可以执行其他操作
//当收到信号后触发信号处理函数
}
return 0;
}
1. fcntl(fd,F_GETFL)调用流程
US:
unsigned int flags = fcntl(fd,F_GETFL)
---------------------------------------------
KS: vi -t sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
==>err = do_fcntl(fd, cmd, arg, f.file);
==>
switch (cmd) {
case F_GETFL:
err = filp->f_flags;
break;}
return err;
-
注:操作为F_GETFL时,本质是将struct file结构体中的f_flags返回
-
注:此处使用的 vi -t 命令需要安装exuberant-ctags软件包,执行命令
sudo apt-get install exuberant-ctags
安装
2. fcntl(fd,F_SETFL,flags|FASYNC)
US:
fcntl(fd,F_SETFL,flags|FASYNC);
-------------------------------------------------------------------
KS: vi -t sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
==>err = do_fcntl(fd, cmd, arg, f.file);
==>switch (cmd) {
case F_SETFL:
err = setfl(fd, filp, arg);
==> //arg = filp->f_flags | FASYNC;
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
//调用驱动的fasync函数执行
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
}
break;
-------------------------------------------------------------------
int mycdev_fasync(int fd, struct file *filp, int on)
{ }
- 注:当操作为F_SETFL时,会执行
arg^filp->flags
操作,执行结果最后会剩下FASYNC标志位,此时与FASYNC相与,如果为真,则说明置位了FASYNC标志位,进而通过struct file结构体找到驱动中的fasync函数
3. fcntl(fd,F_SETOWN,getpid())
US:
fcntl(fd,F_SETOWN,getpid())
-------------------------------------------------------------------
KS: vi -t sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
==>err = do_fcntl(fd, cmd, arg, f.file);
==>switch (cmd) {
case F_SETOWN:
err = f_setown(filp, arg, 1);
==>__f_setown(filp, pid, type, force);
filp->f_owner.pid = get_pid(pid);
break;
- 注:操作为F_SETOWN时,本质就是将pid写入到struct file结构体中的f_owner中的pid
struct file {struct fown_struct f_owner;}
|
struct fown_struct{ struct pid *pid;/* pid or -pgrp where SIGIO should be sent */};
(二)驱动层提供异步通知模型
1. 驱动层中实现异步通知IO模型对应的函数指针
struct file_operations {
int (*fasync) (int, struct file *, int);
}
2. 驱动层使用示例
//定义异步通知队列头
struct fasync_struct * my_fasync=NULL;
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
memset(kbuf, 0, sizeof(kbuf));
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
pr_err("copy_from_user error\n");
return -EIO;
}
cond=1;
//此示例中仍采用,当调用write时,发送信号给驱动的方式
kill_fasync(&my_fasync,SIGIO,POLL_IN);
return size;
}
int mycdev_fasync(int fd, struct file * filp, int on){
return fasync_helper(fd,filp,on,&my_fasync);
}
const struct file_operations myfops={
.open=mycdev_open,
.release=mycdev_close,
.write=mycdev_write,
.read=mycdev_read,
.poll=mycdev_poll,
.fasync=mycdev_fasync, //指定fasync函数指针指向的函数
};
3. 关于fasync_helper函数
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp){ return fasync_add_entry(fd, filp, fapp);}
==>
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
new = fasync_alloc(); //动态分配内存
if (fasync_insert_entry(fd, filp, fapp, new))
}
==>
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
1.判断*fapp是否为NULL,不为NULL则说明new不是队列头,采用类似头插的操作
2.初始化new的值,包括fd和filp
3.返回队列头
}
- 注:即用户使用时需要定义一个struct fasync_struct * 的变量,用来保存函数回传的异步通知队列头。
三、实现区分用户发来的信号
使用上述方法,无论收到的是POLL_IN信号还是POLL_OUT信号都会触发中断。
如果想要区分POLL_IN信号和POLL_OUT信号,需要更改应用程序,使用sigaction函数
(一)sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:获取或者设置信号的属性
参数:
signum:信号的编号 除了 SIGKILL 和 SIGSTOP
act:信号新的属性
oldact:信号之前的属性
返回值:
成功 0
失败 -1 重置错误码
struct sigaction {
void (*sa_handler)(int);//信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理函数
sigset_t sa_mask;//关于阻塞的掩码
int sa_flags;//属性标志位
SA_RESTART 表示信号的自重启属性,关闭自重启属性后 被中断的系统调用会失败 错误码为 EINTR
SA_SIGINFO 如果使用第2个信号处理函数时,需要置位这个属性
void (*sa_restorer)(void);
};
(二)实现示例
test.c
#include <my_head.h>
void my_handler(int signum, siginfo_t *info, void *arg){
printf("This is my_handler\n");
if(signum == SIGIO){
switch (info->si_code)
{
case POLL_IN:
printf("The signal is POLL_IN\n");
break;
case POLL_OUT:
printf("The signal is POLL_OUT\n");
break;
}
}
}
int main(int argc, char const *argv[])
{
int fd=0;
if(-1 == (fd = open("/dev/mycdev",O_RDWR))){
//打开文件失败
ERR_LOG("open error");
}
//第一步:注册信号
fcntl(fd,__F_SETSIG,SIGIO); //让内核空间的数据拷贝到用户空间
struct sigaction action={ //定义结构体
.sa_sigaction = my_handler, //信号处理函数
.sa_flags = SA_SIGINFO, //使用第二个信号处理函数必须置位这个
};
if(sigaction(SIGIO,&action,NULL))
ERR_LOG("signal error");
//第二步:调用底层的fasync函数
unsigned int flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);
//第三步:告诉驱动,该进程可以接收信号
fcntl(fd,F_SETOWN,getpid());
while(1){
//此时程序可以执行其他操作
//当收到信号后触发信号处理函数
}
return 0;
}